Merge commit '4e73b4a3fab11eaa523953de084645744b8c2e05' into dev-release
diff --git a/.gitignore b/.gitignore
index 2e7f798..ceddaef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -266,8 +266,8 @@
 tools/*/art-10.0.0.tar.gz
 tools/*/host/art-12.0.0-beta4
 tools/*/host/art-12.0.0-beta4.tar.gz
-tools/*/host/art-13-master
-tools/*/host/art-13-master.tar.gz
+tools/*/host/art-13-dev
+tools/*/host/art-13-dev.tar.gz
 tools/*/art.tar.gz
 tools/*/dalvik
 tools/*/dalvik-4.0.4
diff --git a/README.md b/README.md
index 2d24142..1621acb 100644
--- a/README.md
+++ b/README.md
@@ -24,11 +24,11 @@
 
     $ git clone https://r8.googlesource.com/r8
     $ cd r8
-    $ tools/gradle.py d8 r8
+    $ tools/gradle.py r8
 
 The `tools/gradle.py` script will bootstrap using depot_tools to download
 a version of gradle to use for building on the first run. This will produce
-two jar files: `build/libs/d8.jar` and `build/libs/r8.jar`.
+a jar file: `build/libs/r8.jar` which contains both R8 and D8.
 
 ## Running D8
 
@@ -45,11 +45,11 @@
 
 Debug mode build:
 
-    $ java -jar build/libs/d8.jar --output out input.jar
+    $ java -cp build/libs/r8.jar com.android.tools.r8.D8  --output out input.jar
 
 Release mode build:
 
-    $ java -jar build/libs/d8.jar --release --output out input.jar
+    $ java -cp build/libs/r8.jar com.android.tools.r8.D8 --release --output out input.jar
 
 The full set of D8 options can be obtained by running the command line tool with
 the `--help` option.
diff --git a/build.gradle b/build.gradle
index e5467f0..5f7b6d0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -369,7 +369,7 @@
                 "linux/art-9.0.0",
                 "linux/art-10.0.0",
                 "linux/host/art-12.0.0-beta4",
-                "linux/host/art-13-master",
+                "linux/host/art-13-dev",
                 "linux/dalvik",
                 "linux/dalvik-4.0.4",
                 "${osString}/dx",
@@ -929,15 +929,6 @@
     outputs.file "${buildDir}/libs/r8_no_manifest.jar"
 }
 
-task D8(type: Jar) {
-    dependsOn r8
-    from zipTree(r8.outputs.files[0])
-    archiveFileName = 'd8.jar'
-    manifest {
-        attributes 'Main-Class': 'com.android.tools.r8.D8'
-    }
-}
-
 def baseCompilerCommandLine(compiler, args = []) {
     // Execute r8 commands against a stable r8 with dependencies.
     // TODO(b/139725780): See if we can remove or lower the heap size (-Xmx8g).
diff --git a/src/library_desugar/java/android/os/Build.java b/src/library_desugar/java/android/os/Build.java
new file mode 100644
index 0000000..908c94c
--- /dev/null
+++ b/src/library_desugar/java/android/os/Build.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2022, 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 android.os;
+
+public class Build {
+  public static class VERSION {
+    public static final int SDK_INT = 0;
+  }
+}
diff --git a/src/library_desugar/java/android/os/StrictMode.java b/src/library_desugar/java/android/os/StrictMode.java
new file mode 100644
index 0000000..5cd5ff1
--- /dev/null
+++ b/src/library_desugar/java/android/os/StrictMode.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2022, 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 android.os;
+
+public class StrictMode {
+
+  public static ThreadPolicy getThreadPolicy() {
+    return null;
+  }
+
+  public static void setThreadPolicy(ThreadPolicy policy) {}
+
+  public static class ThreadPolicy {
+    public static class Builder {
+      public Builder(ThreadPolicy policy) {}
+
+      public Builder permitDiskReads() {
+        return null;
+      }
+
+      public ThreadPolicy build() {
+        return null;
+      }
+    }
+  }
+}
diff --git a/src/library_desugar/java/desugar/sun/nio/fs/DesugarDefaultFileSystemProvider.java b/src/library_desugar/java/desugar/sun/nio/fs/DesugarDefaultFileSystemProvider.java
new file mode 100644
index 0000000..cf36b80
--- /dev/null
+++ b/src/library_desugar/java/desugar/sun/nio/fs/DesugarDefaultFileSystemProvider.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2022, 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 desugar.sun.nio.fs;
+
+import java.nio.file.spi.FileSystemProvider;
+
+public class DesugarDefaultFileSystemProvider {
+
+  public static FileSystemProvider instance() {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/desugar/sun/nio/fs/DesugarDefaultFileTypeDetector.java b/src/library_desugar/java/desugar/sun/nio/fs/DesugarDefaultFileTypeDetector.java
new file mode 100644
index 0000000..916d6e0
--- /dev/null
+++ b/src/library_desugar/java/desugar/sun/nio/fs/DesugarDefaultFileTypeDetector.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2022, 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 desugar.sun.nio.fs;
+
+import java.nio.file.spi.FileTypeDetector;
+
+public class DesugarDefaultFileTypeDetector {
+  public static FileTypeDetector create() {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/j$/nio/file/Files.java b/src/library_desugar/java/j$/nio/file/Files.java
new file mode 100644
index 0000000..2825d24
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/Files.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2022, 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 j$.nio.file;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+public class Files {
+  public static String probeContentType(Path path) throws IOException {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java b/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java
new file mode 100644
index 0000000..658c351
--- /dev/null
+++ b/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2022, 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 java.adapter;
+
+import android.os.Build.VERSION;
+import android.os.StrictMode;
+import android.os.StrictMode.ThreadPolicy;
+import desugar.sun.nio.fs.DesugarDefaultFileSystemProvider;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.spi.FileSystemProvider;
+
+/**
+ * A hybrid file system provider adapter that delegates different implementations based on the
+ * runtime environment.
+ */
+public final class HybridFileSystemProvider {
+  private static final FileSystemProvider INSTANCE = getFileSystemProvider();
+  private static final FileSystem FILE_SYSTEM_INSTANCE =
+      INSTANCE.getFileSystem(URI.create("file:///"));
+
+  private static FileSystemProvider getFileSystemProvider() {
+    if (VERSION.SDK_INT >= 26) {
+      return FileSystems.getDefault().provider();
+    } else {
+      try {
+        // In headless, android.os is absent so the following line will throw.
+        // We cannot set the ThreadPolicy in headless and it is irrelevant.
+        // If we are not in headless, the class will be found and we can set the thread policy.
+        Class.forName("android.os.Build");
+        setThreadPolicy();
+      } catch (ClassNotFoundException ignored) {
+        // Headless mode.
+      }
+      return DesugarDefaultFileSystemProvider.instance();
+    }
+  }
+
+  private static void setThreadPolicy() {
+    // The references to the android.os methods need to be outlined.
+    // TODO(b/207004118): Fix the strict mode allowlisting.
+    ThreadPolicy threadPolicy = StrictMode.getThreadPolicy();
+    StrictMode.setThreadPolicy(new ThreadPolicy.Builder(threadPolicy).permitDiskReads().build());
+  }
+
+  private HybridFileSystemProvider() {}
+
+  /** Returns the platform's default file system provider. */
+  public static FileSystemProvider instance() {
+    return INSTANCE;
+  }
+
+  /** Returns the platform's default file system. */
+  public static FileSystem theFileSystem() {
+    return FILE_SYSTEM_INSTANCE;
+  }
+}
diff --git a/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java b/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java
new file mode 100644
index 0000000..1caf2f1
--- /dev/null
+++ b/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2022, 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 java.adapter;
+
+import android.os.Build.VERSION;
+import desugar.sun.nio.fs.DesugarDefaultFileTypeDetector;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.spi.FileTypeDetector;
+
+/**
+ * A hybrid file type detector adapter that delegates different implementations based on the runtime
+ * environment.
+ */
+public final class HybridFileTypeDetector {
+  private HybridFileTypeDetector() {}
+
+  public static FileTypeDetector create() {
+    if (VERSION.SDK_INT >= 26) {
+      return new PlatformFileTypeDetector();
+    } else {
+      return DesugarDefaultFileTypeDetector.create();
+    }
+  }
+
+  static class PlatformFileTypeDetector extends java.nio.file.spi.FileTypeDetector {
+    @Override
+    public String probeContentType(Path path) throws IOException {
+      // Relies at runtime on java.nio.file.Files.
+      return j$.nio.file.Files.probeContentType(path);
+    }
+  }
+}
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs.json b/src/library_desugar/jdk11/desugar_jdk_libs.json
index c2cea80..6893b8f 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs.json
@@ -6,20 +6,6 @@
   "support_all_callbacks_from_library": true,
   "common_flags": [
     {
-      "api_level_below_or_equal": 32,
-      "rewrite_prefix": {
-        "java.net.URLDecoder": "j$.net.URLDecoder",
-        "java.net.URLEncoder": "j$.net.URLEncoder",
-        "java.io.DesugarInputStream": "j$.io.DesugarInputStream"
-      },
-      "retarget_method_with_emulated_dispatch": {
-        "long java.io.InputStream#transferTo(java.io.OutputStream)": "java.io.DesugarInputStream"
-      },
-      "amend_library_method": [
-        "public long java.io.InputStream#transferTo(java.io.OutputStream)"
-      ]
-    },
-    {
       "api_level_below_or_equal": 30,
       "rewrite_prefix": {
         "java.time.": "j$.time.",
@@ -175,16 +161,6 @@
         "java.util.OptionalInt": "java.util.OptionalConversions",
         "java.util.OptionalLong": "java.util.OptionalConversions"
       }
-    },
-    {
-      "api_level_below_or_equal": 18,
-      "rewrite_prefix": {
-        "java.lang.DesugarCharacter": "j$.lang.DesugarCharacter",
-        "java.nio.charset.StandardCharsets": "j$.nio.charset.StandardCharsets"
-      },
-      "retarget_method": {
-        "boolean java.lang.Character#isBmpCodePoint(int)": "java.lang.DesugarCharacter"
-      }
     }
   ],
   "program_flags": [
@@ -215,76 +191,32 @@
         "long java.util.concurrent.atomic.AtomicLong#getAndUpdate(java.util.function.LongUnaryOperator)": "java.util.concurrent.atomic.DesugarAtomicLong",
         "long java.util.concurrent.atomic.AtomicLong#updateAndGet(java.util.function.LongUnaryOperator)": "java.util.concurrent.atomic.DesugarAtomicLong"
       }
-    },
-    {
-      "api_level_below_or_equal": 19,
-      "dont_retarget": [
-        "android.support.multidex.MultiDexExtractor$ExtractedDex"
-      ]
     }
   ],
   "library_flags": [
     {
-      "api_level_below_or_equal": 32,
-      "rewrite_prefix": {
-        "desugar.": "j$.desugar.",
-        "libcore.": "j$.libcore.",
-        "java.lang.Desugar": "j$.lang.Desugar",
-        "java.lang.ref.Cleaner": "j$.lang.ref.Cleaner",
-        "sun.security.action.": "j$.sun.security.action."
-      }
-    },
-    {
       "api_level_below_or_equal": 30,
+      "rewrite_prefix": {
+        "jdk.internal.": "j$.jdk.internal.",
+        "sun.security.action.": "j$.sun.security.action.",
+        "sun.misc.Desugar": "j$.sun.misc.Desugar"
+      },
       "rewrite_derived_prefix": {
         "java.time.": {
           "j$.time.": "java.time."
         }
-      }
-    },
-    {
-      "api_level_below_or_equal": 25,
-      "rewrite_prefix": {
-        "java.io.DesugarFile": "j$.io.DesugarFile",
-        "java.nio.channels.AsynchronousChannel": "j$.nio.channels.AsynchronousChannel",
-        "java.nio.channels.AsynchronousFileChannel": "j$.nio.channels.AsynchronousFileChannel",
-        "java.nio.channels.CompletionHandler": "j$.nio.channels.CompletionHandler",
-        "java.nio.channels.Desugar": "j$.nio.channels.Desugar",
-        "java.nio.file.": "j$.nio.file.",
-        "jdk.internal.": "j$.jdk.internal.",
-        "sun.misc.Desugar": "j$.sun.misc.Desugar",
-        "sun.nio.cs.": "j$.sun.nio.cs.",
-        "sun.nio.fs.AbstractFileSystemProvider": "j$.sun.nio.fs.AbstractFileSystemProvider",
-        "sun.nio.fs.AbstractFileTypeDetector": "j$.sun.nio.fs.AbstractFileTypeDetector",
-        "sun.nio.fs.BasicFileAttributesHolder": "j$.sun.nio.fs.BasicFileAttributesHolder",
-        "sun.nio.fs.DynamicFileAttributeView": "j$.sun.nio.fs.DynamicFileAttributeView",
-        "sun.util.PreHashedMap": "j$.sun.util.PreHashedMap",
-        "wrapper." : "j$.wrapper."
-      },
-      "rewrite_derived_prefix": {
-        "java.nio.file.attribute.FileTime": {
-          "j$.nio.file.attribute.FileTime": "java.nio.file.attribute.FileTime"
-        },
-        "java.io.": {
-          "__wrapper__.j$.io.": "j$.io.",
-          "__wrapper__.java.io.": "java.io."
-        },
-        "java.nio.": {
-          "__wrapper__.j$.nio.": "j$.nio.",
-          "__wrapper__.java.nio.": "java.nio."
-        }
-      },
-      "retarget_method": {
-        "boolean java.util.Arrays#deepEquals0(java.lang.Object, java.lang.Object)": "java.util.DesugarArrays"
-      },
-      "retarget_method_with_emulated_dispatch": {
-        "java.nio.file.Path java.io.File#toPath()": "java.io.DesugarFile"
       },
       "backport": {
         "java.lang.DesugarDouble": "java.lang.Double",
         "java.lang.DesugarInteger": "java.lang.Integer",
         "java.lang.DesugarLong": "java.lang.Long",
         "java.lang.DesugarMath": "java.lang.Math"
+      }
+    },
+    {
+      "api_level_below_or_equal": 25,
+      "retarget_method": {
+        "boolean java.util.Arrays#deepEquals0(java.lang.Object, java.lang.Object)": "java.util.DesugarArrays"
       },
       "amend_library_method": [
         "private static boolean java.util.Arrays#deepEquals0(java.lang.Object, java.lang.Object)"
@@ -293,8 +225,6 @@
     {
       "api_level_below_or_equal": 23,
       "rewrite_prefix": {
-        "java.lang.FunctionalInterface": "j$.lang.FunctionalInterface",
-        "java.nio.channels.SeekableByteChannel": "j$.nio.channels.SeekableByteChannel",
         "java.util.AbstractList": "j$.util.AbstractList",
         "java.util.CollSer": "j$.util.CollSer",
         "java.util.Comparators": "j$.util.Comparators",
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_legacy.json b/src/library_desugar/jdk11/desugar_jdk_libs_legacy.json
index 1985550..730a294 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_legacy.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_legacy.json
@@ -170,6 +170,7 @@
       "api_level_below_or_equal": 30,
       "rewrite_prefix": {
         "j$.time.": "java.time.",
+        "jdk.internal.": "j$.jdk.internal.",
         "sun.misc.Desugar": "j$.sun.misc.Desugar",
         "sun.security.action.": "j$.sun.security.action."
       },
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json b/src/library_desugar/jdk11/desugar_jdk_libs_path.json
similarity index 83%
copy from src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json
copy to src/library_desugar/jdk11/desugar_jdk_libs_path.json
index 57f7c3e..092b976 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_path.json
@@ -1,9 +1,9 @@
 {
-  "identifier": "com.tools.android:desugar_jdk_libs_alternative_3:2.0.0",
+  "identifier": "com.tools.android:desugar_jdk_libs:2.0.0",
   "configuration_format_version": 100,
   "required_compilation_api_level": 30,
   "synthesized_library_classes_package_prefix": "j$.",
-  "support_all_callbacks_from_library": false,
+  "support_all_callbacks_from_library": true,
   "common_flags": [
     {
       "api_level_below_or_equal": 32,
@@ -52,9 +52,68 @@
       }
     },
     {
+      "api_level_below_or_equal": 25,
+      "rewrite_prefix": {
+        "java.io.DesugarFile": "j$.io.DesugarFile",
+        "java.nio.channels.AsynchronousChannel": "j$.nio.channels.AsynchronousChannel",
+        "java.nio.channels.AsynchronousFileChannel": "j$.nio.channels.AsynchronousFileChannel",
+        "java.nio.channels.CompletionHandler": "j$.nio.channels.CompletionHandler",
+        "java.nio.file.": "j$.nio.file."
+      },
+      "retarget_method_with_emulated_dispatch": {
+        "java.nio.file.Path java.io.File#toPath()": "java.io.DesugarFile"
+      },
+      "wrapper_conversion": [
+        "java.nio.channels.AsynchronousChannel",
+        "java.nio.channels.CompletionHandler",
+        "java.nio.file.Path",
+        "java.nio.file.FileSystem",
+        "java.nio.file.WatchService",
+        "java.nio.file.WatchEvent$Kind",
+        "java.nio.file.WatchKey",
+        "java.nio.file.Watchable",
+        "java.nio.file.PathMatcher",
+        "java.nio.file.WatchEvent$Modifier",
+        "java.nio.file.attribute.UserPrincipalLookupService",
+        "java.nio.file.spi.FileSystemProvider",
+        "java.nio.file.AccessMode",
+        "java.nio.file.LinkOption",
+        "java.nio.file.CopyOption",
+        "java.nio.file.attribute.GroupPrincipal",
+        "java.nio.file.attribute.FileAttribute",
+        "java.nio.file.attribute.FileAttributeView",
+        "java.nio.file.attribute.UserPrincipal",
+        "java.nio.file.FileStore",
+        "java.nio.file.attribute.FileStoreAttributeView",
+        "java.nio.file.DirectoryStream$Filter",
+        "java.nio.file.DirectoryStream",
+        "java.nio.file.OpenOption",
+        "java.nio.file.attribute.BasicFileAttributes"
+      ],
+      "wrapper_conversion_excluding": {
+        "java.nio.channels.AsynchronousFileChannel": [
+          "void java.nio.channels.AsynchronousFileChannel#lock(java.lang.Object, java.nio.channels.CompletionHandler)",
+          "java.util.concurrent.Future java.nio.channels.AsynchronousFileChannel#lock()",
+          "java.nio.channels.FileLock java.nio.channels.AsynchronousFileChannel#tryLock()"
+        ]
+      },
+      "custom_conversion": {
+        "java.nio.file.attribute.FileTime": "java.nio.file.attribute.FileAttributeConversions"
+      }
+    },
+    {
+      "api_level_below_or_equal": 32,
+      "api_level_greater_or_equal": 24,
+      "rewrite_prefix": {
+        "java.util.stream.Collectors": "j$.util.stream.Collectors"
+      }
+    },
+    {
       "api_level_below_or_equal": 23,
       "rewrite_prefix": {
         "java.io.DesugarBufferedReader": "j$.io.DesugarBufferedReader",
+        "java.nio.channels.FileChannel": "j$.nio.channels.FileChannel",
+        "java.nio.channels.SeekableByteChannel": "j$.nio.channels.SeekableByteChannel",
         "java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatistics",
         "java.util.IntSummaryStatistics": "j$.util.IntSummaryStatistics",
         "java.util.LongSummaryStatistics": "j$.util.LongSummaryStatistics",
@@ -69,6 +128,9 @@
         "java.util.function.": "j$.util.function.",
         "java.util.stream.": "j$.util.stream."
       },
+      "dont_rewrite_prefix": [
+        "java.nio.channels.FileChannel$MapMode"
+      ],
       "maintain_prefix": [
         "java.io.UncheckedIOException"
       ],
@@ -110,6 +172,7 @@
         "java.util.Spliterator java.util.LinkedHashSet#spliterator()": "java.util.DesugarLinkedHashSet"
       },
       "wrapper_conversion": [
+        "java.nio.channels.SeekableByteChannel",
         "java.util.function.IntUnaryOperator",
         "java.util.function.BiFunction",
         "java.util.function.IntConsumer",
@@ -164,6 +227,16 @@
         "java.util.Spliterator": [
           "boolean java.util.Spliterator#hasCharacteristics(int)",
           "long java.util.Spliterator#getExactSizeIfKnown()"
+        ],
+        "java.nio.channels.FileChannel": [
+          "long java.nio.channels.FileChannel#read(java.nio.ByteBuffer[])",
+          "long java.nio.channels.FileChannel#write(java.nio.ByteBuffer[])",
+          "java.nio.channels.FileLock java.nio.channels.FileChannel#lock()",
+          "java.nio.channels.FileLock java.nio.channels.FileChannel#tryLock()",
+          "void java.nio.channels.spi.AbstractInterruptibleChannel#close()",
+          "boolean java.nio.channels.spi.AbstractInterruptibleChannel#isOpen()",
+          "void java.nio.channels.spi.AbstractInterruptibleChannel#begin()",
+          "void java.nio.channels.spi.AbstractInterruptibleChannel#end(boolean)"
         ]
       },
       "custom_conversion": {
@@ -198,6 +271,16 @@
       }
     },
     {
+      "api_level_below_or_equal": 25,
+      "rewrite_prefix": {
+        "java.io.DesugarFile": "j$.io.DesugarFile",
+        "java.nio.file.": "j$.nio.file."
+      },
+      "retarget_method_with_emulated_dispatch": {
+        "java.nio.file.Path java.io.File#toPath()": "java.io.DesugarFile"
+      }
+    },
+    {
       "api_level_below_or_equal": 23,
       "retarget_method": {
         "int java.util.concurrent.atomic.AtomicInteger#accumulateAndGet(int, java.util.function.IntBinaryOperator)": "java.util.concurrent.atomic.DesugarAtomicInteger",
@@ -245,12 +328,7 @@
     {
       "api_level_below_or_equal": 25,
       "rewrite_prefix": {
-        "java.io.DesugarFile": "j$.io.DesugarFile",
-        "java.nio.channels.AsynchronousChannel": "j$.nio.channels.AsynchronousChannel",
-        "java.nio.channels.AsynchronousFileChannel": "j$.nio.channels.AsynchronousFileChannel",
-        "java.nio.channels.CompletionHandler": "j$.nio.channels.CompletionHandler",
         "java.nio.channels.Desugar": "j$.nio.channels.Desugar",
-        "java.nio.file.": "j$.nio.file.",
         "jdk.internal.": "j$.jdk.internal.",
         "sun.misc.Desugar": "j$.sun.misc.Desugar",
         "sun.nio.cs.": "j$.sun.nio.cs.",
@@ -259,27 +337,19 @@
         "sun.nio.fs.BasicFileAttributesHolder": "j$.sun.nio.fs.BasicFileAttributesHolder",
         "sun.nio.fs.DynamicFileAttributeView": "j$.sun.nio.fs.DynamicFileAttributeView",
         "sun.util.PreHashedMap": "j$.sun.util.PreHashedMap",
-        "wrapper." : "j$.wrapper."
+        "java.adapter" : "j$.adapter"
       },
       "rewrite_derived_prefix": {
         "java.nio.file.attribute.FileTime": {
           "j$.nio.file.attribute.FileTime": "java.nio.file.attribute.FileTime"
         },
-        "java.io.": {
-          "__wrapper__.j$.io.": "j$.io.",
-          "__wrapper__.java.io.": "java.io."
-        },
-        "java.nio.": {
-          "__wrapper__.j$.nio.": "j$.nio.",
-          "__wrapper__.java.nio.": "java.nio."
+        "java.nio.file.Files": {
+          "j$.nio.file.Files": "java.nio.file.Files"
         }
       },
       "retarget_method": {
         "boolean java.util.Arrays#deepEquals0(java.lang.Object, java.lang.Object)": "java.util.DesugarArrays"
       },
-      "retarget_method_with_emulated_dispatch": {
-        "java.nio.file.Path java.io.File#toPath()": "java.io.DesugarFile"
-      },
       "backport": {
         "java.lang.DesugarDouble": "java.lang.Double",
         "java.lang.DesugarInteger": "java.lang.Integer",
@@ -294,7 +364,6 @@
       "api_level_below_or_equal": 23,
       "rewrite_prefix": {
         "java.lang.FunctionalInterface": "j$.lang.FunctionalInterface",
-        "java.nio.channels.SeekableByteChannel": "j$.nio.channels.SeekableByteChannel",
         "java.util.AbstractList": "j$.util.AbstractList",
         "java.util.CollSer": "j$.util.CollSer",
         "java.util.Comparators": "j$.util.Comparators",
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json b/src/library_desugar/jdk11/desugar_jdk_libs_path_alternative_3.json
similarity index 98%
rename from src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json
rename to src/library_desugar/jdk11/desugar_jdk_libs_path_alternative_3.json
index 57f7c3e..5064ba5 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_path_alternative_3.json
@@ -262,9 +262,6 @@
         "wrapper." : "j$.wrapper."
       },
       "rewrite_derived_prefix": {
-        "java.nio.file.attribute.FileTime": {
-          "j$.nio.file.attribute.FileTime": "java.nio.file.attribute.FileTime"
-        },
         "java.io.": {
           "__wrapper__.j$.io.": "j$.io.",
           "__wrapper__.java.io.": "java.io."
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index acbd677..fe2ed57 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -146,6 +146,41 @@
     }
 
     /**
+     * Set a consumer for receiving the proguard-map content.
+     *
+     * <p>Note that when a proguard-map consumer is specified for a release build, the compiler will
+     * optimize the line-number information and obtaining a source-level stacktrace will require the
+     * use of a retrace tool exactly as is needed for programs built by R8.
+     *
+     * <p>Note that any subsequent call to this method or {@link #setProguardMapOutputPath} will
+     * override the previous setting.
+     *
+     * @param proguardMapConsumer Consumer to receive the content once produced.
+     */
+    @Override
+    public Builder setProguardMapConsumer(StringConsumer proguardMapConsumer) {
+      return super.setProguardMapConsumer(proguardMapConsumer);
+    }
+
+    /**
+     * Set an output destination to which proguard-map content should be written.
+     *
+     * <p>Note that when a proguard-map output is specified for a release build, the compiler will
+     * optimize the line-number information and obtaining a source-level stacktrace will require the
+     * use of a retrace tool exactly as is needed for programs built by R8.
+     *
+     * <p>This is a short-hand for setting a {@link StringConsumer.FileConsumer} using {@link
+     * #setProguardMapConsumer}. Note that any subsequent call to this method or {@link
+     * #setProguardMapConsumer} will override the previous setting.
+     *
+     * @param proguardMapOutput File-system path to write output at.
+     */
+    @Override
+    public Builder setProguardMapOutputPath(Path proguardMapOutput) {
+      return super.setProguardMapOutputPath(proguardMapOutput);
+    }
+
+    /**
      * Indicate if compilation is to intermediate results, i.e., intended for later merging.
      *
      * <p>When compiling to intermediate mode, the compiler will avoid sharing of synthetic items,
@@ -376,6 +411,7 @@
           getThreadCount(),
           getDumpInputFlags(),
           getMapIdProvider(),
+          proguardMapConsumer,
           factory);
     }
   }
@@ -392,6 +428,7 @@
   private final boolean enableMainDexListCheck;
   private final boolean minimalMainDex;
   private final ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
+  private final StringConsumer proguardMapConsumer;
   private final DexItemFactory factory;
 
   public static Builder builder() {
@@ -460,6 +497,7 @@
       int threadCount,
       DumpInputFlags dumpInputFlags,
       MapIdProvider mapIdProvider,
+      StringConsumer proguardMapConsumer,
       DexItemFactory factory) {
     super(
         inputApp,
@@ -488,6 +526,7 @@
     this.enableMainDexListCheck = enableMainDexListCheck;
     this.minimalMainDex = minimalMainDex;
     this.mainDexKeepRules = mainDexKeepRules;
+    this.proguardMapConsumer = proguardMapConsumer;
     this.factory = factory;
   }
 
@@ -503,6 +542,7 @@
     enableMainDexListCheck = true;
     minimalMainDex = false;
     mainDexKeepRules = null;
+    proguardMapConsumer = null;
     factory = null;
   }
 
@@ -532,7 +572,11 @@
     internal.setGlobalSyntheticsConsumer(globalSyntheticsConsumer);
     internal.desugarGraphConsumer = desugarGraphConsumer;
     internal.mainDexKeepRules = mainDexKeepRules;
-    internal.lineNumberOptimization = LineNumberOptimization.OFF;
+    internal.proguardMapConsumer = proguardMapConsumer;
+    internal.lineNumberOptimization =
+        !internal.debug && proguardMapConsumer != null
+            ? LineNumberOptimization.ON
+            : LineNumberOptimization.OFF;
 
     // Assert and fixup defaults.
     assert !internal.isShrinking();
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index 9e729e0..719120f 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -30,6 +30,7 @@
           "--lib",
           "--classpath",
           "--pg-map",
+          "--pg-map-output",
           MIN_API_FLAG,
           "--main-dex-rules",
           "--main-dex-list",
@@ -131,6 +132,9 @@
                       + AndroidApiLevel.getDefault().getLevel()
                       + ".",
                   "  --pg-map <file>         # Use <file> as a mapping file for distribution.",
+                  // TODO(b/183125319): Add help info once supported.
+                  // "  --pg-map-output <file>  # Enable line optimization and output mapping to"
+                  //     +" <file>.",
                   "  --intermediate          # Compile an intermediate result intended for later",
                   "                          # merging.",
                   "  --file-per-class        # Produce a separate dex file per class.",
@@ -226,6 +230,8 @@
         outputMode = OutputMode.ClassFile;
       } else if (arg.equals("--pg-map")) {
         builder.setProguardInputMapFile(Paths.get(nextArg));
+      } else if (arg.equals("--pg-map-output")) {
+        builder.setProguardMapOutputPath(Paths.get(nextArg));
       } else if (arg.equals("--output")) {
         if (outputPath != null) {
           builder.error(
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
deleted file mode 100644
index a7ac9e3..0000000
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ /dev/null
@@ -1,155 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8;
-
-import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
-
-import com.android.tools.r8.DexIndexedConsumer.DirectoryConsumer;
-import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.dex.ApplicationWriter;
-import com.android.tools.r8.dex.Marker;
-import com.android.tools.r8.graph.AppInfo;
-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.graph.LazyLoadedDexApplication;
-import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.shaking.MainDexInfo;
-import com.android.tools.r8.utils.ExceptionUtils;
-import com.android.tools.r8.utils.FeatureClassMapping;
-import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.InternalOptions.DesugarState;
-import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.Timing;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-
-@Keep
-public final class DexSplitterHelper {
-
-  public static void run(
-      D8Command command, FeatureClassMapping featureClassMapping, String output, String proguardMap)
-      throws CompilationFailedException {
-    ExecutorService executor = ThreadUtils.getExecutorService(ThreadUtils.NOT_SPECIFIED);
-    try {
-      ExceptionUtils.withCompilationHandler(
-          command.getReporter(),
-          () -> run(command, featureClassMapping, output, proguardMap, executor));
-    } finally {
-      executor.shutdown();
-    }
-  }
-
-  public static void run(
-      D8Command command,
-      FeatureClassMapping featureClassMapping,
-      String output,
-      String proguardMap,
-      ExecutorService executor)
-      throws IOException {
-    InternalOptions options = command.getInternalOptions();
-    options.desugarState = DesugarState.OFF;
-    options.enableMainDexListCheck = false;
-    options.ignoreMainDexMissingClasses = true;
-    options.minimalMainDex = false;
-    assert !options.isMinifying();
-    options.inlinerOptions().enableInlining = false;
-    options.outline.enabled = false;
-
-    try {
-      Timing timing = new Timing("DexSplitter");
-      ApplicationReader applicationReader =
-          new ApplicationReader(command.getInputApp(), options, timing);
-      DexApplication app = applicationReader.read(executor);
-      MainDexInfo mainDexInfo = applicationReader.readMainDexClasses(app);
-
-      List<Marker> markers = app.dexItemFactory.extractMarkers();
-
-      ClassNameMapper mapper = null;
-      if (proguardMap != null) {
-        mapper = ClassNameMapper.mapperFromFile(Paths.get(proguardMap));
-      }
-      Map<String, LazyLoadedDexApplication.Builder> applications =
-          getDistribution(app, featureClassMapping, mapper);
-      for (Entry<String, LazyLoadedDexApplication.Builder> entry : applications.entrySet()) {
-        String feature = entry.getKey();
-        timing.begin("Feature " + feature);
-        DexApplication featureApp = entry.getValue().build();
-        assert !options.hasMethodsFilter();
-
-        // If this is the base, we add the main dex list.
-        AppInfo appInfo =
-            feature.equals(featureClassMapping.getBaseName())
-                ? AppInfo.createInitialAppInfo(featureApp, mainDexInfo)
-                : AppInfo.createInitialAppInfo(featureApp);
-        AppView<AppInfo> appView = AppView.createForD8(appInfo);
-
-        // Run d8 optimize to ensure jumbo strings are handled.
-        D8.optimize(appView, options, timing, executor);
-
-        // We create a specific consumer for each split.
-        Path outputDir = Paths.get(output).resolve(entry.getKey());
-        if (!Files.exists(outputDir)) {
-          Files.createDirectory(outputDir);
-        }
-        DexIndexedConsumer consumer = new DirectoryConsumer(outputDir);
-
-        try {
-          new ApplicationWriter(
-                  appView,
-                  markers,
-                  NamingLens.getIdentityLens(),
-                  consumer)
-              .write(executor);
-          options.printWarnings();
-        } finally {
-          consumer.finished(options.reporter);
-        }
-        timing.end();
-      }
-    } catch (ExecutionException e) {
-      throw unwrapExecutionException(e);
-    } catch (FeatureMappingException e) {
-      options.reporter.error(e.getMessage());
-    } finally {
-      options.signalFinishedToConsumers();
-    }
-  }
-
-  private static Map<String, LazyLoadedDexApplication.Builder> getDistribution(
-      DexApplication app, FeatureClassMapping featureClassMapping, ClassNameMapper mapper)
-      throws FeatureMappingException {
-    Map<String, LazyLoadedDexApplication.Builder> applications = new HashMap<>();
-    for (DexProgramClass clazz : app.classes()) {
-      String clazzName =
-          mapper != null ? mapper.deobfuscateClassName(clazz.toString()) : clazz.toString();
-      String feature = featureClassMapping.featureForClass(clazzName);
-      LazyLoadedDexApplication.Builder featureApplication = applications.get(feature);
-      if (featureApplication == null) {
-        featureApplication = DexApplication.builder(app.options, app.timing);
-        applications.put(feature, featureApplication);
-      }
-      featureApplication.addProgramClass(clazz);
-    }
-    return applications;
-  }
-
-  public static void runD8ForTesting(D8Command command, boolean dontCreateMarkerInD8)
-      throws CompilationFailedException {
-    InternalOptions options = command.getInternalOptions();
-    options.testing.dontCreateMarkerInD8 = dontCreateMarkerInD8;
-    D8.runForTesting(command.getInputApp(), options);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 888b78b..7bdb3b9 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -986,7 +986,11 @@
     internal.setDesugaredLibrarySpecification(desugaredLibrarySpecification);
     internal.synthesizedClassPrefix = synthesizedClassPrefix;
     // TODO(b/214382176): Enable all the time.
-    internal.loadAllClassDefinitions = !synthesizedClassPrefix.isEmpty();
+    boolean l8Shrinking = !synthesizedClassPrefix.isEmpty();
+    internal.loadAllClassDefinitions = l8Shrinking;
+    if (l8Shrinking) {
+      internal.apiModelingOptions().disableSubbingOfClasses();
+    }
     internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
 
     // Set up the map and source file providers.
diff --git a/src/main/java/com/android/tools/r8/SwissArmyKnife.java b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
index 2d836ae..2e44e14 100644
--- a/src/main/java/com/android/tools/r8/SwissArmyKnife.java
+++ b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
@@ -6,7 +6,6 @@
 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;
 import com.android.tools.r8.tracereferences.TraceReferences;
 import java.util.Arrays;
@@ -41,9 +40,6 @@
       case "dexsegments":
         DexSegments.main(shift(args));
         break;
-      case "dexsplitter":
-        DexSplitter.main(shift(args));
-        break;
       case "disasm":
         Disassemble.main(shift(args));
         break;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
index 67e44d4..af0b133 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -215,4 +216,16 @@
     FrameType frameType = FrameType.fromNumericType(type, dexItemFactory);
     frameBuilder.popAndDiscard(frameType, frameType).push(frameType);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState state,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // ..., value1, value2 →
+    // ..., result
+    FrameType frameType = FrameType.fromNumericType(type, dexItemFactory);
+    return state.pop(appView, frameType, frameType).push(frameType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
index 35a03f2..cdd43c2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -20,6 +21,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -91,4 +93,14 @@
         .popAndDiscardInitialized(dexItemFactory.objectArrayType)
         .push(dexItemFactory.intType);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index dc6a732..5069f14 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -24,6 +25,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -136,4 +138,14 @@
     frameBuilder.popAndDiscardInitialized(dexItemFactory.objectArrayType, dexItemFactory.intType);
     frameBuilder.push(FrameType.fromMemberType(type, dexItemFactory));
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
index 39db93e..c51336f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -23,6 +24,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -127,4 +129,14 @@
         .popAndDiscard(FrameType.fromMemberType(type, dexItemFactory))
         .popAndDiscardInitialized(dexItemFactory.objectArrayType, dexItemFactory.intType);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java b/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java
new file mode 100644
index 0000000..828a2f2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2022, 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.code;
+
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.MemberType;
+
+public class CfAssignability {
+
+  // Based on https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.2.
+  public static boolean isAssignable(FrameType source, FrameType target, AppView<?> appView) {
+    if (target.isTop()) {
+      return true;
+    }
+    if (source.isTop()) {
+      return false;
+    }
+    if (source.isWide() != target.isWide()) {
+      return false;
+    }
+    if (target.isOneWord() || target.isTwoWord()) {
+      return true;
+    }
+    if (source.isUninitializedThis() && target.isUninitializedThis()) {
+      return true;
+    }
+    if (source.isUninitializedNew() && target.isUninitializedNew()) {
+      // TODO(b/168190134): Allow for picking the offset from the target if not set.
+      DexType uninitializedNewTypeSource = source.getUninitializedNewType();
+      DexType uninitializedNewTypeTarget = target.getUninitializedNewType();
+      return uninitializedNewTypeSource == null
+          || uninitializedNewTypeTarget == null
+          || uninitializedNewTypeSource == uninitializedNewTypeTarget;
+    }
+    // TODO(b/168190267): Clean-up the lattice.
+    DexItemFactory factory = appView.dexItemFactory();
+    if (!source.isInitialized()
+        && target.isInitialized()
+        && target.getInitializedType() == factory.objectType) {
+      return true;
+    }
+    if (source.isInitialized() && target.isInitialized()) {
+      // Both are instantiated types and we resort to primitive tyoe/java type hierarchy checking.
+      return isAssignable(source.getInitializedType(), target.getInitializedType(), appView);
+    }
+    return false;
+  }
+
+  // Rules found at https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.2
+  public static boolean isAssignable(DexType source, DexType target, AppView<?> appView) {
+    DexItemFactory factory = appView.dexItemFactory();
+    source = byteCharShortOrBooleanToInt(source, factory);
+    target = byteCharShortOrBooleanToInt(target, factory);
+    if (source == target) {
+      return true;
+    }
+    if (source.isPrimitiveType() || target.isPrimitiveType()) {
+      return false;
+    }
+    // Both are now references - everything is assignable to object.
+    if (target == factory.objectType) {
+      return true;
+    }
+    // isAssignable(null, class(_, _)).
+    // isAssignable(null, arrayOf(_)).
+    if (source == DexItemFactory.nullValueType) {
+      return true;
+    }
+    if (target.isArrayType() != target.isArrayType()) {
+      return false;
+    }
+    if (target.isArrayType()) {
+      return isAssignable(
+          target.toArrayElementType(factory), target.toArrayElementType(factory), appView);
+    }
+    // TODO(b/166570659): Do a sub-type check that allows for missing classes in hierarchy.
+    return MemberType.fromDexType(source) == MemberType.fromDexType(target);
+  }
+
+  private static DexType byteCharShortOrBooleanToInt(DexType type, DexItemFactory factory) {
+    // byte, char, short and boolean has verification type int.
+    if (type.isByteType() || type.isCharType() || type.isShortType() || type.isBooleanType()) {
+      return factory.intType;
+    }
+    return type;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index edd6603..55dd90f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -23,6 +24,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
@@ -140,4 +142,14 @@
     // ..., objectref
     frameBuilder.popAndDiscardInitialized(dexItemFactory.objectType).push(type);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
index 5b3c937..4ca69a0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -25,6 +26,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -140,4 +142,14 @@
     FrameType frameType = FrameType.fromNumericType(type, dexItemFactory);
     frameBuilder.popAndDiscard(frameType, frameType).push(dexItemFactory.intType);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
index abc6070..10240e5 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -23,6 +24,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
@@ -147,4 +149,14 @@
     // ..., value
     frameBuilder.push(dexItemFactory.classType);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
index 3416ff7..666da46 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import java.util.ListIterator;
 import org.objectweb.asm.ConstantDynamic;
@@ -234,4 +235,14 @@
     // ..., value
     frameBuilder.push(dexItemFactory.classType);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
index 48db7ef..2f79cea 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -23,6 +24,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
@@ -111,4 +113,14 @@
     // ..., value
     frameBuilder.push(dexItemFactory.methodHandleType);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
index 855690f..0cc8d78 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -22,6 +23,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
@@ -109,4 +111,14 @@
     // ..., value
     frameBuilder.push(dexItemFactory.methodTypeType);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
index d58b161..efcd063 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -20,6 +21,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -81,4 +83,14 @@
     // ..., value
     frameBuilder.push(DexItemFactory.nullValueType);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
index b1d9c5b..cee2f4a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -21,6 +22,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
 import org.objectweb.asm.MethodVisitor;
@@ -238,4 +240,14 @@
     // ..., value
     frameBuilder.push(type.toPrimitiveType().toDexType(dexItemFactory));
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
index f03e9c7..4e4a325 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import org.objectweb.asm.MethodVisitor;
 
@@ -112,4 +113,15 @@
     // ..., value
     frameBuilder.push(dexItemFactory.stringType);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // ... →
+    // ..., value
+    return frame.push(dexItemFactory.stringType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
index cdc4235..5ab3742 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -24,6 +25,7 @@
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
@@ -131,4 +133,14 @@
     // ..., value
     frameBuilder.push(dexItemFactory.stringType);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index ce94d5d..80813e1 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -3,27 +3,17 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
-import static com.android.tools.r8.utils.BiPredicateUtils.or;
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexField;
 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.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.conversion.CfSourceCode;
-import com.android.tools.r8.ir.conversion.CfState;
-import com.android.tools.r8.ir.conversion.CfState.Slot;
-import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -32,20 +22,20 @@
 
 public abstract class CfFieldInstruction extends CfInstruction {
 
-  private final int opcode;
   private final DexField field;
   private final DexField declaringField;
 
   private static void specify(StructuralSpecification<CfFieldInstruction, ?> spec) {
-    spec.withInt(f -> f.opcode).withItem(f -> f.field).withItem(f -> f.declaringField);
+    spec.withInt(CfFieldInstruction::getOpcode)
+        .withItem(CfFieldInstruction::getField)
+        .withItem(CfFieldInstruction::getDeclaringField);
   }
 
-  public CfFieldInstruction(int opcode, DexField field) {
-    this(opcode, field, field);
+  public CfFieldInstruction(DexField field) {
+    this(field, field);
   }
 
-  public CfFieldInstruction(int opcode, DexField field, DexField declaringField) {
-    this.opcode = opcode;
+  public CfFieldInstruction(DexField field, DexField declaringField) {
     this.field = field;
     this.declaringField = declaringField;
     assert field.type == declaringField.type;
@@ -70,13 +60,15 @@
     return field;
   }
 
-  public int getOpcode() {
-    return opcode;
+  public DexField getDeclaringField() {
+    return declaringField;
   }
 
+  public abstract int getOpcode();
+
   @Override
   public int getCompareToId() {
-    return opcode;
+    return getOpcode();
   }
 
   @Override
@@ -86,15 +78,15 @@
   }
 
   public boolean isFieldGet() {
-    return opcode == Opcodes.GETFIELD || opcode == Opcodes.GETSTATIC;
+    return false;
   }
 
   public boolean isStaticFieldGet() {
-    return opcode == Opcodes.GETSTATIC;
+    return false;
   }
 
   public boolean isStaticFieldPut() {
-    return opcode == Opcodes.PUTSTATIC;
+    return false;
   }
 
   public abstract CfFieldInstruction createWithField(DexField field);
@@ -124,7 +116,7 @@
     String owner = namingLens.lookupInternalName(rewrittenField.holder);
     String name = namingLens.lookupName(rewrittenDeclaringField).toString();
     String desc = namingLens.lookupDescriptor(rewrittenField.type).toString();
-    visitor.visitFieldInsn(opcode, owner, name, desc);
+    visitor.visitFieldInsn(getOpcode(), owner, name, desc);
   }
 
   @Override
@@ -141,92 +133,4 @@
   public boolean canThrow() {
     return true;
   }
-
-  @Override
-  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
-    DexType type = field.type;
-    switch (opcode) {
-      case Opcodes.GETSTATIC:
-        {
-          builder.addStaticGet(state.push(type).register, field);
-          break;
-        }
-      case Opcodes.PUTSTATIC:
-        {
-          Slot value = state.pop();
-          builder.addStaticPut(value.register, field);
-          break;
-        }
-      case Opcodes.GETFIELD:
-        {
-          Slot object = state.pop();
-          builder.addInstanceGet(state.push(type).register, object.register, field);
-          break;
-        }
-      case Opcodes.PUTFIELD:
-        {
-          Slot value = state.pop();
-          Slot object = state.pop();
-          builder.addInstancePut(value.register, object.register, field);
-          break;
-        }
-      default:
-        throw new Unreachable("Unexpected opcode " + opcode);
-    }
-  }
-
-  @Override
-  public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
-    switch (opcode) {
-      case Opcodes.GETSTATIC:
-        return inliningConstraints.forStaticGet(field, context);
-      case Opcodes.PUTSTATIC:
-        return inliningConstraints.forStaticPut(field, context);
-      case Opcodes.GETFIELD:
-        return inliningConstraints.forInstanceGet(field, context);
-      case Opcodes.PUTFIELD:
-        return inliningConstraints.forInstancePut(field, context);
-      default:
-        throw new Unreachable("Unexpected opcode " + opcode);
-    }
-  }
-
-  @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    switch (opcode) {
-      case Opcodes.GETFIELD:
-        // ..., objectref →
-        // ..., value
-        frameBuilder.popAndDiscardInitialized(field.holder).push(field.type);
-        return;
-      case Opcodes.GETSTATIC:
-        // ..., →
-        // ..., value
-        frameBuilder.push(field.type);
-        return;
-      case Opcodes.PUTFIELD:
-        // ..., objectref, value →
-        // ...,
-        frameBuilder
-            .popAndDiscardInitialized(field.type)
-            .pop(
-                field.holder,
-                or(
-                    frameBuilder::isUninitializedThisAndTarget,
-                    frameBuilder::isAssignableAndInitialized));
-        return;
-      case Opcodes.PUTSTATIC:
-        // ..., value →
-        // ...
-        frameBuilder.popAndDiscardInitialized(field.type);
-        return;
-      default:
-        throw new Unreachable("Unexpected opcode " + opcode);
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index 7f7f08b..e7b8c99 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -6,6 +6,7 @@
 import static org.objectweb.asm.Opcodes.F_NEW;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -26,6 +27,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
@@ -377,12 +379,6 @@
     return locals;
   }
 
-  // This is used from tests. As fastutils are repackaged and minified the method above is
-  // not available from tests which use fastutils in their original namespace.
-  public SortedMap<Integer, FrameType> getLocalsAsSortedMap() {
-    return locals;
-  }
-
   public Deque<FrameType> getStack() {
     return stack;
   }
@@ -504,6 +500,16 @@
     frameBuilder.checkFrameAndSet(this);
   }
 
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
+
   public CfFrame markInstantiated(FrameType uninitializedType, DexType initType) {
     if (uninitializedType.isInitialized()) {
       throw CfCodeStackMapValidatingException.error(
@@ -520,7 +526,8 @@
     return new CfFrame(newLocals, newStack);
   }
 
-  private FrameType getInitializedFrameType(FrameType unInit, FrameType other, DexType newType) {
+  public static FrameType getInitializedFrameType(
+      FrameType unInit, FrameType other, DexType newType) {
     assert !unInit.isInitialized();
     if (other.isInitialized()) {
       return other;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
index c81abe4..aad0ba6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.BiPredicateUtils.or;
 
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCodeStackMapValidatingException;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
@@ -27,17 +28,16 @@
 
 public class CfFrameVerificationHelper {
 
-  private final CfFrame NO_FRAME =
+  private static final CfFrame NO_FRAME =
       new CfFrame(
           ImmutableInt2ReferenceSortedMap.<FrameType>builder().build(), ImmutableDeque.of());
 
-  private final Deque<FrameType> throwStack;
+  private final AppView<?> appView;
+  private final DexItemFactory factory;
 
   private CfFrame currentFrame = NO_FRAME;
   private final DexType context;
   private final Map<CfLabel, CfFrame> stateMap;
-  private final BiPredicate<DexType, DexType> isJavaAssignable;
-  private final DexItemFactory factory;
   private final List<CfTryCatch> tryCatchRanges;
   private final int maxStackHeight;
 
@@ -45,19 +45,17 @@
   private final Set<CfLabel> tryCatchRangeLabels;
 
   public CfFrameVerificationHelper(
+      AppView<?> appView,
       DexType context,
       Map<CfLabel, CfFrame> stateMap,
       List<CfTryCatch> tryCatchRanges,
-      BiPredicate<DexType, DexType> isJavaAssignable,
-      DexItemFactory factory,
       int maxStackHeight) {
+    this.appView = appView;
     this.context = context;
     this.stateMap = stateMap;
     this.tryCatchRanges = tryCatchRanges;
-    this.isJavaAssignable = isJavaAssignable;
-    this.factory = factory;
+    this.factory = appView.dexItemFactory();
     this.maxStackHeight = maxStackHeight;
-    throwStack = ImmutableDeque.of(FrameType.initialized(factory.throwableType));
     // Compute all labels that marks a start or end to catch ranges.
     tryCatchRangeLabels = Sets.newIdentityHashSet();
     for (CfTryCatch tryCatchRange : tryCatchRanges) {
@@ -66,10 +64,6 @@
     }
   }
 
-  public DexItemFactory factory() {
-    return factory;
-  }
-
   public FrameType readLocal(int index, DexType expectedType) {
     checkFrameIsSet();
     FrameType frameType = currentFrame.getLocals().get(index);
@@ -197,15 +191,12 @@
           // From the spec: the handler's exception class is assignable to the class Throwable.
           tryCatchRange.guards.forEach(
               guard -> {
-                if (!isJavaAssignable.test(guard, factory.throwableType)) {
+                if (!CfAssignability.isAssignable(guard, factory.throwableType, appView)) {
                   throw CfCodeStackMapValidatingException.error(
                       "Could not assign '" + guard.toSourceString() + "' to throwable.");
                 }
                 checkStackIsAssignable(
-                    ImmutableDeque.of(FrameType.initialized(guard)),
-                    destinationFrame.getStack(),
-                    factory,
-                    isJavaAssignable);
+                    ImmutableDeque.of(FrameType.initialized(guard)), destinationFrame.getStack());
               });
         });
   }
@@ -237,8 +228,7 @@
         if (destinationFrame == null) {
           throw CfCodeStackMapValidatingException.error("No frame for target catch range target");
         }
-        checkLocalsIsAssignable(
-            currentFrame.getLocals(), destinationFrame.getLocals(), factory, isJavaAssignable);
+        checkLocalsIsAssignable(currentFrame.getLocals(), destinationFrame.getLocals());
       }
     }
   }
@@ -259,13 +249,7 @@
   }
 
   public void checkFrame(Int2ReferenceSortedMap<FrameType> locals, Deque<FrameType> stack) {
-    checkIsAssignable(
-        currentFrame.getLocals(),
-        currentFrame.getStack(),
-        locals,
-        stack,
-        factory,
-        isJavaAssignable);
+    checkIsAssignable(currentFrame.getLocals(), currentFrame.getStack(), locals, stack);
   }
 
   public void setNoFrame() {
@@ -290,7 +274,7 @@
     if (!source.isInitialized()) {
       return false;
     }
-    return isJavaAssignable.test(source.getInitializedType(), target);
+    return CfAssignability.isAssignable(source.getInitializedType(), target, appView);
   }
 
   public void checkIsAssignable(
@@ -303,31 +287,28 @@
   }
 
   public void checkIsAssignable(FrameType source, FrameType target) {
-    if (!canBeAssigned(source, target, factory, isJavaAssignable)) {
+    if (!CfAssignability.isAssignable(source, target, appView)) {
       throw CfCodeStackMapValidatingException.error(
           "The expected type " + source + " is not assignable to " + target);
     }
   }
 
   // Based on https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.4.
-  public static void checkIsAssignable(
+  private void checkIsAssignable(
       Int2ReferenceSortedMap<FrameType> sourceLocals,
       Deque<FrameType> sourceStack,
       Int2ReferenceSortedMap<FrameType> destLocals,
-      Deque<FrameType> destStack,
-      DexItemFactory factory,
-      BiPredicate<DexType, DexType> isJavaAssignable) {
-    checkLocalsIsAssignable(sourceLocals, destLocals, factory, isJavaAssignable);
-    checkStackIsAssignable(sourceStack, destStack, factory, isJavaAssignable);
+      Deque<FrameType> destStack) {
+    checkLocalsIsAssignable(sourceLocals, destLocals);
+    checkStackIsAssignable(sourceStack, destStack);
   }
 
-  private static void checkLocalsIsAssignable(
+  private void checkLocalsIsAssignable(
       Int2ReferenceSortedMap<FrameType> sourceLocals,
-      Int2ReferenceSortedMap<FrameType> destLocals,
-      DexItemFactory factory,
-      BiPredicate<DexType, DexType> isJavaAssignable) {
-    final int localsLastKey = sourceLocals.isEmpty() ? -1 : sourceLocals.lastIntKey();
-    final int otherLocalsLastKey = destLocals.isEmpty() ? -1 : destLocals.lastIntKey();
+      Int2ReferenceSortedMap<FrameType> destLocals) {
+    // TODO(b/229826687): The tail of locals could have top(s) at destination but still be valid.
+    int localsLastKey = sourceLocals.isEmpty() ? -1 : sourceLocals.lastIntKey();
+    int otherLocalsLastKey = destLocals.isEmpty() ? -1 : destLocals.lastIntKey();
     if (localsLastKey < otherLocalsLastKey) {
       throw CfCodeStackMapValidatingException.error(
           "Source locals "
@@ -336,11 +317,9 @@
               + MapUtils.toString(destLocals));
     }
     for (int i = 0; i < otherLocalsLastKey; i++) {
-      final FrameType sourceType =
-          sourceLocals.containsKey(i) ? sourceLocals.get(i) : FrameType.top();
-      final FrameType destinationType =
-          destLocals.containsKey(i) ? destLocals.get(i) : FrameType.top();
-      if (!canBeAssigned(sourceType, destinationType, factory, isJavaAssignable)) {
+      FrameType sourceType = sourceLocals.containsKey(i) ? sourceLocals.get(i) : FrameType.top();
+      FrameType destinationType = destLocals.containsKey(i) ? destLocals.get(i) : FrameType.top();
+      if (!CfAssignability.isAssignable(sourceType, destinationType, appView)) {
         throw CfCodeStackMapValidatingException.error(
             "Could not assign '"
                 + MapUtils.toString(sourceLocals)
@@ -357,11 +336,7 @@
     }
   }
 
-  private static void checkStackIsAssignable(
-      Deque<FrameType> sourceStack,
-      Deque<FrameType> destStack,
-      DexItemFactory factory,
-      BiPredicate<DexType, DexType> isJavaAssignable) {
+  private void checkStackIsAssignable(Deque<FrameType> sourceStack, Deque<FrameType> destStack) {
     if (sourceStack.size() != destStack.size()) {
       throw CfCodeStackMapValidatingException.error(
           "Source stack "
@@ -370,11 +345,11 @@
               + Arrays.toString(destStack.toArray())
               + " is not the same size");
     }
-    final Iterator<FrameType> otherIterator = destStack.iterator();
+    Iterator<FrameType> otherIterator = destStack.iterator();
     int i = 0;
     for (FrameType sourceType : sourceStack) {
-      final FrameType destinationType = otherIterator.next();
-      if (!canBeAssigned(sourceType, destinationType, factory, isJavaAssignable)) {
+      FrameType destinationType = otherIterator.next();
+      if (!CfAssignability.isAssignable(sourceType, destinationType, appView)) {
         throw CfCodeStackMapValidatingException.error(
             "Could not assign '"
                 + Arrays.toString(sourceStack.toArray())
@@ -391,46 +366,4 @@
       i++;
     }
   }
-
-  // Based on https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.2.
-  public static boolean canBeAssigned(
-      FrameType source,
-      FrameType target,
-      DexItemFactory factory,
-      BiPredicate<DexType, DexType> isJavaAssignable) {
-    if (target.isTop()) {
-      return true;
-    }
-    if (source.isTop()) {
-      return false;
-    }
-    if (source.isWide() != target.isWide()) {
-      return false;
-    }
-    if (target.isOneWord() || target.isTwoWord()) {
-      return true;
-    }
-    if (source.isUninitializedThis() && target.isUninitializedThis()) {
-      return true;
-    }
-    if (source.isUninitializedNew() && target.isUninitializedNew()) {
-      // TODO(b/168190134): Allow for picking the offset from the target if not set.
-      DexType uninitializedNewTypeSource = source.getUninitializedNewType();
-      DexType uninitializedNewTypeTarget = target.getUninitializedNewType();
-      return uninitializedNewTypeSource == null
-          || uninitializedNewTypeTarget == null
-          || uninitializedNewTypeSource == uninitializedNewTypeTarget;
-    }
-    // TODO(b/168190267): Clean-up the lattice.
-    if (!source.isInitialized()
-        && target.isInitialized()
-        && target.getInitializedType() == factory.objectType) {
-      return true;
-    }
-    if (source.isInitialized() && target.isInitialized()) {
-      // Both are instantiated types and we resort to primitive tyoe/java type hierarchy checking.
-      return isJavaAssignable.test(source.getInitializedType(), target.getInitializedType());
-    }
-    return false;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
index fa3a8a5..ee4b0f2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -19,7 +20,10 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
+import java.util.function.BiFunction;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -63,6 +67,14 @@
   }
 
   @Override
+  public <BT, CT> TraversalContinuation<BT, CT> traverseNormalTargets(
+      BiFunction<? super CfInstruction, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CfInstruction fallthroughInstruction,
+      CT initialValue) {
+    return fn.apply(target, initialValue);
+  }
+
+  @Override
   public void write(
       AppView<?> appView,
       ProgramMethod context,
@@ -105,4 +117,14 @@
     frameBuilder.checkTarget(target);
     frameBuilder.setNoFrame();
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIf.java b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
index 833ac44..d2fbccd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -23,7 +24,10 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
+import java.util.function.BiFunction;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -66,6 +70,16 @@
     return target;
   }
 
+  @Override
+  public <BT, CT> TraversalContinuation<BT, CT> traverseNormalTargets(
+      BiFunction<? super CfInstruction, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CfInstruction fallthroughInstruction,
+      CT initialValue) {
+    return fn.apply(target, initialValue)
+        .ifContinueThen(
+            continuation -> fn.apply(fallthroughInstruction, continuation.getValueOrDefault(null)));
+  }
+
   public int getOpcode() {
     switch (kind) {
       case EQ:
@@ -146,4 +160,14 @@
             : type.toPrimitiveType().toDexType(dexItemFactory));
     frameBuilder.checkTarget(target);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
index c8129a6..f6d94b7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -24,7 +25,10 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
+import java.util.function.BiFunction;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -67,6 +71,16 @@
     return target;
   }
 
+  @Override
+  public <BT, CT> TraversalContinuation<BT, CT> traverseNormalTargets(
+      BiFunction<? super CfInstruction, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CfInstruction fallthroughInstruction,
+      CT initialValue) {
+    return fn.apply(target, initialValue)
+        .ifContinueThen(
+            continuation -> fn.apply(fallthroughInstruction, continuation.getValueOrDefault(null)));
+  }
+
   public int getOpcode() {
     switch (kind) {
       case EQ:
@@ -149,4 +163,14 @@
     frameBuilder.popAndDiscardInitialized(type, type);
     frameBuilder.checkTarget(target);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
index 1b5763c..046fca2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -20,6 +21,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
 import org.objectweb.asm.MethodVisitor;
@@ -102,4 +104,14 @@
       DexItemFactory dexItemFactory) {
     frameBuilder.readLocal(var, dexItemFactory.intType);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
index c307d45..4df1ecd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -23,6 +24,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
@@ -124,4 +126,14 @@
     // ..., value
     frameBuilder.push(dexItemFactory.intType);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java
index 2684c0d..df84afd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java
@@ -5,9 +5,22 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.code.CfOrDexInstanceFieldRead;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import java.util.ListIterator;
 import org.objectweb.asm.Opcodes;
 
@@ -18,7 +31,17 @@
   }
 
   public CfInstanceFieldRead(DexField field, DexField declaringField) {
-    super(Opcodes.GETFIELD, field, declaringField);
+    super(field, declaringField);
+  }
+
+  @Override
+  public int getOpcode() {
+    return Opcodes.GETFIELD;
+  }
+
+  @Override
+  public boolean isFieldGet() {
+    return true;
   }
 
   @Override
@@ -31,4 +54,37 @@
       UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerInstanceFieldReadInstruction(this);
   }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    Slot object = state.pop();
+    builder.addInstanceGet(state.push(getField().getType()).register, object.register, getField());
+  }
+
+  @Override
+  public ConstraintWithTarget inliningConstraint(
+      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
+    return inliningConstraints.forInstanceGet(getField(), context);
+  }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // ..., objectref →
+    // ..., value
+    frameBuilder.popAndDiscardInitialized(getField().getHolderType()).push(getField().getType());
+  }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java
index c9138bb..03766dd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java
@@ -4,9 +4,24 @@
 
 package com.android.tools.r8.cf.code;
 
+import static com.android.tools.r8.utils.BiPredicateUtils.or;
+
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import java.util.ListIterator;
 import org.objectweb.asm.Opcodes;
 
@@ -17,7 +32,7 @@
   }
 
   public CfInstanceFieldWrite(DexField field, DexField declaringField) {
-    super(Opcodes.PUTFIELD, field, declaringField);
+    super(field, declaringField);
   }
 
   @Override
@@ -26,8 +41,53 @@
   }
 
   @Override
+  public int getOpcode() {
+    return Opcodes.PUTFIELD;
+  }
+
+  @Override
   void internalRegisterUse(
       UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerInstanceFieldWrite(getField());
   }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    Slot value = state.pop();
+    Slot object = state.pop();
+    builder.addInstancePut(value.register, object.register, getField());
+  }
+
+  @Override
+  public ConstraintWithTarget inliningConstraint(
+      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
+    return inliningConstraints.forInstancePut(getField(), context);
+  }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // ..., objectref, value →
+    // ...,
+    frameBuilder
+        .popAndDiscardInitialized(getField().getType())
+        .pop(
+            getField().getHolderType(),
+            or(
+                frameBuilder::isUninitializedThisAndTarget,
+                frameBuilder::isAssignableAndInitialized));
+  }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
index 0469499..da48878 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -22,6 +23,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
@@ -134,4 +136,14 @@
     // ..., result
     frameBuilder.popAndDiscardInitialized(dexItemFactory.objectType).push(dexItemFactory.intType);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 40b9737..7f449e6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -25,8 +25,12 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import java.util.ListIterator;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
 import org.objectweb.asm.MethodVisitor;
 
 public abstract class CfInstruction implements CfOrDexInstruction {
@@ -106,6 +110,26 @@
     return null;
   }
 
+  public final void forEachNormalTarget(
+      Consumer<? super CfInstruction> consumer, CfInstruction fallthroughInstruction) {
+    traverseNormalTargets(
+        (target, ignore) -> {
+          consumer.accept(target);
+          return TraversalContinuation.doContinue();
+        },
+        fallthroughInstruction,
+        null);
+  }
+
+  public <BT, CT> TraversalContinuation<BT, CT> traverseNormalTargets(
+      BiFunction<? super CfInstruction, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CfInstruction fallthroughInstruction,
+      CT initialValue) {
+    // The method is overridden in each jump instruction.
+    assert !isJump();
+    return fn.apply(fallthroughInstruction, initialValue);
+  }
+
   @Override
   public CfInstruction asCfInstruction() {
     return this;
@@ -317,4 +341,7 @@
       DexMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory);
+
+  public abstract CfFrameState evaluate(
+      CfFrameState frame, ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory);
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 8397bc6..c972e4e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
 import java.util.Arrays;
@@ -337,6 +338,30 @@
     }
   }
 
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // ..., objectref, [arg1, [arg2 ...]] →
+    // ... [ returnType ]
+    // OR, for static method calls:
+    // ..., [arg1, [arg2 ...]] →
+    // ...
+    frame = frame.popInitialized(appView, method.getParameters().getBacking());
+    if (opcode != Opcodes.INVOKESTATIC) {
+      frame =
+          opcode == Opcodes.INVOKESPECIAL && context.getDefinition().isInstanceInitializer()
+              ? frame.popAndInitialize(appView, method, context)
+              : frame.popInitialized(appView, method.getHolderType());
+    }
+    if (method.getReturnType().isVoidType()) {
+      return frame;
+    }
+    return frame.push(method.getReturnType());
+  }
+
   private Type computeInvokeTypeForInvokeSpecial(
       AppView<?> appView, DexMethod method, ProgramMethod context, DexType originalHolder) {
     if (appView.dexItemFactory().isConstructor(method)) {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index a02060b..421902e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -28,6 +29,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import java.util.ArrayList;
 import java.util.List;
@@ -178,4 +180,14 @@
       frameBuilder.push(callSite.methodProto.returnType);
     }
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
index bc866cb..41e1063 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCodeStackMapValidatingException;
@@ -21,6 +22,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import org.objectweb.asm.MethodVisitor;
 
@@ -93,6 +95,16 @@
     throw CfCodeStackMapValidatingException.error("Unexpected JSR/RET instruction");
   }
 
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
+
   public int getLocal() {
     return local;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
index 3df1eba..4bd20a2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -19,6 +20,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
@@ -102,4 +104,14 @@
       DexItemFactory dexItemFactory) {
     frameBuilder.seenLabel(this);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
index 99a32da..889d4aa 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -22,6 +23,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -139,4 +141,14 @@
                 ? dexItemFactory.objectType
                 : type.toPrimitiveType().toDexType(dexItemFactory)));
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
index 2407b10..a5ee7eb 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -23,6 +24,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -197,4 +199,14 @@
     }
     frameBuilder.popAndDiscard(value1Type, value2Type).push(value1Type);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
index 11df4ee..85ddc69 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -22,6 +23,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -103,4 +105,14 @@
     // ...
     frameBuilder.pop(FrameType.initialized(dexItemFactory.objectType));
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
index 509e0cf..796a93f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -22,6 +23,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -140,4 +142,14 @@
     }
     frameBuilder.push(type);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
index da12fc5..9c6a7f8 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -23,6 +24,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -129,4 +131,14 @@
     FrameType frameType = FrameType.fromNumericType(type, dexItemFactory);
     frameBuilder.popAndDiscard(frameType).push(frameType);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
index b4c070a..0fde2a7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
@@ -123,4 +124,15 @@
     // ..., objectref
     frameBuilder.push(FrameType.uninitializedNew(new CfLabel(), type));
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // ... →
+    // ..., objectref
+    return frame.push(FrameType.uninitializedNew(new CfLabel(), type));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
index 5149789..ba81a58 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -24,6 +25,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import java.util.ListIterator;
@@ -174,4 +176,14 @@
     assert type.isArrayType();
     frameBuilder.popAndDiscardInitialized(dexItemFactory.intType).push(type);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java b/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
index 0c75328..8683ddd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -24,6 +25,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
@@ -126,4 +128,14 @@
     // ..., objectref
     frameBuilder.push(FrameType.initialized(type));
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNop.java b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
index f77241e..e8b3a2e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -19,6 +20,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -83,4 +85,14 @@
       DexItemFactory dexItemFactory) {
     // This is an actual Nop.
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
index b8fe9c1..0341487 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -23,6 +24,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -201,4 +203,14 @@
         .popAndDiscard(FrameType.fromNumericType(from, dexItemFactory))
         .push(FrameType.fromNumericType(to, dexItemFactory));
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
index 6f82c07..155b17d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -20,6 +21,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import org.objectweb.asm.MethodVisitor;
 
@@ -116,4 +118,14 @@
       DexItemFactory dexItemFactory) {
     // This is a no-op.
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java b/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java
index f8fad92..4a9fd91 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -23,6 +24,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -119,4 +121,14 @@
     }
     frameBuilder.push(dexItemFactory.objectArrayType);
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
index d06db87..27b73f6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -22,7 +23,10 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
+import java.util.function.BiFunction;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -72,6 +76,14 @@
   }
 
   @Override
+  public <BT, CT> TraversalContinuation<BT, CT> traverseNormalTargets(
+      BiFunction<? super CfInstruction, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CfInstruction fallthroughInstruction,
+      CT initialValue) {
+    return TraversalContinuation.doContinue(initialValue);
+  }
+
+  @Override
   public boolean isJump() {
     return true;
   }
@@ -121,4 +133,14 @@
     frameBuilder.popAndDiscardInitialized(context.getReturnType());
     frameBuilder.setNoFrame();
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
index d492b04..a61642f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
@@ -19,13 +19,24 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
+import java.util.function.BiFunction;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
 public class CfReturnVoid extends CfInstruction {
 
   @Override
+  public <BT, CT> TraversalContinuation<BT, CT> traverseNormalTargets(
+      BiFunction<? super CfInstruction, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CfInstruction fallthroughInstruction,
+      CT initialValue) {
+    return TraversalContinuation.doContinue(initialValue);
+  }
+
+  @Override
   public boolean isJump() {
     return true;
   }
@@ -93,4 +104,13 @@
       DexItemFactory dexItemFactory) {
     frameBuilder.setNoFrame();
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    return CfFrameState.bottom();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
index 366033d..5f5e5a9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -24,6 +25,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -358,7 +360,6 @@
       DexMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-
     switch (opcode) {
       case Pop:
         // ..., value →
@@ -513,4 +514,61 @@
         throw new Unreachable("Invalid opcode for CfStackInstruction");
     }
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    switch (opcode) {
+      case Pop:
+        {
+          // TODO(b/214496607): Implement this.
+          throw new Unimplemented();
+        }
+      case Pop2:
+        {
+          // TODO(b/214496607): Implement this.
+          throw new Unimplemented();
+        }
+      case Dup:
+        // ..., value →
+        // ..., value, value
+        return frame.pop(
+            appView, FrameType.oneWord(), frameType -> frame.push(frameType).push(frameType));
+      case DupX1:
+        {
+          // TODO(b/214496607): Implement this.
+          throw new Unimplemented();
+        }
+      case DupX2:
+        {
+          // TODO(b/214496607): Implement this.
+          throw new Unimplemented();
+        }
+      case Dup2:
+        {
+          // TODO(b/214496607): Implement this.
+          throw new Unimplemented();
+        }
+      case Dup2X1:
+        {
+          // TODO(b/214496607): Implement this.
+          throw new Unimplemented();
+        }
+      case Dup2X2:
+        {
+          // TODO(b/214496607): Implement this.
+          throw new Unimplemented();
+        }
+      case Swap:
+        {
+          // TODO(b/214496607): Implement this.
+          throw new Unimplemented();
+        }
+      default:
+        throw new Unreachable("Invalid opcode for CfStackInstruction");
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
index 30715e1..3950a9c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
@@ -5,20 +5,31 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.code.CfOrDexStaticFieldRead;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import java.util.ListIterator;
 import org.objectweb.asm.Opcodes;
 
 public class CfStaticFieldRead extends CfFieldInstruction implements CfOrDexStaticFieldRead {
 
   public CfStaticFieldRead(DexField field) {
-    super(Opcodes.GETSTATIC, field);
+    super(field);
   }
 
   public CfStaticFieldRead(DexField field, DexField declaringField) {
-    super(Opcodes.GETSTATIC, field, declaringField);
+    super(field, declaringField);
   }
 
   @Override
@@ -27,8 +38,56 @@
   }
 
   @Override
+  public int getOpcode() {
+    return Opcodes.GETSTATIC;
+  }
+
+  @Override
+  public boolean isFieldGet() {
+    return true;
+  }
+
+  @Override
+  public boolean isStaticFieldGet() {
+    return true;
+  }
+
+  @Override
   void internalRegisterUse(
       UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerStaticFieldReadInstruction(this);
   }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    builder.addStaticGet(state.push(getField().getType()).register, getField());
+  }
+
+  @Override
+  public ConstraintWithTarget inliningConstraint(
+      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
+    return inliningConstraints.forStaticGet(getField(), context);
+  }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // ..., →
+    // ..., value
+    frameBuilder.push(getField().getType());
+  }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // ..., →
+    // ..., value
+    return frame.push(getField().getType());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java
index 24b5e6d..26114d8 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java
@@ -4,20 +4,33 @@
 
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import java.util.ListIterator;
 import org.objectweb.asm.Opcodes;
 
 public class CfStaticFieldWrite extends CfFieldInstruction {
 
   public CfStaticFieldWrite(DexField field) {
-    super(Opcodes.PUTSTATIC, field);
+    super(field);
   }
 
   public CfStaticFieldWrite(DexField field, DexField declaringField) {
-    super(Opcodes.PUTSTATIC, field, declaringField);
+    super(field, declaringField);
   }
 
   @Override
@@ -26,8 +39,51 @@
   }
 
   @Override
+  public int getOpcode() {
+    return Opcodes.PUTSTATIC;
+  }
+
+  @Override
+  public boolean isStaticFieldPut() {
+    return true;
+  }
+
+  @Override
   void internalRegisterUse(
       UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerStaticFieldWrite(getField());
   }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    Slot value = state.pop();
+    builder.addStaticPut(value.register, getField());
+  }
+
+  @Override
+  public ConstraintWithTarget inliningConstraint(
+      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
+    return inliningConstraints.forStaticPut(getField(), context);
+  }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // ..., value →
+    // ...
+    frameBuilder.popAndDiscardInitialized(getField().getType());
+  }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStore.java b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
index 616ed84..e095dc8 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
@@ -7,6 +7,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -25,6 +26,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -172,4 +174,14 @@
         throw new Unreachable("Unexpected type " + type);
     }
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
index a86bcd1..ea40b51 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -21,9 +22,13 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.TraversalUtils;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import java.util.List;
+import java.util.function.BiFunction;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -47,6 +52,16 @@
   }
 
   @Override
+  public <BT, CT> TraversalContinuation<BT, CT> traverseNormalTargets(
+      BiFunction<? super CfInstruction, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CfInstruction fallthroughInstruction,
+      CT initialValue) {
+    return TraversalUtils.traverseIterable(targets, fn, initialValue)
+        .ifContinueThen(
+            continuation -> fn.apply(defaultTarget, continuation.getValueOrDefault(null)));
+  }
+
+  @Override
   public int getCompareToId() {
     return kind == Kind.LOOKUP ? Opcodes.LOOKUPSWITCH : Opcodes.TABLESWITCH;
   }
@@ -171,4 +186,14 @@
     }
     frameBuilder.setNoFrame();
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
index 6e5eb78..d0aa3f8 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -20,13 +21,24 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
+import java.util.function.BiFunction;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
 public class CfThrow extends CfInstruction {
 
   @Override
+  public <BT, CT> TraversalContinuation<BT, CT> traverseNormalTargets(
+      BiFunction<? super CfInstruction, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CfInstruction fallthroughInstruction,
+      CT initialValue) {
+    return TraversalContinuation.doContinue(initialValue);
+  }
+
+  @Override
   public boolean isJump() {
     return true;
   }
@@ -104,4 +116,14 @@
     // The exception edges are verified in CfCode since this is a throwing instruction.
     frameBuilder.setNoFrame();
   }
+
+  @Override
+  public CfFrameState evaluate(
+      CfFrameState frame,
+      ProgramMethod context,
+      AppView<?> appView,
+      DexItemFactory dexItemFactory) {
+    // TODO(b/214496607): Implement this.
+    throw new Unimplemented();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java b/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
index 2f821be..22aac9a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Consumer;
 
 public class CfTryCatch {
   public final CfLabel start;
@@ -28,6 +29,22 @@
     assert verifyAllNonNull(guards);
   }
 
+  public void forEachTarget(Consumer<CfLabel> consumer) {
+    targets.forEach(consumer);
+  }
+
+  public CfLabel getStart() {
+    return start;
+  }
+
+  public CfLabel getEnd() {
+    return end;
+  }
+
+  public List<CfLabel> getTargets() {
+    return targets;
+  }
+
   private static boolean verifyAllNonNull(List<DexType> types) {
     for (DexType type : types) {
       assert type != null;
diff --git a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
deleted file mode 100644
index 4ee416c..0000000
--- a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
+++ /dev/null
@@ -1,377 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.dexsplitter;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.D8Command;
-import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.DexSplitterHelper;
-import com.android.tools.r8.Diagnostic;
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.Keep;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.utils.AbortException;
-import com.android.tools.r8.utils.ExceptionDiagnostic;
-import com.android.tools.r8.utils.ExceptionUtils;
-import com.android.tools.r8.utils.FeatureClassMapping;
-import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
-import com.android.tools.r8.utils.OptionsParsing;
-import com.android.tools.r8.utils.OptionsParsing.ParseContext;
-import com.android.tools.r8.utils.StringDiagnostic;
-import com.android.tools.r8.utils.ZipUtils;
-import com.google.common.collect.ImmutableList;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-@Keep
-public final class DexSplitter {
-
-  private static final String DEFAULT_OUTPUT_DIR = "output";
-  private static final String DEFAULT_BASE_NAME = "base";
-
-  private static final boolean PRINT_ARGS = false;
-
-  public static class FeatureJar {
-    private String jar;
-    private String outputName;
-
-    public FeatureJar(String jar, String outputName) {
-      this.jar = jar;
-      this.outputName = outputName;
-    }
-
-    public FeatureJar(String jar) {
-      this(jar, featureNameFromJar(jar));
-    }
-
-    public String getJar() {
-      return jar;
-    }
-
-    public String getOutputName() {
-      return outputName;
-    }
-
-    private static String featureNameFromJar(String jar) {
-      Path jarPath = Paths.get(jar);
-      String featureName = jarPath.getFileName().toString();
-      if (featureName.endsWith(".jar") || featureName.endsWith(".zip")) {
-        featureName = featureName.substring(0, featureName.length() - 4);
-      }
-      return featureName;
-    }
-  }
-
-  private static class ZipFileOrigin extends PathOrigin {
-
-    public ZipFileOrigin(Path path) {
-      super(path);
-    }
-
-    @Override
-    public String part() {
-      return "splitting of file '" + super.part() + "'";
-    }
-  }
-
-  @Keep
-  public static final class Options {
-    private final DiagnosticsHandler diagnosticsHandler;
-    private List<String> inputArchives = new ArrayList<>();
-    private List<FeatureJar> featureJars = new ArrayList<>();
-    private List<String> baseJars = new ArrayList<>();
-    private String baseOutputName = DEFAULT_BASE_NAME;
-    private String output = DEFAULT_OUTPUT_DIR;
-    private String featureSplitMapping;
-    private String proguardMap;
-    private String mainDexList;
-    private boolean splitNonClassResources = false;
-
-    public Options() {
-      this(new DiagnosticsHandler() {});
-    }
-
-    public Options(DiagnosticsHandler diagnosticsHandler) {
-      this.diagnosticsHandler = diagnosticsHandler;
-    }
-
-    public DiagnosticsHandler getDiagnosticsHandler() {
-      return diagnosticsHandler;
-    }
-
-    public String getMainDexList() {
-      return mainDexList;
-    }
-
-    public void setMainDexList(String mainDexList) {
-      this.mainDexList = mainDexList;
-    }
-
-    public String getOutput() {
-      return output;
-    }
-
-    public void setOutput(String output) {
-      this.output = output;
-    }
-
-    public String getFeatureSplitMapping() {
-      return featureSplitMapping;
-    }
-
-    public void setFeatureSplitMapping(String featureSplitMapping) {
-      this.featureSplitMapping = featureSplitMapping;
-    }
-
-    public String getProguardMap() {
-      return proguardMap;
-    }
-
-    public void setProguardMap(String proguardMap) {
-      this.proguardMap = proguardMap;
-    }
-
-    public String getBaseOutputName() {
-      return baseOutputName;
-    }
-
-    public void setBaseOutputName(String baseOutputName) {
-      this.baseOutputName = baseOutputName;
-    }
-
-    public void addInputArchive(String inputArchive) {
-      inputArchives.add(inputArchive);
-    }
-
-    public void addBaseJar(String baseJar) {
-      baseJars.add(baseJar);
-    }
-
-    private void addFeatureJar(FeatureJar featureJar) {
-      featureJars.add(featureJar);
-    }
-
-    public void addFeatureJar(String jar) {
-      featureJars.add(new FeatureJar(jar));
-    }
-
-    public void addFeatureJar(String jar, String outputName) {
-      featureJars.add(new FeatureJar(jar, outputName));
-    }
-
-    public void setSplitNonClassResources(boolean value) {
-      splitNonClassResources = value;
-    }
-
-    public ImmutableList<String> getInputArchives() {
-      return ImmutableList.copyOf(inputArchives);
-    }
-
-    ImmutableList<FeatureJar> getFeatureJars() {
-      return ImmutableList.copyOf(featureJars);
-    }
-
-    ImmutableList<String> getBaseJars() {
-      return ImmutableList.copyOf(baseJars);
-    }
-
-    // Shorthand error messages.
-    public Diagnostic error(String msg) {
-      StringDiagnostic error = new StringDiagnostic(msg);
-      diagnosticsHandler.error(error);
-      return error;
-    }
-  }
-
-  /**
-   * Parse a feature jar argument and return the corresponding FeatureJar representation.
-   * Default to use the name of the jar file if the argument contains no ':', if the argument
-   * contains ':', then use the value after the ':' as the name.
-   * @param argument
-   */
-  private static FeatureJar parseFeatureJarArgument(String argument) {
-    if (argument.contains(":")) {
-      String[] parts = argument.split(":");
-      if (parts.length > 2) {
-        throw new RuntimeException("--feature-jar argument contains more than one :");
-      }
-      return new FeatureJar(parts[0], parts[1]);
-    }
-    return new FeatureJar(argument);
-  }
-
-  private static Options parseArguments(String[] args) {
-    Options options = new Options();
-    ParseContext context = new ParseContext(args);
-    while (context.head() != null) {
-      List<String> inputs = OptionsParsing.tryParseMulti(context, "--input");
-      if (inputs != null) {
-        inputs.forEach(options::addInputArchive);
-        continue;
-      }
-      List<String> featureJars = OptionsParsing.tryParseMulti(context, "--feature-jar");
-      if (featureJars != null) {
-        featureJars.forEach((feature) -> options.addFeatureJar(parseFeatureJarArgument(feature)));
-        continue;
-      }
-      List<String> baseJars = OptionsParsing.tryParseMulti(context, "--base-jar");
-      if (baseJars != null) {
-        baseJars.forEach(options::addBaseJar);
-        continue;
-      }
-      String output = OptionsParsing.tryParseSingle(context, "--output", "-o");
-      if (output != null) {
-        options.setOutput(output);
-        continue;
-      }
-
-      String mainDexList= OptionsParsing.tryParseSingle(context, "--main-dex-list", null);
-      if (mainDexList!= null) {
-        options.setMainDexList(mainDexList);
-        continue;
-      }
-
-      String proguardMap = OptionsParsing.tryParseSingle(context, "--proguard-map", null);
-      if (proguardMap != null) {
-        options.setProguardMap(proguardMap);
-        continue;
-      }
-      String baseOutputName = OptionsParsing.tryParseSingle(context, "--base-output-name", null);
-      if (baseOutputName != null) {
-        options.setBaseOutputName(baseOutputName);
-        continue;
-      }
-      String featureSplit = OptionsParsing.tryParseSingle(context, "--feature-splits", null);
-      if (featureSplit != null) {
-        options.setFeatureSplitMapping(featureSplit);
-        continue;
-      }
-      Boolean b = OptionsParsing.tryParseBoolean(context, "--split-non-class-resources");
-      if (b != null) {
-        options.setSplitNonClassResources(b);
-        continue;
-      }
-      throw new RuntimeException(String.format("Unknown options: '%s'.", context.head()));
-    }
-    return options;
-  }
-
-  private static FeatureClassMapping createFeatureClassMapping(Options options)
-      throws FeatureMappingException {
-    if (options.getFeatureSplitMapping() != null) {
-      return FeatureClassMapping.fromSpecification(
-          Paths.get(options.getFeatureSplitMapping()), options.getDiagnosticsHandler());
-    }
-    assert !options.getFeatureJars().isEmpty();
-    return FeatureClassMapping.Internal.fromJarFiles(options.getFeatureJars(),
-        options.getBaseJars(), options.getBaseOutputName(), options.getDiagnosticsHandler());
-  }
-
-  private static void run(String[] args)
-      throws CompilationFailedException, FeatureMappingException {
-    Options options = parseArguments(args);
-    run(options);
-  }
-
-  public static void run(Options options)
-      throws FeatureMappingException, CompilationFailedException {
-    Diagnostic error = null;
-    if (options.getInputArchives().isEmpty()) {
-      error = options.error("Need at least one --input");
-    }
-    if (options.getFeatureSplitMapping() == null && options.getFeatureJars().isEmpty()) {
-      error = options.error("You must supply a feature split mapping or feature jars");
-    }
-    if (options.getFeatureSplitMapping() != null && !options.getFeatureJars().isEmpty()) {
-      error = options.error("You can't supply both a feature split mapping and feature jars");
-    }
-    if (error != null) {
-      throw new AbortException(error);
-    }
-
-    D8Command.Builder builder = D8Command.builder(options.diagnosticsHandler);
-
-
-    for (String s : options.inputArchives) {
-      builder.addProgramFiles(Paths.get(s));
-    }
-    // We set the actual consumer on the ApplicationWriter when we have calculated the distribution
-    // since we don't yet know the distribution.
-    builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    if (options.getMainDexList() != null) {
-      builder.addMainDexListFiles(Paths.get(options.getMainDexList()));
-    }
-
-    FeatureClassMapping featureClassMapping = createFeatureClassMapping(options);
-
-    DexSplitterHelper.run(
-        builder.build(), featureClassMapping, options.getOutput(), options.getProguardMap());
-
-    if (options.splitNonClassResources) {
-      splitNonClassResources(options, featureClassMapping);
-    }
-  }
-
-  private static void splitNonClassResources(Options options,
-      FeatureClassMapping featureClassMapping) {
-    for (String s : options.inputArchives) {
-      try (ZipFile zipFile = new ZipFile(s, StandardCharsets.UTF_8)) {
-        Enumeration<? extends ZipEntry> entries = zipFile.entries();
-        while (entries.hasMoreElements()) {
-          ZipEntry entry = entries.nextElement();
-          String name = entry.getName();
-          if (!ZipUtils.isDexFile(name) && !ZipUtils.isClassFile(name)) {
-            String feature = featureClassMapping.featureForNonClass(name);
-            Path outputDir = Paths.get(options.getOutput()).resolve(feature);
-            try (InputStream stream = zipFile.getInputStream(entry)) {
-              Path outputFile = outputDir.resolve(name);
-              Path parent = outputFile.getParent();
-              if (parent != null) {
-                Files.createDirectories(parent);
-              }
-              Files.copy(stream, outputFile);
-            }
-          }
-        }
-      } catch (IOException e) {
-        ExceptionDiagnostic error = new ExceptionDiagnostic(e, new ZipFileOrigin(Paths.get(s)));
-        options.getDiagnosticsHandler().error(error);
-        throw new AbortException(error);
-      }
-    }
-  }
-
-  public static void main(String[] args) {
-    if (PRINT_ARGS) {
-      printArgs(args);
-    }
-    ExceptionUtils.withMainProgramHandler(
-        () -> {
-          try {
-            run(args);
-          } catch (FeatureMappingException e) {
-            // TODO(ricow): Report feature mapping errors via the reporter.
-            throw new RuntimeException("Splitting failed: " + e.getMessage());
-          }
-        });
-  }
-
-  private static void printArgs(String[] args) {
-    System.err.printf("r8.DexSplitter");
-    for (String s : args) {
-      System.err.printf(" %s", s);
-    }
-    System.err.println("");
-  }
-}
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 81979c8..e5e4459 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -592,7 +592,7 @@
   // TODO(b/155968472): This should take a parameter `boolean isInterface` and use resolveMethod().
   public DexEncodedMethod lookupStaticTarget(DexMethod method, DexProgramClass context) {
     assert checkIfObsolete();
-    return unsafeResolveMethodDueToDexFormat(method).lookupInvokeStaticTarget(context, this);
+    return unsafeResolveMethodDueToDexFormatLegacy(method).lookupInvokeStaticTarget(context, this);
   }
 
   // TODO(b/155968472): This should take a parameter `boolean isInterface` and use resolveMethod().
@@ -613,7 +613,7 @@
   // TODO(b/155968472): This should take a parameter `boolean isInterface` and use resolveMethod().
   public DexClassAndMethod lookupSuperTarget(DexMethod method, DexProgramClass context) {
     assert checkIfObsolete();
-    return unsafeResolveMethodDueToDexFormat(method).lookupInvokeSuperTarget(context, this);
+    return unsafeResolveMethodDueToDexFormatLegacy(method).lookupInvokeSuperTarget(context, this);
   }
 
   // TODO(b/155968472): This should take a parameter `boolean isInterface` and use resolveMethod().
@@ -632,7 +632,7 @@
   // TODO(b/155968472): This should take a parameter `boolean isInterface` and use resolveMethod().
   public DexEncodedMethod lookupDirectTarget(DexMethod method, DexProgramClass context) {
     assert checkIfObsolete();
-    return unsafeResolveMethodDueToDexFormat(method).lookupInvokeDirectTarget(context, this);
+    return unsafeResolveMethodDueToDexFormatLegacy(method).lookupInvokeDirectTarget(context, this);
   }
 
   // TODO(b/155968472): This should take a parameter `boolean isInterface` and use resolveMethod().
@@ -646,32 +646,129 @@
    * <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 MethodResolutionResult unsafeResolveMethodDueToDexFormat(DexMethod method) {
+  public MethodResolutionResult unsafeResolveMethodDueToDexFormatLegacy(DexMethod method) {
     assert checkIfObsolete();
     return MethodResolution.createLegacy(this::definitionFor, dexItemFactory())
         .unsafeResolveMethodDueToDexFormat(method);
   }
 
+  public MethodResolutionResult resolveMethodLegacy(DexMethod invokedMethod, boolean isInterface) {
+    assert checkIfObsolete();
+    return resolveMethodOnLegacy(invokedMethod.getHolderType(), invokedMethod, isInterface);
+  }
+
+  public MethodResolutionResult resolveMethodOnLegacy(DexClass clazz, DexMethod method) {
+    assert checkIfObsolete();
+    return clazz.isInterface()
+        ? resolveMethodOnInterfaceLegacy(clazz, method)
+        : resolveMethodOnClassLegacy(clazz, method);
+  }
+
+  public MethodResolutionResult resolveMethodOnLegacy(
+      DexClass clazz, DexMethodSignature methodSignature) {
+    assert checkIfObsolete();
+    return clazz.isInterface()
+        ? resolveMethodOnInterfaceLegacy(clazz, methodSignature)
+        : resolveMethodOnClassLegacy(clazz, methodSignature);
+  }
+
+  public MethodResolutionResult resolveMethodOnLegacy(
+      DexType holder, DexMethod method, boolean isInterface) {
+    assert checkIfObsolete();
+    return isInterface
+        ? resolveMethodOnInterfaceLegacy(holder, method)
+        : resolveMethodOnClassLegacy(holder, method);
+  }
+
+  public MethodResolutionResult resolveMethodOnClassHolderLegacy(DexMethod method) {
+    assert checkIfObsolete();
+    return resolveMethodOnClassLegacy(method.getHolderType(), method);
+  }
+
+  public MethodResolutionResult resolveMethodOnClassLegacy(DexType holder, DexMethod method) {
+    assert checkIfObsolete();
+    return resolveMethodOnClassLegacy(holder, method.getProto(), method.getName());
+  }
+
+  public MethodResolutionResult resolveMethodOnClassLegacy(
+      DexType holder, DexMethodSignature signature) {
+    assert checkIfObsolete();
+    return resolveMethodOnClassLegacy(holder, signature.getProto(), signature.getName());
+  }
+
+  public MethodResolutionResult resolveMethodOnClassLegacy(
+      DexType holder, DexProto proto, DexString name) {
+    assert checkIfObsolete();
+    return MethodResolution.createLegacy(this::definitionFor, dexItemFactory())
+        .resolveMethodOnClass(holder, proto, name);
+  }
+
+  public MethodResolutionResult resolveMethodOnClassLegacy(DexClass clazz, DexMethod method) {
+    assert checkIfObsolete();
+    return resolveMethodOnClassLegacy(clazz, method.getProto(), method.getName());
+  }
+
+  public MethodResolutionResult resolveMethodOnClassLegacy(
+      DexClass clazz, DexMethodSignature signature) {
+    assert checkIfObsolete();
+    return resolveMethodOnClassLegacy(clazz, signature.getProto(), signature.getName());
+  }
+
+  public MethodResolutionResult resolveMethodOnClassLegacy(
+      DexClass clazz, DexProto proto, DexString name) {
+    assert checkIfObsolete();
+    return MethodResolution.createLegacy(this::definitionFor, dexItemFactory())
+        .resolveMethodOnClass(clazz, proto, name);
+  }
+
+  public MethodResolutionResult resolveMethodOnInterfaceHolderLegacy(DexMethod method) {
+    assert checkIfObsolete();
+    return resolveMethodOnInterfaceLegacy(method.getHolderType(), method);
+  }
+
+  public MethodResolutionResult resolveMethodOnInterfaceLegacy(DexType holder, DexMethod method) {
+    assert checkIfObsolete();
+    return MethodResolution.createLegacy(this::definitionFor, dexItemFactory())
+        .resolveMethodOnInterface(holder, method.getProto(), method.getName());
+  }
+
+  public MethodResolutionResult resolveMethodOnInterfaceLegacy(DexClass clazz, DexMethod method) {
+    assert checkIfObsolete();
+    return resolveMethodOnInterfaceLegacy(clazz, method.getProto(), method.getName());
+  }
+
+  public MethodResolutionResult resolveMethodOnInterfaceLegacy(
+      DexClass clazz, DexMethodSignature methodSignature) {
+    assert checkIfObsolete();
+    return resolveMethodOnInterfaceLegacy(
+        clazz, methodSignature.getProto(), methodSignature.getName());
+  }
+
+  public MethodResolutionResult resolveMethodOnInterfaceLegacy(
+      DexClass clazz, DexProto proto, DexString name) {
+    assert checkIfObsolete();
+    return MethodResolution.createLegacy(this::definitionFor, dexItemFactory())
+        .resolveMethodOnInterface(clazz, proto, name);
+  }
+
+  /**
+   * This method will query the definition of the holder to decide on which resolution to use.
+   *
+   * <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 MethodResolutionResult unsafeResolveMethodDueToDexFormat(DexMethod method) {
+    assert checkIfObsolete();
+    return MethodResolution.create(
+            this::contextIndependentDefinitionForWithResolutionResult, dexItemFactory())
+        .unsafeResolveMethodDueToDexFormat(method);
+  }
+
   public MethodResolutionResult resolveMethod(DexMethod invokedMethod, boolean isInterface) {
     assert checkIfObsolete();
     return resolveMethodOn(invokedMethod.getHolderType(), invokedMethod, isInterface);
   }
 
-  public MethodResolutionResult resolveMethodOn(DexClass clazz, DexMethod method) {
-    assert checkIfObsolete();
-    return clazz.isInterface()
-        ? resolveMethodOnInterface(clazz, method)
-        : resolveMethodOnClass(clazz, method);
-  }
-
-  public MethodResolutionResult resolveMethodOn(
-      DexClass clazz, DexMethodSignature methodSignature) {
-    assert checkIfObsolete();
-    return clazz.isInterface()
-        ? resolveMethodOnInterface(clazz, methodSignature)
-        : resolveMethodOnClass(clazz, methodSignature);
-  }
-
   public MethodResolutionResult resolveMethodOn(
       DexType holder, DexMethod method, boolean isInterface) {
     assert checkIfObsolete();
@@ -698,7 +795,8 @@
   public MethodResolutionResult resolveMethodOnClass(
       DexType holder, DexProto proto, DexString name) {
     assert checkIfObsolete();
-    return MethodResolution.createLegacy(this::definitionFor, dexItemFactory())
+    return MethodResolution.create(
+            this::contextIndependentDefinitionForWithResolutionResult, dexItemFactory())
         .resolveMethodOnClass(holder, proto, name);
   }
 
@@ -715,7 +813,8 @@
   public MethodResolutionResult resolveMethodOnClass(
       DexClass clazz, DexProto proto, DexString name) {
     assert checkIfObsolete();
-    return MethodResolution.createLegacy(this::definitionFor, dexItemFactory())
+    return MethodResolution.create(
+            this::contextIndependentDefinitionForWithResolutionResult, dexItemFactory())
         .resolveMethodOnClass(clazz, proto, name);
   }
 
@@ -726,7 +825,8 @@
 
   public MethodResolutionResult resolveMethodOnInterface(DexType holder, DexMethod method) {
     assert checkIfObsolete();
-    return MethodResolution.createLegacy(this::definitionFor, dexItemFactory())
+    return MethodResolution.create(
+            this::contextIndependentDefinitionForWithResolutionResult, dexItemFactory())
         .resolveMethodOnInterface(holder, method.getProto(), method.getName());
   }
 
@@ -744,7 +844,8 @@
   public MethodResolutionResult resolveMethodOnInterface(
       DexClass clazz, DexProto proto, DexString name) {
     assert checkIfObsolete();
-    return MethodResolution.createLegacy(this::definitionFor, dexItemFactory())
+    return MethodResolution.create(
+            this::contextIndependentDefinitionForWithResolutionResult, dexItemFactory())
         .resolveMethodOnInterface(clazz, proto, name);
   }
 
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 4974a5d..8797a80 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -27,7 +27,6 @@
 import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadata;
 import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Position.SyntheticPosition;
@@ -62,7 +61,6 @@
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
-import java.util.function.BiPredicate;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
 
@@ -957,12 +955,7 @@
     }
     CfFrameVerificationHelper builder =
         new CfFrameVerificationHelper(
-            previousMethodSignature.getHolderType(),
-            stateMap,
-            tryCatchRanges,
-            isAssignablePredicate(appView),
-            appView.dexItemFactory(),
-            maxStack);
+            appView, previousMethodSignature.getHolderType(), stateMap, tryCatchRanges, maxStack);
     for (CfTryCatch tryCatchRange : tryCatchRanges) {
       try {
         builder.checkTryCatchRange(tryCatchRange);
@@ -1080,47 +1073,4 @@
     }
     return initialLocals;
   }
-
-  private BiPredicate<DexType, DexType> isAssignablePredicate(AppView<?> appView) {
-    return (source, target) -> isAssignable(source, target, appView);
-  }
-
-  // Rules found at https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.2
-  private boolean isAssignable(DexType source, DexType target, AppView<?> appView) {
-    DexItemFactory factory = appView.dexItemFactory();
-    source = byteCharShortOrBooleanToInt(source, factory);
-    target = byteCharShortOrBooleanToInt(target, factory);
-    if (source == target) {
-      return true;
-    }
-    if (source.isPrimitiveType() || target.isPrimitiveType()) {
-      return false;
-    }
-    // Both are now references - everything is assignable to object.
-    if (target == factory.objectType) {
-      return true;
-    }
-    // isAssignable(null, class(_, _)).
-    // isAssignable(null, arrayOf(_)).
-    if (source == DexItemFactory.nullValueType) {
-      return true;
-    }
-    if (target.isArrayType() != target.isArrayType()) {
-      return false;
-    }
-    if (target.isArrayType()) {
-      return isAssignable(
-          target.toArrayElementType(factory), target.toArrayElementType(factory), appView);
-    }
-    // TODO(b/166570659): Do a sub-type check that allows for missing classes in hierarchy.
-    return MemberType.fromDexType(source) == MemberType.fromDexType(target);
-  }
-
-  private DexType byteCharShortOrBooleanToInt(DexType type, DexItemFactory factory) {
-    // byte, char, short and boolean has verification type int.
-    if (type.isByteType() || type.isCharType() || type.isShortType() || type.isBooleanType()) {
-      return factory.intType;
-    }
-    return type;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolution.java b/src/main/java/com/android/tools/r8/graph/MethodResolution.java
index 0927de2..a680b25 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolution.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolution.java
@@ -52,6 +52,11 @@
         false);
   }
 
+  public static MethodResolution create(
+      Function<DexType, ClassResolutionResult> definitionFor, DexItemFactory factory) {
+    return new MethodResolution(definitionFor, factory, true);
+  }
+
   private ClassResolutionResult definitionFor(DexType type) {
     return definitionFor.apply(type);
   }
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 6c8703b..6abb160 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -60,7 +60,7 @@
       SingleResolutionResult<?> resolutionResult =
           appView
               .appInfo()
-              .resolveMethodOnClass(group.getSuperType(), template)
+              .resolveMethodOnClassLegacy(group.getSuperType(), template)
               .asSingleResolution();
       if (resolutionResult == null || resolutionResult.getResolvedMethod().isAbstract()) {
         // If there is no super method or the method is abstract it should not be called.
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
index fa9bbde..9856e0b 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
@@ -487,7 +487,7 @@
         DexMethod rewrittenMethod =
             appView.graphLens().lookupInvokeDirect(method, getContext()).getReference();
         MethodResolutionResult resolutionResult =
-            appView.appInfo().resolveMethodOnClassHolder(rewrittenMethod);
+            appView.appInfo().resolveMethodOnClassHolderLegacy(rewrittenMethod);
         if (resolutionResult.isSingleResolution()
             && resolutionResult.getResolvedHolder().isProgramClass()) {
           enqueueMethod(resolutionResult.getResolvedProgramMethod());
@@ -499,7 +499,10 @@
         DexMethod rewrittenMethod =
             appView.graphLens().lookupInvokeInterface(method, getContext()).getReference();
         DexClassAndMethod resolvedMethod =
-            appView.appInfo().resolveMethodOnInterfaceHolder(rewrittenMethod).getResolutionPair();
+            appView
+                .appInfo()
+                .resolveMethodOnInterfaceHolderLegacy(rewrittenMethod)
+                .getResolutionPair();
         if (resolvedMethod != null) {
           fail();
         }
@@ -512,7 +515,7 @@
         ProgramMethod resolvedMethod =
             appView
                 .appInfo()
-                .unsafeResolveMethodDueToDexFormat(rewrittenMethod)
+                .unsafeResolveMethodDueToDexFormatLegacy(rewrittenMethod)
                 .getResolvedProgramMethod();
         if (resolvedMethod != null) {
           triggerClassInitializerIfNotAlreadyTriggeredInContext(resolvedMethod.getHolder());
@@ -537,7 +540,7 @@
         DexMethod rewrittenMethod =
             appView.graphLens().lookupInvokeVirtual(method, getContext()).getReference();
         DexClassAndMethod resolvedMethod =
-            appView.appInfo().resolveMethodOnClassHolder(rewrittenMethod).getResolutionPair();
+            appView.appInfo().resolveMethodOnClassHolderLegacy(rewrittenMethod).getResolutionPair();
         if (resolvedMethod != null) {
           if (!resolvedMethod.getHolder().isEffectivelyFinal(appView)) {
             fail();
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 fecb888..07b2fc7 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
@@ -108,7 +108,7 @@
     SingleResolutionResult<?> resolutionResult =
         appView
             .appInfo()
-            .resolveMethodOnClass(superType, method.getReference())
+            .resolveMethodOnClassLegacy(superType, method.getReference())
             .asSingleResolution();
     return resolutionResult != null && !resolutionResult.getResolvedMethod().isAbstract();
   }
@@ -121,7 +121,7 @@
           SingleResolutionResult<?> resolutionResult =
               appView
                   .appInfo()
-                  .resolveMethodOnInterface(interfaceType, method.getReference())
+                  .resolveMethodOnInterfaceLegacy(interfaceType, method.getReference())
                   .asSingleResolution();
           return resolutionResult != null && !resolutionResult.getResolvedMethod().isAbstract();
         });
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 ca52f7c..2a3602c 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
@@ -171,7 +171,7 @@
         if (clazzReserved.contains(signature)) {
           DexMethod template = signature.withHolder(clazz, appView.dexItemFactory());
           SingleResolutionResult<?> result =
-              appView.appInfo().resolveMethodOnClass(clazz, template).asSingleResolution();
+              appView.appInfo().resolveMethodOnClassLegacy(clazz, template).asSingleResolution();
           if (result == null || result.getResolvedHolder().isInterface()) {
             category = MethodCategory.KEEP_ABSENT;
           }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/deadlock/SingleCallerInformation.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/deadlock/SingleCallerInformation.java
index 95f0385..bbf486f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/deadlock/SingleCallerInformation.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/deadlock/SingleCallerInformation.java
@@ -216,7 +216,7 @@
         ProgramMethod target =
             appView
                 .appInfo()
-                .unsafeResolveMethodDueToDexFormat(rewrittenMethod)
+                .unsafeResolveMethodDueToDexFormatLegacy(rewrittenMethod)
                 .getResolvedProgramMethod();
         if (target != null) {
           recordDispatchTarget(target);
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 670f74a..9a7ddd8 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
@@ -333,7 +333,7 @@
       }
       DexMethod method = instruction.getInvokedMethod();
       MethodResolutionResult resolutionResult =
-          appView.appInfo().resolveMethodOnInterface(method.holder, method);
+          appView.appInfo().resolveMethodOnInterfaceLegacy(method.holder, method);
       if (!resolutionResult.isSingleResolution()) {
         return false;
       }
@@ -394,7 +394,7 @@
         return false;
       }
       MethodResolutionResult resolutionResult =
-          appView.appInfo().resolveMethodOn(superType, method, instruction.isInterface);
+          appView.appInfo().resolveMethodOnLegacy(superType, method, instruction.isInterface);
       if (!resolutionResult.isSingleResolution()) {
         return false;
       }
@@ -431,7 +431,7 @@
       }
       DexMethod method = instruction.getInvokedMethod();
       MethodResolutionResult resolutionResult =
-          appView.appInfo().resolveMethodOnClass(method.holder, method);
+          appView.appInfo().resolveMethodOnClassLegacy(method.holder, method);
       if (!resolutionResult.isSingleResolution()) {
         return false;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/ControlFlowGraph.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/ControlFlowGraph.java
index 5710f6e..a6150bc 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/ControlFlowGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/ControlFlowGraph.java
@@ -5,38 +5,155 @@
 package com.android.tools.r8.ir.analysis.framework.intraprocedural;
 
 import com.android.tools.r8.utils.TraversalContinuation;
-import com.google.common.collect.Iterables;
-import java.util.Collection;
+import com.android.tools.r8.utils.TraversalUtils;
 import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
 
 public interface ControlFlowGraph<Block, Instruction> {
 
-  Collection<Block> getPredecessors(Block block);
-
-  Collection<Block> getSuccessors(Block block);
-
   default boolean hasUniquePredecessor(Block block) {
-    return getPredecessors(block).size() == 1;
-  }
-
-  default Block getUniquePredecessor(Block block) {
-    assert hasUniquePredecessor(block);
-    return Iterables.getOnlyElement(getPredecessors(block));
+    return TraversalUtils.isSingleton(counter -> traversePredecessors(block, counter));
   }
 
   default boolean hasUniqueSuccessor(Block block) {
-    return getSuccessors(block).size() == 1;
+    return TraversalUtils.isSingleton(counter -> traverseSuccessors(block, counter));
   }
 
   default boolean hasUniqueSuccessorWithUniquePredecessor(Block block) {
-    return hasUniqueSuccessor(block) && getPredecessors(getUniqueSuccessor(block)).size() == 1;
+    return hasUniqueSuccessor(block) && hasUniquePredecessor(getUniqueSuccessor(block));
   }
 
   default Block getUniqueSuccessor(Block block) {
     assert hasUniqueSuccessor(block);
-    return Iterables.getOnlyElement(getSuccessors(block));
+    return TraversalUtils.getFirst(collector -> traverseSuccessors(block, collector));
   }
 
+  // Block traversal.
+
+  default <BT, CT> TraversalContinuation<BT, CT> traversePredecessors(
+      Block block, Function<? super Block, TraversalContinuation<BT, CT>> fn) {
+    return traversePredecessors(block, (predecessor, ignore) -> fn.apply(predecessor), null);
+  }
+
+  default <BT, CT> TraversalContinuation<BT, CT> traverseNormalPredecessors(
+      Block block, Function<? super Block, TraversalContinuation<BT, CT>> fn) {
+    return traverseNormalPredecessors(block, (predecessor, ignore) -> fn.apply(predecessor), null);
+  }
+
+  default <BT, CT> TraversalContinuation<BT, CT> traverseExceptionalPredecessors(
+      Block block, Function<? super Block, TraversalContinuation<BT, CT>> fn) {
+    return traverseExceptionalPredecessors(
+        block, (predecessor, ignore) -> fn.apply(predecessor), null);
+  }
+
+  default <BT, CT> TraversalContinuation<BT, CT> traverseSuccessors(
+      Block block, Function<? super Block, TraversalContinuation<BT, CT>> fn) {
+    return traverseSuccessors(block, (successor, ignore) -> fn.apply(successor), null);
+  }
+
+  default <BT, CT> TraversalContinuation<BT, CT> traverseNormalSuccessors(
+      Block block, Function<? super Block, TraversalContinuation<BT, CT>> fn) {
+    return traverseNormalSuccessors(block, (successor, ignore) -> fn.apply(successor), null);
+  }
+
+  default <BT, CT> TraversalContinuation<BT, CT> traverseExceptionalSuccessors(
+      Block block, Function<? super Block, TraversalContinuation<BT, CT>> fn) {
+    return traverseExceptionalSuccessors(block, (successor, ignore) -> fn.apply(successor), null);
+  }
+
+  // Block traversal with result.
+
+  default <BT, CT> TraversalContinuation<BT, CT> traversePredecessors(
+      Block block,
+      BiFunction<? super Block, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    return traverseNormalPredecessors(block, fn, initialValue)
+        .ifContinueThen(
+            continuation ->
+                traverseExceptionalPredecessors(block, fn, continuation.getValueOrDefault(null)));
+  }
+
+  <BT, CT> TraversalContinuation<BT, CT> traverseNormalPredecessors(
+      Block block,
+      BiFunction<? super Block, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue);
+
+  <BT, CT> TraversalContinuation<BT, CT> traverseExceptionalPredecessors(
+      Block block,
+      BiFunction<? super Block, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue);
+
+  default <BT, CT> TraversalContinuation<BT, CT> traverseSuccessors(
+      Block block,
+      BiFunction<? super Block, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    return traverseNormalSuccessors(block, fn, initialValue)
+        .ifContinueThen(
+            continuation ->
+                traverseExceptionalSuccessors(block, fn, continuation.getValueOrDefault(null)));
+  }
+
+  <BT, CT> TraversalContinuation<BT, CT> traverseNormalSuccessors(
+      Block block,
+      BiFunction<? super Block, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue);
+
+  <BT, CT> TraversalContinuation<BT, CT> traverseExceptionalSuccessors(
+      Block block,
+      BiFunction<? super Block, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue);
+
+  // Block iteration.
+
+  default void forEachPredecessor(Block block, Consumer<Block> consumer) {
+    forEachNormalPredecessor(block, consumer);
+    forEachExceptionalPredecessor(block, consumer);
+  }
+
+  default void forEachNormalPredecessor(Block block, Consumer<Block> consumer) {
+    traverseNormalPredecessors(
+        block,
+        predecessor -> {
+          consumer.accept(predecessor);
+          return TraversalContinuation.doContinue();
+        });
+  }
+
+  default void forEachExceptionalPredecessor(Block block, Consumer<Block> consumer) {
+    traverseExceptionalPredecessors(
+        block,
+        exceptionalPredecessor -> {
+          consumer.accept(exceptionalPredecessor);
+          return TraversalContinuation.doContinue();
+        });
+  }
+
+  default void forEachSuccessor(Block block, Consumer<Block> consumer) {
+    forEachNormalSuccessor(block, consumer);
+    forEachExceptionalSuccessor(block, consumer);
+  }
+
+  default void forEachNormalSuccessor(Block block, Consumer<Block> consumer) {
+    traverseNormalSuccessors(
+        block,
+        successor -> {
+          consumer.accept(successor);
+          return TraversalContinuation.doContinue();
+        });
+  }
+
+  default void forEachExceptionalSuccessor(Block block, Consumer<Block> consumer) {
+    traverseExceptionalSuccessors(
+        block,
+        exceptionalSuccessor -> {
+          consumer.accept(exceptionalSuccessor);
+          return TraversalContinuation.doContinue();
+        });
+  }
+
+  // Instruction traversal.
+
   <BT, CT> TraversalContinuation<BT, CT> traverseInstructions(
       Block block, BiFunction<Instruction, CT, TraversalContinuation<BT, CT>> fn, CT initialValue);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IRControlFlowGraph.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IRControlFlowGraph.java
new file mode 100644
index 0000000..aae27f7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IRControlFlowGraph.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2022, 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.framework.intraprocedural;
+
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.Instruction;
+
+public interface IRControlFlowGraph extends ControlFlowGraph<BasicBlock, Instruction> {
+
+  @Override
+  default boolean hasUniquePredecessor(BasicBlock block) {
+    return block.hasUniquePredecessor();
+  }
+
+  @Override
+  default boolean hasUniqueSuccessor(BasicBlock block) {
+    return block.hasUniqueSuccessor();
+  }
+
+  @Override
+  default boolean hasUniqueSuccessorWithUniquePredecessor(BasicBlock block) {
+    return block.hasUniqueSuccessorWithUniquePredecessor();
+  }
+
+  @Override
+  default BasicBlock getUniqueSuccessor(BasicBlock block) {
+    return block.getUniqueSuccessor();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraProceduralDataflowAnalysisBase.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraProceduralDataflowAnalysisBase.java
index 969197f..f904da3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraProceduralDataflowAnalysisBase.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraProceduralDataflowAnalysisBase.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.SuccessfulDataflowAnalysisResult;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.TraversalUtils;
 import com.android.tools.r8.utils.WorkList;
 import java.util.IdentityHashMap;
 import java.util.Map;
@@ -100,7 +101,7 @@
       // Update the block exit state, and re-enqueue all successor blocks if the abstract state
       // changed.
       if (setBlockExitState(end, state)) {
-        worklist.addAllIgnoringSeenSet(cfg.getSuccessors(end));
+        cfg.forEachSuccessor(end, worklist::addIgnoringSeenSet);
       }
 
       // Add the computed exit state to the entry state of each successor that satisfies the
@@ -114,14 +115,17 @@
     if (shouldCacheBlockEntryStateFor(block)) {
       return blockEntryStatesCache.getOrDefault(block, bottom);
     }
-    StateType result = bottom;
-    for (Block predecessor : cfg.getPredecessors(block)) {
-      StateType edgeState =
-          transfer.computeBlockEntryState(
-              block, predecessor, blockExitStates.getOrDefault(predecessor, bottom));
-      result = result.join(edgeState);
-    }
-    return result;
+    TraversalContinuation<?, StateType> traversalContinuation =
+        cfg.traversePredecessors(
+            block,
+            (predecessor, entryState) -> {
+              StateType edgeState =
+                  transfer.computeBlockEntryState(
+                      block, predecessor, blockExitStates.getOrDefault(predecessor, bottom));
+              return TraversalContinuation.doContinue(entryState.join(edgeState));
+            },
+            bottom);
+    return traversalContinuation.asContinue().getValue();
   }
 
   boolean setBlockExitState(Block block, StateType state) {
@@ -132,16 +136,18 @@
   }
 
   void updateBlockEntryStateCacheForSuccessors(Block block, StateType state) {
-    for (Block successor : cfg.getSuccessors(block)) {
-      if (shouldCacheBlockEntryStateFor(successor)) {
-        StateType edgeState = transfer.computeBlockEntryState(successor, block, state);
-        StateType previous = blockEntryStatesCache.getOrDefault(successor, bottom);
-        blockEntryStatesCache.put(successor, previous.join(edgeState));
-      }
-    }
+    cfg.forEachSuccessor(
+        block,
+        successor -> {
+          if (shouldCacheBlockEntryStateFor(successor)) {
+            StateType edgeState = transfer.computeBlockEntryState(successor, block, state);
+            StateType previous = blockEntryStatesCache.getOrDefault(successor, bottom);
+            blockEntryStatesCache.put(successor, previous.join(edgeState));
+          }
+        });
   }
 
   boolean shouldCacheBlockEntryStateFor(Block block) {
-    return cfg.getPredecessors(block).size() > 2;
+    return TraversalUtils.isSizeGreaterThan(counter -> cfg.traversePredecessors(block, counter), 2);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
index 13a9c42..789a2cb 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
@@ -17,4 +17,9 @@
       AbstractTransferFunction<BasicBlock, Instruction, StateType> transfer) {
     super(bottom, code, transfer);
   }
+
+  @Override
+  boolean shouldCacheBlockEntryStateFor(BasicBlock block) {
+    return block.getPredecessors().size() > 2;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfBlock.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfBlock.java
new file mode 100644
index 0000000..b86915c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfBlock.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2022, 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.framework.intraprocedural.cf;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.utils.SetUtils;
+import java.util.ArrayList;
+import java.util.List;
+
+/** A basic block for {@link com.android.tools.r8.graph.CfCode}. */
+public class CfBlock {
+
+  // The CfCode instruction index of the block's first instruction.
+  int firstInstructionIndex = -1;
+
+  // The CfCode instruction index of the block's last instruction.
+  int lastInstructionIndex = -1;
+
+  // The predecessors of the block. These are stored explicitly (unlike the successors) since they
+  // cannot efficiently be computed from the block.
+  final List<CfBlock> predecessors = new ArrayList<>();
+
+  // The exceptional predecessors of the block.
+  final List<CfBlock> exceptionalPredecessors = new ArrayList<>();
+
+  // The exceptional successors of the block (i.e., the catch handlers of the block).
+  final List<CfBlock> exceptionalSuccessors = new ArrayList<>();
+
+  public CfInstruction getFallthroughInstruction(CfCode code) {
+    int fallthroughInstructionIndex = getLastInstructionIndex() + 1;
+    return fallthroughInstructionIndex < code.getInstructions().size()
+        ? code.getInstructions().get(fallthroughInstructionIndex)
+        : null;
+  }
+
+  public int getFirstInstructionIndex() {
+    return firstInstructionIndex;
+  }
+
+  public CfInstruction getLastInstruction(CfCode code) {
+    return code.getInstructions().get(lastInstructionIndex);
+  }
+
+  public int getLastInstructionIndex() {
+    return lastInstructionIndex;
+  }
+
+  public List<CfBlock> getPredecessors() {
+    return predecessors;
+  }
+
+  // TODO(b/214496607): This currently only encodes the graph, but we likely need to include the
+  //  guard types here.
+  public List<CfBlock> getExceptionalPredecessors() {
+    return exceptionalPredecessors;
+  }
+
+  // TODO(b/214496607): This currently only encodes the graph, but we likely need to include the
+  //  guard types here.
+  public List<CfBlock> getExceptionalSuccessors() {
+    return exceptionalSuccessors;
+  }
+
+  // A mutable interface for block construction.
+  static class MutableCfBlock extends CfBlock {
+
+    void addPredecessor(CfBlock block) {
+      predecessors.add(block);
+    }
+
+    void addExceptionalPredecessor(CfBlock block) {
+      exceptionalPredecessors.add(block);
+    }
+
+    void addExceptionalSuccessor(CfBlock block) {
+      exceptionalSuccessors.add(block);
+    }
+
+    void setFirstInstructionIndex(int firstInstructionIndex) {
+      this.firstInstructionIndex = firstInstructionIndex;
+    }
+
+    void setLastInstructionIndex(int lastInstructionIndex) {
+      this.lastInstructionIndex = lastInstructionIndex;
+    }
+
+    boolean validate() {
+      assert 0 <= firstInstructionIndex;
+      assert firstInstructionIndex <= lastInstructionIndex;
+      assert SetUtils.newIdentityHashSet(predecessors).size() == predecessors.size();
+      return true;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfControlFlowGraph.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfControlFlowGraph.java
new file mode 100644
index 0000000..4cf9e5e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfControlFlowGraph.java
@@ -0,0 +1,312 @@
+// Copyright (c) 2022, 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.framework.intraprocedural.cf;
+
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.ControlFlowGraph;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.cf.CfBlock.MutableCfBlock;
+import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.TraversalUtils;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiFunction;
+
+/**
+ * The following provides a control flow graph for a piece of {@link CfCode}.
+ *
+ * <p>In the {@link CfControlFlowGraph}, each instruction that is the target of a jump (including
+ * fallthrough targets following jumps) starts a new basic block. The first instruction in {@link
+ * CfCode} also starts a new block.
+ *
+ * <p>Each block is identified by the first instruction of the block.
+ */
+public class CfControlFlowGraph implements ControlFlowGraph<CfBlock, CfInstruction> {
+
+  // Mapping from block entry instructions to cf blocks.
+  private final Map<CfInstruction, ? extends CfBlock> blocks;
+  private final CfCode code;
+
+  private CfControlFlowGraph(Map<CfInstruction, ? extends CfBlock> blocks, CfCode code) {
+    this.blocks = blocks;
+    this.code = code;
+  }
+
+  private static Builder builder(CfCode code) {
+    return new Builder(code);
+  }
+
+  public static CfControlFlowGraph create(CfCode code) {
+    return builder(code).build();
+  }
+
+  private CfBlock getBlock(CfInstruction blockEntry) {
+    assert blocks.containsKey(blockEntry);
+    return blocks.get(blockEntry);
+  }
+
+  public CfBlock getEntryBlock() {
+    return getBlock(code.getInstructions().get(0));
+  }
+
+  @Override
+  public <BT, CT> TraversalContinuation<BT, CT> traverseNormalPredecessors(
+      CfBlock block,
+      BiFunction<? super CfBlock, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    return TraversalUtils.traverseIterable(block.getPredecessors(), fn, initialValue);
+  }
+
+  @Override
+  public <BT, CT> TraversalContinuation<BT, CT> traverseExceptionalPredecessors(
+      CfBlock block,
+      BiFunction<? super CfBlock, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    return TraversalUtils.traverseIterable(block.getExceptionalPredecessors(), fn, initialValue);
+  }
+
+  @Override
+  public <BT, CT> TraversalContinuation<BT, CT> traverseNormalSuccessors(
+      CfBlock block,
+      BiFunction<? super CfBlock, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    CfInstruction blockExit = block.getLastInstruction(code);
+    CfInstruction fallthroughInstruction = block.getFallthroughInstruction(code);
+    return blockExit.traverseNormalTargets(
+        (target, value) -> fn.apply(getBlock(target), value), fallthroughInstruction, initialValue);
+  }
+
+  @Override
+  public <BT, CT> TraversalContinuation<BT, CT> traverseExceptionalSuccessors(
+      CfBlock block,
+      BiFunction<? super CfBlock, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    return TraversalUtils.traverseIterable(block.getExceptionalSuccessors(), fn, initialValue);
+  }
+
+  @Override
+  public <BT, CT> TraversalContinuation<BT, CT> traverseInstructions(
+      CfBlock block,
+      BiFunction<CfInstruction, CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    TraversalContinuation<BT, CT> traversalContinuation =
+        TraversalContinuation.doContinue(initialValue);
+    for (int instructionIndex = block.getFirstInstructionIndex();
+        instructionIndex <= block.getLastInstructionIndex();
+        instructionIndex++) {
+      CfInstruction instruction = code.getInstructions().get(instructionIndex);
+      traversalContinuation = fn.apply(instruction, traversalContinuation.asContinue().getValue());
+      if (traversalContinuation.shouldBreak()) {
+        break;
+      }
+    }
+    return traversalContinuation;
+  }
+
+  private static class Builder {
+
+    // Mapping from block entry instructions to the block that starts at each such instruction.
+    private final Map<CfInstruction, MutableCfBlock> blocks = new IdentityHashMap<>();
+
+    private final CfCode code;
+
+    Builder(CfCode code) {
+      this.code = code;
+    }
+
+    CfControlFlowGraph build() {
+      // Perform an initial pass over the CfCode to identify all instructions that start a new
+      // block.
+      createBlocks();
+
+      // Perform a second pass over the CfCode to finalize the identified blocks. This includes
+      // setting the instruction index of the last instruction of each block, which relies on having
+      // identified all block entries up front.
+      processBlocks();
+
+      assert blocks.values().stream().allMatch(MutableCfBlock::validate);
+
+      return new CfControlFlowGraph(blocks, code);
+    }
+
+    private void createBlocks() {
+      List<CfInstruction> instructions = code.getInstructions();
+
+      // The first instruction starts the first block.
+      createBlockIfAbsent(instructions.get(0));
+
+      // Create a block for each instruction that is targeted by a jump or fallthrough.
+      for (int instructionIndex = 0; instructionIndex < instructions.size(); instructionIndex++) {
+        CfInstruction instruction = instructions.get(instructionIndex);
+        if (instruction.isJump()) {
+          int fallthroughInstructionIndex = instructionIndex + 1;
+          CfInstruction fallthroughInstruction =
+              fallthroughInstructionIndex < instructions.size()
+                  ? instructions.get(fallthroughInstructionIndex)
+                  : null;
+          instruction.forEachNormalTarget(this::createBlockIfAbsent, fallthroughInstruction);
+        }
+      }
+
+      for (CfTryCatch tryCatch : code.getTryCatchRanges()) {
+        // Create a new block at the beginning and end of each try range. This is needed to ensure
+        // that each block has the same set of catch handlers.
+        createBlockIfAbsent(tryCatch.getStart());
+        createBlockIfAbsent(tryCatch.getEnd());
+
+        // Create a new block for the beginning of each catch handler.
+        tryCatch.forEachTarget(this::createBlockIfAbsent);
+      }
+    }
+
+    private void processBlocks() {
+      // A collection of active catch handlers. The catch handlers are stored in a map where the key
+      // is the label at which the catch handlers end.
+      Map<CfLabel, List<CfTryCatch>> activeUntilCatchHandlers = new IdentityHashMap<>();
+
+      // A collection of inactive catch handlers. The catch handlers are stored in a map where the
+      // key is the label at which the catch handlers start.
+      Map<CfLabel, List<CfTryCatch>> inactiveUntilCatchHandlers = new IdentityHashMap<>();
+
+      // Initialize all catch handlers to be inactive.
+      for (CfTryCatch tryCatch : code.getTryCatchRanges()) {
+        inactiveUntilCatchHandlers
+            .computeIfAbsent(tryCatch.getStart(), ignoreKey(ArrayList::new))
+            .add(tryCatch);
+      }
+
+      // Process each instruction.
+      List<CfInstruction> instructions = code.getInstructions();
+      for (int instructionIndex = 0; instructionIndex < instructions.size(); instructionIndex++) {
+        CfInstruction instruction = instructions.get(instructionIndex);
+        MutableCfBlock block = getBlockOrNull(instruction);
+        if (block != null) {
+          instructionIndex =
+              processBlock(
+                  instruction,
+                  instructionIndex,
+                  block,
+                  activeUntilCatchHandlers,
+                  inactiveUntilCatchHandlers);
+        }
+      }
+
+      assert activeUntilCatchHandlers.isEmpty();
+      assert inactiveUntilCatchHandlers.isEmpty();
+    }
+
+    private int processBlock(
+        CfInstruction instruction,
+        int instructionIndex,
+        MutableCfBlock block,
+        Map<CfLabel, List<CfTryCatch>> activeUntilCatchHandlers,
+        Map<CfLabel, List<CfTryCatch>> inactiveUntilCatchHandlers) {
+      // Record the index of the first instruction of the block.
+      block.setFirstInstructionIndex(instructionIndex);
+
+      if (instruction.isLabel()) {
+        updateCatchHandlers(
+            instruction.asLabel(), activeUntilCatchHandlers, inactiveUntilCatchHandlers);
+      }
+
+      // Visit each instruction belonging to the current block.
+      Set<CfLabel> exceptionalSuccessors = new LinkedHashSet<>();
+      do {
+        assert !instruction.isLabel()
+            || verifyCatchHandlersUnchanged(
+                instruction.asLabel(), activeUntilCatchHandlers, inactiveUntilCatchHandlers);
+        if (instruction.canThrow()) {
+          for (CfTryCatch tryCatch : IterableUtils.flatten(activeUntilCatchHandlers.values())) {
+            exceptionalSuccessors.addAll(tryCatch.getTargets());
+          }
+        }
+        if (isBlockExit(instructionIndex)) {
+          break;
+        }
+        instruction = code.getInstructions().get(++instructionIndex);
+      } while (true);
+
+      // Record the index of the last instruction of the block.
+      block.setLastInstructionIndex(instructionIndex);
+
+      // Add the current block as a predecessor of the successor blocks.
+      CfInstruction fallthroughInstruction = block.getFallthroughInstruction(code);
+      instruction.forEachNormalTarget(
+          target -> getBlock(target).addPredecessor(block), fallthroughInstruction);
+
+      // Add the current block as an exceptional predecessor of the exceptional successor blocks.
+      exceptionalSuccessors.forEach(
+          exceptionalSuccessor -> {
+            MutableCfBlock exceptionalSuccessorBlock = getBlock(exceptionalSuccessor);
+            block.addExceptionalSuccessor(exceptionalSuccessorBlock);
+            exceptionalSuccessorBlock.addExceptionalPredecessor(block);
+          });
+
+      return instructionIndex;
+    }
+
+    private boolean isBlockEntry(CfInstruction instruction) {
+      return blocks.containsKey(instruction);
+    }
+
+    private boolean isBlockExit(int instructionIndex) {
+      int lastInstructionIndex = code.getInstructions().size() - 1;
+      if (instructionIndex == lastInstructionIndex) {
+        return true;
+      }
+      CfInstruction nextInstruction = code.getInstructions().get(instructionIndex + 1);
+      return isBlockEntry(nextInstruction);
+    }
+
+    private void updateCatchHandlers(
+        CfLabel instruction,
+        Map<CfLabel, List<CfTryCatch>> activeUntilCatchHandlers,
+        Map<CfLabel, List<CfTryCatch>> inactiveUntilCatchHandlers) {
+      // Remove active catch handlers that have expired at the current instruction.
+      activeUntilCatchHandlers.remove(instruction);
+
+      // Promote inactive catch handlers that is activated at the current instruction to active.
+      for (CfTryCatch tryCatch :
+          inactiveUntilCatchHandlers.getOrDefault(instruction, Collections.emptyList())) {
+        assert tryCatch.getEnd() != tryCatch.getStart();
+        activeUntilCatchHandlers
+            .computeIfAbsent(tryCatch.getEnd(), ignoreKey(ArrayList::new))
+            .add(tryCatch);
+      }
+    }
+
+    private boolean verifyCatchHandlersUnchanged(
+        CfLabel instruction,
+        Map<CfLabel, List<CfTryCatch>> activeUntilCatchHandlers,
+        Map<CfLabel, List<CfTryCatch>> inactiveUntilCatchHandlers) {
+      assert !activeUntilCatchHandlers.containsKey(instruction);
+      assert !inactiveUntilCatchHandlers.containsKey(instruction);
+      return true;
+    }
+
+    private void createBlockIfAbsent(CfInstruction blockEntry) {
+      blocks.computeIfAbsent(blockEntry, ignoreKey(MutableCfBlock::new));
+    }
+
+    private MutableCfBlock getBlock(CfInstruction blockEntry) {
+      assert blocks.containsKey(blockEntry);
+      return blocks.get(blockEntry);
+    }
+
+    private MutableCfBlock getBlockOrNull(CfInstruction blockEntry) {
+      return blocks.get(blockEntry);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfIntraproceduralDataflowAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfIntraproceduralDataflowAnalysis.java
new file mode 100644
index 0000000..b4ca696
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfIntraproceduralDataflowAnalysis.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2022, 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.framework.intraprocedural.cf;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractState;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractTransferFunction;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.IntraProceduralDataflowAnalysisBase;
+
+public class CfIntraproceduralDataflowAnalysis<StateType extends AbstractState<StateType>>
+    extends IntraProceduralDataflowAnalysisBase<CfBlock, CfInstruction, StateType> {
+
+  public CfIntraproceduralDataflowAnalysis(
+      StateType bottom,
+      CfControlFlowGraph cfg,
+      AbstractTransferFunction<CfBlock, CfInstruction, StateType> transfer) {
+    super(bottom, cfg, transfer);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index b664c13..0d1bf75 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableList;
@@ -48,6 +49,7 @@
 import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.WeakHashMap;
+import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -119,7 +121,7 @@
 
   public enum ThrowingInfo {
     NO_THROW,
-    CAN_THROW;
+    CAN_THROW
   }
 
   public enum EdgeType {
@@ -189,6 +191,73 @@
   // Map of registers to current SSA value. Used during SSA numbering and cleared once filled.
   private Map<Integer, Value> currentDefinitions = new HashMap<>();
 
+  public <BT, CT> TraversalContinuation<BT, CT> traverseNormalPredecessors(
+      BiFunction<? super BasicBlock, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    TraversalContinuation<BT, CT> traversalContinuation =
+        TraversalContinuation.doContinue(initialValue);
+    for (BasicBlock predecessor : getPredecessors()) {
+      if (predecessor.hasCatchSuccessor(this)) {
+        continue;
+      }
+      traversalContinuation =
+          fn.apply(predecessor, traversalContinuation.asContinue().getValueOrDefault(null));
+      if (traversalContinuation.isBreak()) {
+        break;
+      }
+    }
+    return traversalContinuation;
+  }
+
+  public <BT, CT> TraversalContinuation<BT, CT> traverseNormalSuccessors(
+      BiFunction<? super BasicBlock, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    TraversalContinuation<BT, CT> traversalContinuation =
+        TraversalContinuation.doContinue(initialValue);
+    for (int i = successors.size() - numberOfNormalSuccessors(); i < successors.size(); i++) {
+      traversalContinuation =
+          fn.apply(successors.get(i), traversalContinuation.asContinue().getValueOrDefault(null));
+      if (traversalContinuation.isBreak()) {
+        break;
+      }
+    }
+    return traversalContinuation;
+  }
+
+  public <BT, CT> TraversalContinuation<BT, CT> traverseExceptionalPredecessors(
+      BiFunction<? super BasicBlock, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    TraversalContinuation<BT, CT> traversalContinuation =
+        TraversalContinuation.doContinue(initialValue);
+    for (BasicBlock predecessor : getPredecessors()) {
+      if (!predecessor.hasCatchSuccessor(this)) {
+        continue;
+      }
+      traversalContinuation =
+          fn.apply(predecessor, traversalContinuation.asContinue().getValueOrDefault(null));
+      if (traversalContinuation.isBreak()) {
+        break;
+      }
+    }
+    return traversalContinuation;
+  }
+
+  public <BT, CT> TraversalContinuation<BT, CT> traverseExceptionalSuccessors(
+      BiFunction<? super BasicBlock, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    int numberOfExceptionalSuccessors = numberOfExceptionalSuccessors();
+    TraversalContinuation<BT, CT> traversalContinuation =
+        TraversalContinuation.doContinue(initialValue);
+    for (int i = 0; i < numberOfExceptionalSuccessors; i++) {
+      traversalContinuation =
+          fn.apply(successors.get(i), traversalContinuation.asContinue().getValueOrDefault(null));
+      if (traversalContinuation.isBreak()) {
+        break;
+      }
+    }
+    return traversalContinuation;
+  }
+
   public void addControlFlowEdgesMayChangeListener(BasicBlockChangeListener listener) {
     if (onControlFlowEdgesMayChangeListeners == null) {
       // WeakSet to allow the listeners to be garbage collected.
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index b8eb09d..552ebf3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -202,7 +202,7 @@
       DexItemFactory dexItemFactory = appView.dexItemFactory();
       DexEncodedMethod resolutionResult =
           appInfo
-              .resolveMethodOnClass(clazz, dexItemFactory.objectMembers.finalize)
+              .resolveMethodOnClassLegacy(clazz, dexItemFactory.objectMembers.finalize)
               .getSingleTarget();
       return resolutionResult != null && resolutionResult.isProgramMethod(appView);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 53fa359..bf8f7ad 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
 import com.android.tools.r8.ir.analysis.TypeChecker;
 import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
-import com.android.tools.r8.ir.analysis.framework.intraprocedural.ControlFlowGraph;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.IRControlFlowGraph;
 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.TypeElement;
@@ -64,7 +64,7 @@
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-public class IRCode implements ControlFlowGraph<BasicBlock, Instruction>, ValueFactory {
+public class IRCode implements IRControlFlowGraph, ValueFactory {
 
   private static final int MAX_MARKING_COLOR = 0x40000000;
 
@@ -1352,17 +1352,47 @@
     return blocks;
   }
 
-  @Override
   public Collection<BasicBlock> getPredecessors(BasicBlock block) {
     return block.getPredecessors();
   }
 
-  @Override
   public Collection<BasicBlock> getSuccessors(BasicBlock block) {
     return block.getSuccessors();
   }
 
   @Override
+  public <BT, CT> TraversalContinuation<BT, CT> traverseNormalPredecessors(
+      BasicBlock block,
+      BiFunction<? super BasicBlock, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    return block.traverseNormalPredecessors(fn, initialValue);
+  }
+
+  @Override
+  public <BT, CT> TraversalContinuation<BT, CT> traverseNormalSuccessors(
+      BasicBlock block,
+      BiFunction<? super BasicBlock, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    return block.traverseNormalSuccessors(fn, initialValue);
+  }
+
+  @Override
+  public <BT, CT> TraversalContinuation<BT, CT> traverseExceptionalPredecessors(
+      BasicBlock block,
+      BiFunction<? super BasicBlock, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    return block.traverseExceptionalPredecessors(fn, initialValue);
+  }
+
+  @Override
+  public <BT, CT> TraversalContinuation<BT, CT> traverseExceptionalSuccessors(
+      BasicBlock block,
+      BiFunction<? super BasicBlock, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    return block.traverseExceptionalSuccessors(fn, initialValue);
+  }
+
+  @Override
   public <BT, CT> TraversalContinuation<BT, CT> traverseInstructions(
       BasicBlock block,
       BiFunction<Instruction, CT, TraversalContinuation<BT, CT>> fn,
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 4e77680..ec23162 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
@@ -151,7 +151,7 @@
       }
     }
     MethodResolutionResult resolutionResult =
-        appView.appInfo().resolveMethod(method, getInterfaceBit());
+        appView.appInfo().resolveMethodLegacy(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 c13800c..668d515 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
@@ -200,7 +200,7 @@
     SingleResolutionResult<?> resolutionResult =
         appViewWithClassHierarchy
             .appInfo()
-            .resolveMethod(getInvokedMethod(), getInterfaceBit())
+            .resolveMethodLegacy(getInvokedMethod(), getInterfaceBit())
             .asSingleResolution();
     if (resolutionResult == null) {
       return true;
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 f39baa4..b405dbb 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
@@ -199,7 +199,7 @@
     SingleResolutionResult<?> resolutionResult =
         appViewWithLiveness
             .appInfo()
-            .resolveMethod(getInvokedMethod(), isInterface)
+            .resolveMethodLegacy(getInvokedMethod(), isInterface)
             .asSingleResolution();
 
     // Verify that the target method is present.
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 baae8e5..85bae5c 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
@@ -194,7 +194,7 @@
     MethodResolutionResult finalizeResolutionResult =
         appViewWithClassHierarchy
             .appInfo()
-            .resolveMethodOnClass(clazz, dexItemFactory.objectMembers.finalize);
+            .resolveMethodOnClassLegacy(clazz, dexItemFactory.objectMembers.finalize);
     if (finalizeResolutionResult.isSingleResolution()) {
       DexMethod finalizeMethod = finalizeResolutionResult.getSingleTarget().getReference();
       if (finalizeMethod != dexItemFactory.enumMembers.finalize
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 fc23097..fa7a539 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
@@ -94,12 +94,12 @@
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.NeverMergeGroup;
+import com.android.tools.r8.utils.LazyBox;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.google.common.base.Suppliers;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -1175,7 +1175,7 @@
       return timing;
     }
 
-    assertionsRewriter.run(method, code, timing);
+    assertionsRewriter.run(method, code, deadCodeRemover, timing);
 
     if (serviceLoaderRewriter != null) {
       assert appView.appInfo().hasLiveness();
@@ -1402,7 +1402,7 @@
           methodProcessor,
           methodProcessingContext,
           inliner,
-          Suppliers.memoize(
+          new LazyBox<>(
               () ->
                   inliner.createDefaultOracle(
                       code.context(),
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java
index edd4f14..3fc7ad0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java
@@ -78,7 +78,7 @@
     if (type == Invoke.Type.INTERFACE || type == Invoke.Type.VIRTUAL) {
       // For virtual and interface calls add all potential targets that could be called.
       MethodResolutionResult resolutionResult =
-          appView.appInfo().resolveMethod(method, type == Invoke.Type.INTERFACE);
+          appView.appInfo().resolveMethodLegacy(method, type == Invoke.Type.INTERFACE);
       DexClassAndMethod target = resolutionResult.getResolutionPair();
       if (target != null) {
         processInvokeWithDynamicDispatch(type, target, context);
@@ -115,7 +115,7 @@
             target,
             method -> {
               MethodResolutionResult resolution =
-                  appView.appInfo().resolveMethod(method, isInterface);
+                  appView.appInfo().resolveMethodLegacy(method, isInterface);
               if (resolution.isVirtualTarget()) {
                 LookupResult lookupResult =
                     resolution.lookupVirtualDispatchTargets(context.getHolder(), appView.appInfo());
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 012c31c..ac5addd 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
@@ -373,7 +373,7 @@
     assert descriptor.verifyTargetFoundInClass(accessedFrom.getHolderType());
     if (implHandle.type.isInvokeStatic()) {
       MethodResolutionResult resolution =
-          appView.appInfoForDesugaring().resolveMethod(implMethod, implHandle.isInterface);
+          appView.appInfoForDesugaring().resolveMethodLegacy(implMethod, implHandle.isInterface);
       if (resolution.isFailedResolution()) {
         return new InvalidLambdaImplTarget(
             implMethod,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index bc7a544..d6b10b0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -123,7 +123,7 @@
       case INVOKE_INSTANCE: {
           DexEncodedMethod target =
               appInfo
-                  .resolveMethodOn(getImplReceiverType(), method, implHandle.isInterface)
+                  .resolveMethodOnLegacy(getImplReceiverType(), method, implHandle.isInterface)
                   .getSingleTarget();
         if (target == null) {
             target = appInfo.lookupDirectTarget(method, context);
@@ -149,7 +149,9 @@
 
       case INVOKE_INTERFACE: {
           DexEncodedMethod target =
-              appInfo.resolveMethodOnInterface(getImplReceiverType(), method).getSingleTarget();
+              appInfo
+                  .resolveMethodOnInterfaceLegacy(getImplReceiverType(), method)
+                  .getSingleTarget();
         assert target == null || isInstanceMethod(target);
         return target;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
index 8935159..e408232 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
@@ -120,7 +120,7 @@
     MethodResolutionResult resolution =
         appView
             .appInfoForDesugaring()
-            .resolveMethod(bootstrapMethodReference, bootstrapMethodHandle.isInterface);
+            .resolveMethodLegacy(bootstrapMethodReference, bootstrapMethodHandle.isInterface);
     if (resolution.isSingleResolution()
         && resolution.asSingleResolution().getResolvedMethod().isStatic()) {
       SingleResolutionResult<?> result = resolution.asSingleResolution();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java
new file mode 100644
index 0000000..66a980a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2022, 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.utils.AndroidApiLevel;
+import java.util.Objects;
+
+public class ApiLevelRange {
+
+  private final AndroidApiLevel apiLevelBelowOrEqual;
+  private final AndroidApiLevel apiLevelGreaterOrEqual;
+
+  public ApiLevelRange(int apiLevelBelowOrEqual) {
+    this(AndroidApiLevel.getAndroidApiLevel(apiLevelBelowOrEqual), null);
+  }
+
+  public ApiLevelRange(int apiLevelBelowOrEqual, int apiLevelGreaterOrEqual) {
+    this(
+        AndroidApiLevel.getAndroidApiLevel(apiLevelBelowOrEqual),
+        AndroidApiLevel.getAndroidApiLevel(apiLevelGreaterOrEqual));
+  }
+
+  public ApiLevelRange(
+      AndroidApiLevel apiLevelBelowOrEqual, AndroidApiLevel apiLevelGreaterOrEqual) {
+    this.apiLevelBelowOrEqual = apiLevelBelowOrEqual;
+    this.apiLevelGreaterOrEqual = apiLevelGreaterOrEqual;
+  }
+
+  public int getApiLevelBelowOrEqualAsInt() {
+    return apiLevelBelowOrEqual.getLevel();
+  }
+
+  public int getApiLevelGreaterOrEqualAsInt() {
+    return apiLevelGreaterOrEqual.getLevel();
+  }
+
+  public boolean hasApiLevelGreaterOrEqual() {
+    return apiLevelGreaterOrEqual != null;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof ApiLevelRange)) {
+      return false;
+    }
+    ApiLevelRange that = (ApiLevelRange) o;
+    return apiLevelBelowOrEqual.equals(that.apiLevelBelowOrEqual)
+        && apiLevelGreaterOrEqual.equals(that.apiLevelGreaterOrEqual);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(apiLevelBelowOrEqual, apiLevelGreaterOrEqual);
+  }
+
+  public int deterministicOrder(ApiLevelRange other) {
+    int compare = apiLevelBelowOrEqual.compareTo(other.apiLevelBelowOrEqual);
+    if (compare != 0) {
+      return compare;
+    }
+    if (apiLevelGreaterOrEqual == null) {
+      if (other.apiLevelGreaterOrEqual == null) {
+        return 0;
+      }
+      return 1;
+    }
+    if (other.apiLevelGreaterOrEqual == null) {
+      return -1;
+    }
+    return apiLevelGreaterOrEqual.compareTo(other.apiLevelGreaterOrEqual);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java
index f041fe3..55abcef 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java
@@ -139,7 +139,7 @@
         ? appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context)
         : appView
             .appInfoForDesugaring()
-            .resolveMethod(invokedMethod, invoke.isInterface())
+            .resolveMethodLegacy(invokedMethod, invoke.isInterface())
             .getResolutionPair();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
index 79f1ff2..47f48d4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
@@ -287,7 +287,7 @@
                 + invokedMethod.holder
                 + "#"
                 + invokedMethod.name
-                + " may not work correctly at runtime (Cannot convert type "
+                + " may not work correctly at runtime (No conversion registered for type "
                 + desugaredType
                 + ").",
             origin,
@@ -520,12 +520,13 @@
         .setInterfaces(interfaces)
         .setSuperType(superType)
         .setInstanceFields(Collections.singletonList(wrapperField))
-        .addMethod(methodBuilder -> buildWrapperConstructor(wrapperField, methodBuilder));
+        .addMethod(
+            methodBuilder -> buildWrapperConstructor(wrapperField, methodBuilder, superType));
     return wrapperField;
   }
 
   private void buildWrapperConstructor(
-      DexEncodedField wrappedValueField, SyntheticMethodBuilder methodBuilder) {
+      DexEncodedField wrappedValueField, SyntheticMethodBuilder methodBuilder, DexType superType) {
     methodBuilder
         .setName(factory.constructorMethodName)
         .setProto(factory.createProto(factory.voidType, wrappedValueField.getType()))
@@ -536,7 +537,8 @@
         .disableAndroidApiLevelCheck()
         .setCode(
             codeSynthesizor ->
-                new APIConverterConstructorCfCodeProvider(appView, wrappedValueField.getReference())
+                new APIConverterConstructorCfCodeProvider(
+                        appView, wrappedValueField.getReference(), superType)
                     .generateCfCode());
   }
 
@@ -650,7 +652,7 @@
 
   @Override
   public String uniqueIdentifier() {
-    return "$wrapper$";
+    return "$wrapper";
   }
 
   // Program wrappers are harder to deal with than classpath wrapper because generating a method's
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
index 5936d8c..027a2ae 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
@@ -47,10 +47,12 @@
   static final String PROGRAM_FLAGS_KEY = "program_flags";
 
   static final String API_LEVEL_BELOW_OR_EQUAL_KEY = "api_level_below_or_equal";
+  static final String API_LEVEL_GREATER_OR_EQUAL_KEY = "api_level_greater_or_equal";
   static final String WRAPPER_CONVERSION_KEY = "wrapper_conversion";
   static final String WRAPPER_CONVERSION_EXCLUDING_KEY = "wrapper_conversion_excluding";
   static final String CUSTOM_CONVERSION_KEY = "custom_conversion";
   static final String REWRITE_PREFIX_KEY = "rewrite_prefix";
+  static final String DONT_REWRITE_PREFIX_KEY = "dont_rewrite_prefix";
   static final String MAINTAIN_PREFIX_KEY = "maintain_prefix";
   static final String RETARGET_STATIC_FIELD_KEY = "retarget_static_field";
   static final String RETARGET_METHOD_KEY = "retarget_method";
@@ -231,7 +233,13 @@
       JsonObject flag = jsonFlagSet.getAsJsonObject();
       int api_level_below_or_equal = required(flag, API_LEVEL_BELOW_OR_EQUAL_KEY).getAsInt();
       if (minAPILevel <= api_level_below_or_equal) {
-        parseFlags(flag, builder);
+        if (flag.has(API_LEVEL_GREATER_OR_EQUAL_KEY)) {
+          if (minAPILevel >= flag.get(API_LEVEL_GREATER_OR_EQUAL_KEY).getAsInt()) {
+            parseFlags(flag, builder);
+          }
+        } else {
+          parseFlags(flag, builder);
+        }
       }
     }
   }
@@ -248,6 +256,12 @@
         builder.putMaintainPrefix(maintainPrefix.getAsString());
       }
     }
+    if (jsonFlagSet.has(DONT_REWRITE_PREFIX_KEY)) {
+      for (JsonElement dontRewritePrefix :
+          jsonFlagSet.get(DONT_REWRITE_PREFIX_KEY).getAsJsonArray()) {
+        builder.putDontRewritePrefix(dontRewritePrefix.getAsString());
+      }
+    }
     if (jsonFlagSet.has(REWRITE_DERIVED_PREFIX_KEY)) {
       for (Map.Entry<String, JsonElement> prefixToMatch :
           jsonFlagSet.get(REWRITE_DERIVED_PREFIX_KEY).getAsJsonObject().entrySet()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
index 50e98e1..fc5bcca 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
@@ -26,6 +26,7 @@
 public class HumanRewritingFlags {
 
   private final Map<String, String> rewritePrefix;
+  private final Set<String> dontRewritePrefix;
   private final Set<String> maintainPrefix;
   private final Map<String, Map<String, String>> rewriteDerivedPrefix;
   private final Map<DexType, DexType> emulatedInterfaces;
@@ -42,6 +43,7 @@
 
   HumanRewritingFlags(
       Map<String, String> rewritePrefix,
+      Set<String> dontRewritePrefix,
       Set<String> maintainPrefix,
       Map<String, Map<String, String>> rewriteDerivedPrefix,
       Map<DexType, DexType> emulateLibraryInterface,
@@ -56,6 +58,7 @@
       Map<DexMethod, MethodAccessFlags> amendLibraryMethod,
       Map<DexField, FieldAccessFlags> amendLibraryField) {
     this.rewritePrefix = rewritePrefix;
+    this.dontRewritePrefix = dontRewritePrefix;
     this.maintainPrefix = maintainPrefix;
     this.rewriteDerivedPrefix = rewriteDerivedPrefix;
     this.emulatedInterfaces = emulateLibraryInterface;
@@ -75,6 +78,7 @@
     return new HumanRewritingFlags(
         ImmutableMap.of(),
         ImmutableSet.of(),
+        ImmutableSet.of(),
         ImmutableMap.of(),
         ImmutableMap.of(),
         ImmutableMap.of(),
@@ -98,6 +102,7 @@
         reporter,
         origin,
         rewritePrefix,
+        dontRewritePrefix,
         maintainPrefix,
         rewriteDerivedPrefix,
         emulatedInterfaces,
@@ -117,6 +122,10 @@
     return rewritePrefix;
   }
 
+  public Set<String> getDontRewritePrefix() {
+    return dontRewritePrefix;
+  }
+
   public Set<String> getMaintainPrefix() {
     return maintainPrefix;
   }
@@ -185,6 +194,7 @@
     private final Origin origin;
 
     private final Map<String, String> rewritePrefix;
+    private final Set<String> dontRewritePrefix;
     private final Set<String> maintainPrefix;
     private final Map<String, Map<String, String>> rewriteDerivedPrefix;
     private final Map<DexType, DexType> emulatedInterfaces;
@@ -205,6 +215,7 @@
           origin,
           new HashMap<>(),
           Sets.newIdentityHashSet(),
+          Sets.newIdentityHashSet(),
           new HashMap<>(),
           new IdentityHashMap<>(),
           new IdentityHashMap<>(),
@@ -223,6 +234,7 @@
         Reporter reporter,
         Origin origin,
         Map<String, String> rewritePrefix,
+        Set<String> dontRewritePrefix,
         Set<String> maintainPrefix,
         Map<String, Map<String, String>> rewriteDerivedPrefix,
         Map<DexType, DexType> emulateLibraryInterface,
@@ -239,6 +251,7 @@
       this.reporter = reporter;
       this.origin = origin;
       this.rewritePrefix = new HashMap<>(rewritePrefix);
+      this.dontRewritePrefix = Sets.newHashSet(dontRewritePrefix);
       this.maintainPrefix = Sets.newHashSet(maintainPrefix);
       this.rewriteDerivedPrefix = new HashMap<>(rewriteDerivedPrefix);
       this.emulatedInterfaces = new IdentityHashMap<>(emulateLibraryInterface);
@@ -281,6 +294,11 @@
       return this;
     }
 
+    public Builder putDontRewritePrefix(String prefix) {
+      dontRewritePrefix.add(prefix);
+      return this;
+    }
+
     public Builder putMaintainPrefix(String prefix) {
       maintainPrefix.add(prefix);
       return this;
@@ -385,6 +403,7 @@
       validate();
       return new HumanRewritingFlags(
           ImmutableMap.copyOf(rewritePrefix),
+          ImmutableSet.copyOf(dontRewritePrefix),
           ImmutableSet.copyOf(maintainPrefix),
           ImmutableMap.copyOf(rewriteDerivedPrefix),
           ImmutableMap.copyOf(emulatedInterfaces),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecification.java
index 69c82a9..ef8be02 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecification.java
@@ -4,24 +4,24 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification;
 
+import com.android.tools.r8.ir.desugar.desugaredlibrary.ApiLevelRange;
 import com.android.tools.r8.origin.Origin;
-import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import java.util.Map;
 
 public class MultiAPILevelHumanDesugaredLibrarySpecification {
 
   private final Origin origin;
   private final HumanTopLevelFlags topLevelFlags;
-  private final Int2ObjectMap<HumanRewritingFlags> commonFlags;
-  private final Int2ObjectMap<HumanRewritingFlags> libraryFlags;
-  private final Int2ObjectMap<HumanRewritingFlags> programFlags;
+  private final Map<ApiLevelRange, HumanRewritingFlags> commonFlags;
+  private final Map<ApiLevelRange, HumanRewritingFlags> libraryFlags;
+  private final Map<ApiLevelRange, HumanRewritingFlags> programFlags;
 
   public MultiAPILevelHumanDesugaredLibrarySpecification(
       Origin origin,
       HumanTopLevelFlags topLevelFlags,
-      Int2ObjectMap<HumanRewritingFlags> commonFlags,
-      Int2ObjectMap<HumanRewritingFlags> libraryFlags,
-      Int2ObjectMap<HumanRewritingFlags> programFlags) {
+      Map<ApiLevelRange, HumanRewritingFlags> commonFlags,
+      Map<ApiLevelRange, HumanRewritingFlags> libraryFlags,
+      Map<ApiLevelRange, HumanRewritingFlags> programFlags) {
     this.origin = origin;
     this.topLevelFlags = topLevelFlags;
     this.commonFlags = commonFlags;
@@ -37,28 +37,15 @@
     return topLevelFlags;
   }
 
-  public Int2ObjectMap<HumanRewritingFlags> getCommonFlags() {
+  public Map<ApiLevelRange, HumanRewritingFlags> getCommonFlags() {
     return commonFlags;
   }
 
-  public Int2ObjectMap<HumanRewritingFlags> getLibraryFlags() {
+  public Map<ApiLevelRange, HumanRewritingFlags> getLibraryFlags() {
     return libraryFlags;
   }
 
-  public Int2ObjectMap<HumanRewritingFlags> getProgramFlags() {
+  public Map<ApiLevelRange, HumanRewritingFlags> getProgramFlags() {
     return programFlags;
   }
-
-  public Map<Integer, HumanRewritingFlags> getCommonFlagsForTesting() {
-    return commonFlags;
-  }
-
-  public Map<Integer, HumanRewritingFlags> getLibraryFlagsForTesting() {
-    return libraryFlags;
-  }
-
-  public Map<Integer, HumanRewritingFlags> getProgramFlagsForTesting() {
-    return programFlags;
-  }
-
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.java
index c059c27..6283419 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.java
@@ -8,10 +8,10 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.ApiLevelRange;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.Reporter;
-import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
-import it.unimi.dsi.fastutil.ints.IntArraySet;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.BiConsumer;
@@ -23,12 +23,12 @@
       MultiAPILevelHumanDesugaredLibrarySpecification specification,
       Reporter reporter) {
 
-    IntArraySet apis = new IntArraySet();
+    Set<ApiLevelRange> apis = new HashSet<>();
     apis.addAll(specification.getCommonFlags().keySet());
     apis.addAll(specification.getLibraryFlags().keySet());
     apis.addAll(specification.getProgramFlags().keySet());
 
-    for (Integer api : apis) {
+    for (ApiLevelRange api : apis) {
       deduplicateFlags(specification, reporter, api);
     }
   }
@@ -36,11 +36,11 @@
   private static void deduplicateFlags(
       MultiAPILevelHumanDesugaredLibrarySpecification specification,
       Reporter reporter,
-      int api) {
+      ApiLevelRange api) {
 
-    Int2ObjectMap<HumanRewritingFlags> commonFlags = specification.getCommonFlags();
-    Int2ObjectMap<HumanRewritingFlags> libraryFlags = specification.getLibraryFlags();
-    Int2ObjectMap<HumanRewritingFlags> programFlags = specification.getProgramFlags();
+    Map<ApiLevelRange, HumanRewritingFlags> commonFlags = specification.getCommonFlags();
+    Map<ApiLevelRange, HumanRewritingFlags> libraryFlags = specification.getLibraryFlags();
+    Map<ApiLevelRange, HumanRewritingFlags> programFlags = specification.getProgramFlags();
 
     HumanRewritingFlags library = libraryFlags.get(api);
     HumanRewritingFlags program = programFlags.get(api);
@@ -68,7 +68,9 @@
   }
 
   private static void putNewFlags(
-      int api, Int2ObjectMap<HumanRewritingFlags> flags, HumanRewritingFlags.Builder builder) {
+      ApiLevelRange api,
+      Map<ApiLevelRange, HumanRewritingFlags> flags,
+      HumanRewritingFlags.Builder builder) {
     HumanRewritingFlags build = builder.build();
     if (build.isEmpty()) {
       flags.remove(api);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java
index 928eeab..d77a0bc 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser.CONFIGURATION_FORMAT_VERSION_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.AMEND_LIBRARY_METHOD_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.API_LEVEL_BELOW_OR_EQUAL_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.API_LEVEL_GREATER_OR_EQUAL_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.BACKPORT_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.COMMON_FLAGS_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.CURRENT_HUMAN_CONFIGURATION_FORMAT_VERSION;
@@ -35,10 +36,9 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.ApiLevelRange;
 import com.google.gson.Gson;
-import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import java.util.ArrayList;
-import java.util.Comparator;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -81,14 +81,17 @@
   }
 
   private List<Object> rewritingFlagsToString(
-      Int2ObjectMap<HumanRewritingFlags> rewritingFlagsMap) {
+      Map<ApiLevelRange, HumanRewritingFlags> rewritingFlagsMap) {
     ArrayList<Object> list = new ArrayList<>();
-    ArrayList<Integer> apis = new ArrayList<>(rewritingFlagsMap.keySet());
-    apis.sort(Comparator.reverseOrder());
-    for (int apiBelowOrEqual : apis) {
-      HumanRewritingFlags flags = rewritingFlagsMap.get(apiBelowOrEqual);
+    ArrayList<ApiLevelRange> apis = new ArrayList<>(rewritingFlagsMap.keySet());
+    apis.sort((x, y) -> -x.deterministicOrder(y));
+    for (ApiLevelRange range : apis) {
+      HumanRewritingFlags flags = rewritingFlagsMap.get(range);
       HashMap<String, Object> toJson = new LinkedHashMap<>();
-      toJson.put(API_LEVEL_BELOW_OR_EQUAL_KEY, apiBelowOrEqual);
+      toJson.put(API_LEVEL_BELOW_OR_EQUAL_KEY, range.getApiLevelBelowOrEqualAsInt());
+      if (range.hasApiLevelGreaterOrEqual()) {
+        toJson.put(API_LEVEL_GREATER_OR_EQUAL_KEY, range.getApiLevelGreaterOrEqualAsInt());
+      }
       if (!flags.getRewritePrefix().isEmpty()) {
         toJson.put(REWRITE_PREFIX_KEY, new TreeMap<>(flags.getRewritePrefix()));
       }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationParser.java
index 9f9fa81..2eab9cf 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationParser.java
@@ -6,11 +6,12 @@
 
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.ApiLevelRange;
 import com.android.tools.r8.utils.Reporter;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
-import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
-import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import java.util.HashMap;
+import java.util.Map;
 
 public class MultiAPILevelHumanDesugaredLibrarySpecificationParser
     extends HumanDesugaredLibrarySpecificationParser {
@@ -27,26 +28,31 @@
 
     HumanTopLevelFlags topLevelFlags = parseTopLevelFlags(jsonConfigString, builder -> {});
 
-    Int2ObjectMap<HumanRewritingFlags> commonFlags = parseAllFlags(COMMON_FLAGS_KEY);
-    Int2ObjectMap<HumanRewritingFlags> libraryFlags = parseAllFlags(LIBRARY_FLAGS_KEY);
-    Int2ObjectMap<HumanRewritingFlags> programFlags = parseAllFlags(PROGRAM_FLAGS_KEY);
+    Map<ApiLevelRange, HumanRewritingFlags> commonFlags = parseAllFlags(COMMON_FLAGS_KEY);
+    Map<ApiLevelRange, HumanRewritingFlags> libraryFlags = parseAllFlags(LIBRARY_FLAGS_KEY);
+    Map<ApiLevelRange, HumanRewritingFlags> programFlags = parseAllFlags(PROGRAM_FLAGS_KEY);
 
     return new MultiAPILevelHumanDesugaredLibrarySpecification(
         getOrigin(), topLevelFlags, commonFlags, libraryFlags, programFlags);
   }
 
-  private Int2ObjectMap<HumanRewritingFlags> parseAllFlags(String flagKey) {
+  private Map<ApiLevelRange, HumanRewritingFlags> parseAllFlags(String flagKey) {
     JsonElement jsonFlags = required(getJsonConfig(), flagKey);
-    Int2ObjectMap<HumanRewritingFlags> flags = new Int2ObjectArrayMap<>();
+    Map<ApiLevelRange, HumanRewritingFlags> flags = new HashMap<>();
     for (JsonElement jsonFlagSet : jsonFlags.getAsJsonArray()) {
       JsonObject flag = jsonFlagSet.getAsJsonObject();
-      int api_level_below_or_equal = required(flag, API_LEVEL_BELOW_OR_EQUAL_KEY).getAsInt();
+      int apiLevelBelowOrEqual = required(flag, API_LEVEL_BELOW_OR_EQUAL_KEY).getAsInt();
+      ApiLevelRange range =
+          flag.has(API_LEVEL_GREATER_OR_EQUAL_KEY)
+              ? new ApiLevelRange(
+                  apiLevelBelowOrEqual, flag.get(API_LEVEL_GREATER_OR_EQUAL_KEY).getAsInt())
+              : new ApiLevelRange(apiLevelBelowOrEqual);
       HumanRewritingFlags.Builder builder =
-          flags.containsKey(api_level_below_or_equal)
-              ? flags.get(api_level_below_or_equal).newBuilder(reporter(), getOrigin())
+          flags.containsKey(range)
+              ? flags.get(range).newBuilder(reporter(), getOrigin())
               : HumanRewritingFlags.builder(reporter(), getOrigin());
       parseFlags(flag, builder);
-      flags.put(api_level_below_or_equal, builder.build());
+      flags.put(range, builder.build());
     }
     return flags;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeter.java
index a929fb9..8213473 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeter.java
@@ -175,7 +175,7 @@
     DexMethod invokedMethod = cfInvoke.getMethod();
     AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethod(invokedMethod, cfInvoke.isInterface());
+        appInfo.resolveMethodLegacy(invokedMethod, cfInvoke.isInterface());
     if (!resolutionResult.isSingleResolution()) {
       return NO_REWRITING;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterL8Synthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterL8Synthesizer.java
index 6a32412..4ef9c00 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterL8Synthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterL8Synthesizer.java
@@ -35,7 +35,7 @@
 
   @Override
   public String uniqueIdentifier() {
-    return "$retargeter$";
+    return "$retargeter";
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
index e1447fb..bfbf6a8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
@@ -153,7 +153,7 @@
     DexMethod forwardMethod = syntheticHelper.forwardingMethod(descriptor);
     assert forwardMethod != null && forwardMethod != target;
     DexEncodedMethod resolvedMethod =
-        appView.appInfoForDesugaring().resolveMethod(target, true).getResolvedMethod();
+        appView.appInfoForDesugaring().resolveMethodLegacy(target, true).getResolvedMethod();
     assert resolvedMethod != null;
     DexEncodedMethod desugaringForwardingMethod =
         DexEncodedMethod.createDesugaringForwardingMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
index 78fc59f..8cb5743 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
@@ -27,6 +27,7 @@
   private final boolean libraryCompilation;
   private final Map<DexString, DexString> descriptorPrefix;
   private final Set<DexString> descriptorMaintainPrefix;
+  private final Set<DexString> descriptorDontRewritePrefix;
   private final Map<DexString, Map<DexString, DexString>> descriptorDifferentPrefix;
   private final Set<DexString> usedPrefix = Sets.newIdentityHashSet();
 
@@ -40,7 +41,8 @@
     this.synthesizedPrefix = humanSpec.getSynthesizedLibraryClassesPackagePrefix();
     this.libraryCompilation = humanSpec.isLibraryCompilation();
     this.descriptorPrefix = convertRewritePrefix(rewritingFlags.getRewritePrefix());
-    this.descriptorMaintainPrefix = convertMaintainPrefix(rewritingFlags.getMaintainPrefix());
+    this.descriptorDontRewritePrefix = convertPrefixSet(rewritingFlags.getDontRewritePrefix());
+    this.descriptorMaintainPrefix = convertPrefixSet(rewritingFlags.getMaintainPrefix());
     this.descriptorDifferentPrefix =
         convertRewriteDifferentPrefix(rewritingFlags.getRewriteDerivedPrefix());
   }
@@ -102,6 +104,11 @@
   }
 
   private void registerClassType(DexType type) {
+    // TODO(b/222647019): To remove, the problem is that the prefix java.nio.channels.FileChannel
+    //  matches java.nio.channels.FileChannel$MapMode.
+    if (type.toString().equals("java.nio.channels.FileChannel$MapMode")) {
+      return;
+    }
     registerType(type);
     registerMaintainType(type);
     registerDifferentType(type);
@@ -110,6 +117,9 @@
   private void registerType(DexType type) {
     DexType rewrittenType = rewrittenType(type);
     if (rewrittenType != null) {
+      if (prefixMatching(type, descriptorDontRewritePrefix) != null) {
+        return;
+      }
       builder.rewriteType(type, rewrittenType);
     }
   }
@@ -174,7 +184,7 @@
     return mapBuilder.build();
   }
 
-  private ImmutableSet<DexString> convertMaintainPrefix(Set<String> maintainPrefix) {
+  private ImmutableSet<DexString> convertPrefixSet(Set<String> maintainPrefix) {
     ImmutableSet.Builder<DexString> builder = ImmutableSet.builder();
     for (String prefix : maintainPrefix) {
       builder.add(toDescriptorPrefix(prefix));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineWrapperConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineWrapperConverter.java
index 9487210..18147b5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineWrapperConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineWrapperConverter.java
@@ -12,9 +12,12 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
@@ -22,6 +25,7 @@
 
 public class HumanToMachineWrapperConverter {
 
+  private final MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
   private final AppInfoWithClassHierarchy appInfo;
   private final Set<DexType> missingClasses = Sets.newIdentityHashSet();
 
@@ -56,6 +60,10 @@
 
   private List<DexMethod> allImplementedMethods(
       DexClass wrapperClass, Set<DexMethod> excludedMethods) {
+    HashSet<Wrapper<DexMethod>> wrappers = new HashSet<>();
+    for (DexMethod excludedMethod : excludedMethods) {
+      wrappers.add(equivalence.wrap(excludedMethod));
+    }
     LinkedList<DexClass> workList = new LinkedList<>();
     List<DexMethod> implementedMethods = new ArrayList<>();
     workList.add(wrapperClass);
@@ -64,7 +72,7 @@
       for (DexEncodedMethod virtualMethod : dexClass.virtualMethods()) {
         if (!virtualMethod.isPrivateMethod()) {
           assert virtualMethod.isProtectedMethod() || virtualMethod.isPublicMethod();
-          boolean alreadyAdded = excludedMethods.contains(virtualMethod.getReference());
+          boolean alreadyAdded = wrappers.contains(equivalence.wrap(virtualMethod.getReference()));
           // This looks quadratic but given the size of the collections met in practice for
           // desugared libraries (Max ~15) it does not matter.
           if (!alreadyAdded) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
index bd26b562..baa4178 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.ApiLevelRange;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanTopLevelFlags;
@@ -32,22 +33,27 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 public class LegacyToHumanSpecificationConverter {
 
   private static final String WRAPPER_PREFIX = "__wrapper__.";
   private static final AndroidApiLevel LEGACY_HACK_LEVEL = AndroidApiLevel.N_MR1;
   private final Timing timing;
+  private final Set<String> missingClasses = new HashSet<>();
+  private final Set<String> missingMethods = new HashSet<>();
 
   public LegacyToHumanSpecificationConverter(Timing timing) {
     this.timing = timing;
@@ -81,14 +87,15 @@
         AppForSpecConversion.readAppForTesting(desugaredJDKLib, androidLib, options, true, timing);
 
     HumanTopLevelFlags humanTopLevelFlags = convertTopLevelFlags(legacySpec.getTopLevelFlags());
-    Int2ObjectArrayMap<HumanRewritingFlags> commonFlags =
+    Map<ApiLevelRange, HumanRewritingFlags> commonFlags =
         convertRewritingFlagMap(legacySpec.getCommonFlags(), app, origin);
-    Int2ObjectArrayMap<HumanRewritingFlags> programFlags =
+    Map<ApiLevelRange, HumanRewritingFlags> programFlags =
         convertRewritingFlagMap(legacySpec.getProgramFlags(), app, origin);
-    Int2ObjectArrayMap<HumanRewritingFlags> libraryFlags =
+    Map<ApiLevelRange, HumanRewritingFlags> libraryFlags =
         convertRewritingFlagMap(legacySpec.getLibraryFlags(), app, origin);
 
     legacyLibraryFlagHacks(libraryFlags, app, origin);
+    reportWarnings(app.options.reporter);
 
     MultiAPILevelHumanDesugaredLibrarySpecification humanSpec =
         new MultiAPILevelHumanDesugaredLibrarySpecification(
@@ -135,16 +142,34 @@
       humanRewritingFlags = builder.build();
       timing.end();
     }
-
+    reportWarnings(app.options.reporter);
     timing.end();
     return new HumanDesugaredLibrarySpecification(
         humanTopLevelFlags, humanRewritingFlags, legacySpec.isLibraryCompilation());
   }
 
+  private void reportWarnings(Reporter reporter) {
+    String errorSdk = "This usually means that the compilation SDK is absent or too old.";
+    if (!missingClasses.isEmpty()) {
+      reporter.warning(
+          "Cannot retarget core lib member for missing classes: "
+              + missingClasses
+              + ". "
+              + errorSdk);
+    }
+    if (!missingMethods.isEmpty()) {
+      reporter.warning(
+          "Should have found a method (library specifications) for "
+              + missingMethods
+              + ". "
+              + errorSdk);
+    }
+  }
+
   private void legacyLibraryFlagHacks(
-      Int2ObjectArrayMap<HumanRewritingFlags> libraryFlags, DexApplication app, Origin origin) {
-    int level = LEGACY_HACK_LEVEL.getLevel();
-    HumanRewritingFlags humanRewritingFlags = libraryFlags.get(level);
+      Map<ApiLevelRange, HumanRewritingFlags> libraryFlags, DexApplication app, Origin origin) {
+    ApiLevelRange range = new ApiLevelRange(LEGACY_HACK_LEVEL.getLevel());
+    HumanRewritingFlags humanRewritingFlags = libraryFlags.get(range);
     if (humanRewritingFlags == null) {
       // Skip CHM only configuration.
       return;
@@ -152,7 +177,7 @@
     HumanRewritingFlags.Builder builder =
         humanRewritingFlags.newBuilder(app.options.reporter, origin);
     legacyLibraryFlagHacks(app.dexItemFactory(), builder);
-    libraryFlags.put(level, builder.build());
+    libraryFlags.put(range, builder.build());
   }
 
   private void legacyLibraryFlagHacks(
@@ -186,10 +211,11 @@
     builder.retargetMethod(source, target);
   }
 
-  private Int2ObjectArrayMap<HumanRewritingFlags> convertRewritingFlagMap(
+  private Map<ApiLevelRange, HumanRewritingFlags> convertRewritingFlagMap(
       Int2ObjectMap<LegacyRewritingFlags> libFlags, DexApplication app, Origin origin) {
-    Int2ObjectArrayMap<HumanRewritingFlags> map = new Int2ObjectArrayMap<>();
-    libFlags.forEach((key, flags) -> map.put((int) key, convertRewritingFlags(flags, app, origin)));
+    Map<ApiLevelRange, HumanRewritingFlags> map = new HashMap<>();
+    libFlags.forEach(
+        (key, flags) -> map.put(new ApiLevelRange(key), convertRewritingFlags(flags, app, origin)));
     return map;
   }
 
@@ -257,7 +283,11 @@
     typeMap.forEach(
         (type, rewrittenType) -> {
           DexClass dexClass = app.definitionFor(type);
-          assert dexClass != null;
+          if (dexClass == null) {
+            assert false : "Cannot retarget core lib member for missing class " + type;
+            missingClasses.add(type.toSourceString());
+            return;
+          }
           List<DexClassAndMethod> methodsWithName =
               findMethodsWithName(name, dexClass, builder, app);
           for (DexClassAndMethod dexClassAndMethod : methodsWithName) {
@@ -297,12 +327,11 @@
           DexEncodedMethod.builder().setMethod(method).setAccessFlags(flags).build();
       return ImmutableList.of(DexClassAndMethod.create(clazz, build));
     }
-    assert !found.isEmpty()
-        : "Should have found a method (library specifications) for "
-            + clazz.toSourceString()
-            + "."
-            + methodName
-            + ". Maybe the library used for the compilation should be newer.";
+    if (found.isEmpty()) {
+      String warning = clazz.toSourceString() + "." + methodName;
+      assert false : "Should have found a method (library specifications) for " + warning;
+      missingMethods.add(warning);
+    }
     return found;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java
index 5c89290..9630887 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java
@@ -70,7 +70,7 @@
       CfInvoke invoke = instruction.asInvoke();
       DexMethod invokedMethod = invoke.getMethod();
       MethodResolutionResult resolutionResult =
-          appView.appInfo().resolveMethod(invokedMethod, invoke.isInterface());
+          appView.appInfo().resolveMethodLegacy(invokedMethod, invoke.isInterface());
       if (shouldRewriteInvokeToThrow(invoke, resolutionResult)) {
         return computeInvokeAsThrowRewrite(appView, invoke, resolutionResult);
       }
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 266b1c5..0806685 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
@@ -646,7 +646,7 @@
     AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
     for (Wrapper<DexMethod> signature : emulatedInterfaceInfo.signatures.signatures) {
       MethodResolutionResult resolutionResult =
-          appInfo.resolveMethodOnClass(clazz, signature.get());
+          appInfo.resolveMethodOnClassLegacy(clazz, signature.get());
       if (resolutionResult.isFailedResolution()) {
         return true;
       }
@@ -685,7 +685,7 @@
   private void resolveForwardForSignature(
       DexClass clazz, DexMethod method, BiConsumer<DexClassAndMethod, DexMethod> addForward) {
     AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOn(clazz, method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnLegacy(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
@@ -702,7 +702,7 @@
             resolutionResult.asSuccessfulMemberResolutionResult().getResolvedMember().isStatic());
       }
       if (staticTarget.isAssigned() && staticTarget.isTrue()) {
-        resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+        resolutionResult = appInfo.resolveMethodOnInterfaceLegacy(method.holder, method);
       }
       if (resolutionResult.isFailedResolution()) {
         if (resolutionResult.isIncompatibleClassChangeErrorResult()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
index 3919202..eda530c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
@@ -263,7 +263,10 @@
     }
     assert verifyKind(method, kinds -> kinds.COMPANION_CLASS);
     DexClassAndMethod resolvedMethod =
-        appView.appInfoForDesugaring().resolveMethod(method.getMethod(), true).getResolutionPair();
+        appView
+            .appInfoForDesugaring()
+            .resolveMethodLegacy(method.getMethod(), true)
+            .getResolutionPair();
     return ensureDefaultAsMethodOfCompanionClassStub(resolvedMethod).getReference();
   }
 
@@ -274,7 +277,7 @@
     DexClassAndMethod method =
         appView
             .appInfoForDesugaring()
-            .resolveMethod(emulatedDispatchMethod.getMethod(), true)
+            .resolveMethodLegacy(emulatedDispatchMethod.getMethod(), true)
             .getResolutionPair();
     assert verifyKind(emulatedDispatchMethod, kinds -> kinds.EMULATED_INTERFACE_CLASS);
     if (method.isProgramMethod()) {
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 7b3bb07..bc8ebd0 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
@@ -307,7 +307,7 @@
     // would change behavior from throwing ICCE to dispatching to the companion class method.
     AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
     MethodResolutionResult resolution =
-        appInfo.resolveMethod(invoke.getMethod(), invoke.isInterface());
+        appInfo.resolveMethodLegacy(invoke.getMethod(), invoke.isInterface());
     if (!resolution.isSingleResolution()
         || !resolution.asSingleResolution().getResolvedMethod().isStatic()) {
       return DesugarDescription.nothing();
@@ -408,7 +408,7 @@
     SingleResolutionResult<?> resolutionResult =
         appView
             .appInfoForDesugaring()
-            .resolveMethodOnInterface(holder, invoke.getMethod())
+            .resolveMethodOnInterfaceLegacy(holder, invoke.getMethod())
             .asSingleResolution();
     if (holder.isInterface() && shouldRewriteToInvokeToThrow(resolutionResult, true)) {
       return computeInvokeAsThrowRewrite(invoke, resolutionResult, context);
@@ -437,7 +437,7 @@
     AppInfoWithClassHierarchy appInfoForDesugaring = appView.appInfoForDesugaring();
     SingleResolutionResult<?> resolution =
         appInfoForDesugaring
-            .resolveMethod(invoke.getMethod(), invoke.isInterface())
+            .resolveMethodLegacy(invoke.getMethod(), invoke.isInterface())
             .asSingleResolution();
     if (resolution != null
         && resolution.getResolvedMethod().isPrivate()
@@ -454,7 +454,9 @@
 
   private DesugarDescription computeEmulatedInterfaceVirtualDispatchOrNull(CfInvoke invoke) {
     MethodResolutionResult resolutionResult =
-        appView.appInfoForDesugaring().resolveMethod(invoke.getMethod(), invoke.isInterface());
+        appView
+            .appInfoForDesugaring()
+            .resolveMethodLegacy(invoke.getMethod(), invoke.isInterface());
     DerivedMethod emulatedDispatchMethod =
         helper.computeEmulatedInterfaceDispatchMethod(resolutionResult);
     if (emulatedDispatchMethod == null) {
@@ -492,7 +494,7 @@
     }
 
     MethodResolutionResult resolution =
-        appView.appInfoForDesugaring().resolveMethod(invokedMethod, invoke.isInterface());
+        appView.appInfoForDesugaring().resolveMethodLegacy(invokedMethod, invoke.isInterface());
     if (resolution.isFailedResolution()) {
       return computeInvokeAsThrowRewrite(invoke, null, context);
     }
@@ -638,7 +640,10 @@
     }
 
     SingleResolutionResult<?> resolutionResult =
-        appView.appInfoForDesugaring().resolveMethodOn(clazz, invokedMethod).asSingleResolution();
+        appView
+            .appInfoForDesugaring()
+            .resolveMethodOnLegacy(clazz, invokedMethod)
+            .asSingleResolution();
     if (clazz.isInterface() && shouldRewriteToInvokeToThrow(resolutionResult, false)) {
       return computeInvokeAsThrowRewrite(invoke, resolutionResult, context);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
index b8b50d9..6a57dae 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
@@ -114,7 +114,7 @@
 
   @Override
   public String uniqueIdentifier() {
-    return "$emulatedInterface$";
+    return "$emulatedInterface";
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
index 6a84f09..366ee34 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
@@ -476,7 +476,7 @@
 
   @Override
   public String uniqueIdentifier() {
-    return "$record$";
+    return "$record";
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
index 72b8d6a..1961b30 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
@@ -32,12 +32,14 @@
 import com.android.tools.r8.utils.LazyBox;
 import com.android.tools.r8.utils.ThrowingCharIterator;
 import com.android.tools.r8.utils.Timing;
-import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap;
 import java.io.UTFDataFormatException;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 public class AssertionsRewriter {
@@ -274,6 +276,7 @@
    *   }
    * }
    * </pre>
+   *
    * For Kotlin the Class instance method desiredAssertionStatus() is only called for the class
    * kotlin._Assertions, where kotlin._Assertions.class.desiredAssertionStatus() is read into the
    * static field kotlin._Assertions.ENABLED.
@@ -311,7 +314,8 @@
    * }
    * </pre>
    *
-   * With the rewriting below and AssertionTransformation.DISABLE (and other rewritings) the resulting code is:
+   * With the rewriting below and AssertionTransformation.DISABLE (and other rewritings) the
+   * resulting code is:
    *
    * <pre>
    * class A {
@@ -332,23 +336,26 @@
    *   }
    * }
    * </pre>
-
+   *
    * NOTE: that in Kotlin the assertion condition is always calculated. So it is still present in
    * the code and even for AssertionTransformation.DISABLE.
    */
-  public void run(DexEncodedMethod method, IRCode code, Timing timing) {
+  public void run(
+      DexEncodedMethod method, IRCode code, DeadCodeRemover deadCodeRemover, Timing timing) {
     if (enabled) {
       timing.begin("Rewrite assertions");
-      runInternal(method, code);
+      if (runInternal(method, code)) {
+        deadCodeRemover.run(code, timing);
+      }
       assert code.isConsistentSSA(appView);
       timing.end();
     }
   }
 
-  private void runInternal(DexEncodedMethod method, IRCode code) {
+  private boolean runInternal(DexEncodedMethod method, IRCode code) {
     ConfigurationEntryWithDexString configuration = getTransformationForMethod(method);
     if (configuration.isPassthrough()) {
-      return;
+      return false;
     }
     DexEncodedMethod clinit;
     // If the <clinit> of this class did not have have code to turn on assertions don't try to
@@ -358,20 +365,25 @@
     } else {
       DexClass clazz = appView.definitionFor(method.getHolderType());
       if (clazz == null) {
-        return;
+        return false;
       }
       clinit = clazz.getClassInitializer();
     }
     // For the transformation to rewrite the throw with a callback collect information on the
     // blocks covered by the if (!$assertionsDisabled or ENABLED) condition together with weather
     // the assertion handling is on the true or false branch.
-    Map<If, Boolean> assertionEntryIfs = new IdentityHashMap<>();
+    Map<If, Boolean> assertionEntryIfs = new Reference2BooleanOpenHashMap<>();
     Map<Throw, BasicBlock> throwSuccessorAfterHandler = new IdentityHashMap<>();
+    Set<BasicBlock> assertionBlocks = Sets.newIdentityHashSet();
+    Map<If, Boolean> additionalAssertionsEnabledIfs = new Reference2BooleanOpenHashMap<>();
     if (configuration.isAssertionHandler()) {
       LazyBox<DominatorTree> dominatorTree = new LazyBox<>(() -> new DominatorTree(code));
       code.getBlocks()
           .forEach(
               basicBlock -> {
+                if (assertionBlocks.contains(basicBlock)) {
+                  return;
+                }
                 If theIf = isCheckAssertionsEnabledBlock(basicBlock);
                 if (theIf != null) {
                   // All blocks dominated by the if is the assertion code. For Java it is on the
@@ -382,25 +394,42 @@
                           theIf.lhs().getDefinition().asStaticGet());
                   BasicBlock assertionBlockEntry =
                       theIf.targetFromBoolean(conditionForAssertionBlock);
-                  List<BasicBlock> blocks =
+                  List<BasicBlock> dominatedBlocks =
                       dominatorTree.computeIfAbsent().dominatedBlocks(assertionBlockEntry);
-                  Throw throwInstruction = isAlwaysThrowingEntry(assertionBlockEntry, blocks);
+                  Throw throwInstruction =
+                      dominatedBlocksHasSingleThrow(assertionBlockEntry, dominatedBlocks);
                   if (throwInstruction != null) {
                     assertionEntryIfs.put(theIf, conditionForAssertionBlock);
                     throwSuccessorAfterHandler.put(
                         throwInstruction, theIf.targetFromBoolean(!conditionForAssertionBlock));
+                    // Collect any additional assertions enabled checks dominated by the current
+                    // assertions entry check.
+                    dominatedBlocks.forEach(
+                        block -> {
+                          If additionalAssertionsEnabledIf = isCheckAssertionsEnabledBlock(block);
+                          if (additionalAssertionsEnabledIf != null) {
+                            additionalAssertionsEnabledIfs.put(
+                                additionalAssertionsEnabledIf,
+                                !isUsingJavaAssertionsDisabledField(
+                                    additionalAssertionsEnabledIf
+                                        .lhs()
+                                        .getDefinition()
+                                        .asStaticGet()));
+                          }
+                        });
+                    assertionBlocks.addAll(dominatedBlocks);
                   }
                 }
               });
     }
     assert assertionEntryIfs.size() == throwSuccessorAfterHandler.size();
-
     // For javac generated code it is assumed that the code in <clinit> will tell if the code
     // in other methods of the class can have assertion checks.
     boolean isInitializerEnablingJavaVmAssertions =
         clinit != null && clinit.getOptimizationInfo().isInitializerEnablingJavaVmAssertions();
     // This code will process the assertion code in all methods including <clinit>.
     InstructionListIterator iterator = code.instructionListIterator();
+    boolean needsDeadCodeRemoval = false;
     while (iterator.hasNext()) {
       Instruction current = iterator.next();
       if (current.isInvokeMethod()) {
@@ -446,11 +475,12 @@
         if (current.isIf()) {
           If ifInstruction = current.asIf();
           if (assertionEntryIfs.containsKey(ifInstruction)) {
-            ifInstruction
-                .targetFromBoolean(!assertionEntryIfs.get(ifInstruction))
-                .unlinkSinglePredecessorSiblingsAllowed();
-            ifInstruction.lhs().removeUser(ifInstruction);
-            iterator.replaceCurrentInstruction(new Goto());
+            forceAssertionsEnabled(ifInstruction, assertionEntryIfs, iterator);
+            needsDeadCodeRemoval = true;
+          }
+          if (additionalAssertionsEnabledIfs.containsKey(ifInstruction)) {
+            forceAssertionsEnabled(ifInstruction, additionalAssertionsEnabledIfs, iterator);
+            needsDeadCodeRemoval = true;
           }
         } else if (current.isThrow()) {
           Throw throwInstruction = current.asThrow();
@@ -469,6 +499,7 @@
         }
       }
     }
+    return needsDeadCodeRemoval;
   }
 
   private void rewriteKotlinAssertionEnable(
@@ -541,15 +572,9 @@
         : null;
   }
 
-  private Throw isAlwaysThrowingEntry(BasicBlock block, List<BasicBlock> blocks) {
-    WorkList<BasicBlock> workList = WorkList.newIdentityWorkList(block);
+  private Throw dominatedBlocksHasSingleThrow(BasicBlock block, List<BasicBlock> dominatedBlocks) {
     Throw theThrow = null;
-    while (workList.hasNext()) {
-      BasicBlock current = workList.next();
-      workList.addIfNotSeen(current.getNormalSuccessors());
-      if (!blocks.containsAll(current.getNormalSuccessors())) {
-        return null;
-      }
+    for (BasicBlock current : dominatedBlocks) {
       if (current.exit().isReturn()) {
         return null;
       }
@@ -562,4 +587,13 @@
     }
     return theThrow;
   }
+
+  private void forceAssertionsEnabled(
+      If ifInstruction, Map<If, Boolean> targetMap, InstructionListIterator iterator) {
+    ifInstruction
+        .targetFromBoolean(!targetMap.get(ifInstruction))
+        .unlinkSinglePredecessorSiblingsAllowed();
+    ifInstruction.lhs().removeUser(ifInstruction);
+    iterator.replaceCurrentInstruction(new Goto());
+  }
 }
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 1c49fc9..df9d508 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
@@ -230,7 +230,7 @@
     SingleResolutionResult<?> resolutionResult =
         appView
             .appInfo()
-            .unsafeResolveMethodDueToDexFormat(invoke.getInvokedMethod())
+            .unsafeResolveMethodDueToDexFormatLegacy(invoke.getInvokedMethod())
             .asSingleResolution();
     if (resolutionResult == null) {
       return false;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 86578a2..3d9d2b8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -98,11 +98,11 @@
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOutputMode;
+import com.android.tools.r8.utils.LazyBox;
 import com.android.tools.r8.utils.LongInterval;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.base.Suppliers;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -145,7 +145,6 @@
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
-import java.util.function.Supplier;
 
 public class CodeRewriter {
 
@@ -1838,8 +1837,7 @@
     // and ConstStrings with one user.
     // TODO(ager): Generalize this to shorten live ranges for more instructions? Currently
     // doing so seems to make things worse.
-    Supplier<DominatorTree> dominatorTreeMemoization =
-        Suppliers.memoize(() -> new DominatorTree(code));
+    LazyBox<DominatorTree> dominatorTreeMemoization = new LazyBox<>(() -> new DominatorTree(code));
     Map<BasicBlock, Map<Value, Instruction>> addConstantInBlock = new IdentityHashMap<>();
     LinkedList<BasicBlock> blocks = code.blocks;
     for (BasicBlock block : blocks) {
@@ -1945,7 +1943,7 @@
   private void shortenLiveRangesInsideBlock(
       IRCode code,
       BasicBlock block,
-      Supplier<DominatorTree> dominatorTreeMemoization,
+      LazyBox<DominatorTree> dominatorTreeMemoization,
       Map<BasicBlock, Map<Value, Instruction>> addConstantInBlock,
       Predicate<ConstInstruction> selector) {
     InstructionListIterator iterator = block.listIterator(code);
@@ -2014,7 +2012,7 @@
         userBlocks.add(phi.getBlock());
       }
       // Locate the closest dominator block for all user blocks.
-      DominatorTree dominatorTree = dominatorTreeMemoization.get();
+      DominatorTree dominatorTree = dominatorTreeMemoization.computeIfAbsent();
       BasicBlock dominator = dominatorTree.closestDominator(userBlocks);
       // If the closest dominator block is a block that uses the constant for a phi the constant
       // needs to go in the immediate dominator block so that it is available for phi moves.
@@ -2816,9 +2814,9 @@
       return;
     }
 
-    Supplier<Long2ReferenceMap<List<ConstNumber>>> constantsByValue =
-        Suppliers.memoize(() -> getConstantsByValue(code));
-    Supplier<DominatorTree> dominatorTree = Suppliers.memoize(() -> new DominatorTree(code));
+    LazyBox<Long2ReferenceMap<List<ConstNumber>>> constantsByValue =
+        new LazyBox<>(() -> getConstantsByValue(code));
+    LazyBox<DominatorTree> dominatorTree = new LazyBox<>(() -> new DominatorTree(code));
 
     boolean changed = false;
     for (BasicBlock block : code.blocks) {
@@ -2908,7 +2906,7 @@
         }
       }
 
-      if (constantsByValue.get().isEmpty()) {
+      if (constantsByValue.computeIfAbsent().isEmpty()) {
         break;
       }
     }
@@ -2954,16 +2952,17 @@
       long withValue,
       Value newValue,
       BasicBlock dominator,
-      Supplier<Long2ReferenceMap<List<ConstNumber>>> constantsByValueSupplier,
+      LazyBox<Long2ReferenceMap<List<ConstNumber>>> constantsByValueSupplier,
       IRCode code,
-      Supplier<DominatorTree> dominatorTree) {
+      LazyBox<DominatorTree> dominatorTree) {
     if (newValue.hasLocalInfo()) {
       // We cannot replace a constant with a value that has local info, because that could change
       // debugging behavior.
       return false;
     }
 
-    Long2ReferenceMap<List<ConstNumber>> constantsByValue = constantsByValueSupplier.get();
+    Long2ReferenceMap<List<ConstNumber>> constantsByValue =
+        constantsByValueSupplier.computeIfAbsent();
     List<ConstNumber> constantsWithValue = constantsByValue.get(withValue);
     if (constantsWithValue == null || constantsWithValue.isEmpty()) {
       return false;
@@ -3003,7 +3002,7 @@
         continue;
       }
 
-      if (dominatorTree.get().dominatedBy(block, dominator)) {
+      if (dominatorTree.computeIfAbsent().dominatedBy(block, dominator)) {
         if (newValue.getType().lessThanOrEqual(value.getType(), appView)) {
           value.replaceUsers(newValue);
           block.listIterator(code, constNumber).removeOrReplaceByDebugLocalRead();
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 9ba2beb..5930cd3 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
@@ -369,7 +369,10 @@
     }
 
     SingleResolutionResult<?> resolutionResult =
-        appView.appInfo().resolveMethodOnClass(target.getHolderType(), target).asSingleResolution();
+        appView
+            .appInfo()
+            .resolveMethodOnClassLegacy(target.getHolderType(), target)
+            .asSingleResolution();
     if (resolutionResult == null
         || resolutionResult
             .isAccessibleForVirtualDispatchFrom(context, appView.appInfo())
@@ -385,7 +388,7 @@
     }
 
     SingleResolutionResult<?> newResolutionResult =
-        appView.appInfo().resolveMethodOnClass(receiverType, target).asSingleResolution();
+        appView.appInfo().resolveMethodOnClassLegacy(receiverType, target).asSingleResolution();
     if (newResolutionResult == null
         || newResolutionResult
             .isAccessibleForVirtualDispatchFrom(context, appView.appInfo())
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 265d097..257af5e 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
@@ -139,7 +139,7 @@
 
           SingleResolutionResult<?> resolutionResult =
               appInfoWithLiveness
-                  .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit())
+                  .resolveMethodLegacy(invoke.getInvokedMethod(), invoke.getInterfaceBit())
                   .asSingleResolution();
           if (resolutionResult == null
               || resolutionResult
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 4ca07c5..4be71a5 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
@@ -963,7 +963,7 @@
           SingleResolutionResult<?> resolutionResult =
               appView
                   .appInfo()
-                  .resolveMethod(invokedMethod, invoke.getInterfaceBit())
+                  .resolveMethodLegacy(invokedMethod, invoke.getInterfaceBit())
                   .asSingleResolution();
           if (resolutionResult == null
               || resolutionResult.isAccessibleFrom(context, appView).isPossiblyFalse()) {
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 4cf1c8c..59e06c4 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
@@ -175,7 +175,7 @@
       return ConstraintWithTarget.ALWAYS;
     }
     MethodResolutionResult resolutionResult =
-        appView.appInfo().unsafeResolveMethodDueToDexFormat(lookup);
+        appView.appInfo().unsafeResolveMethodDueToDexFormatLegacy(lookup);
     DexEncodedMethod target =
         singleTargetWhileVerticalClassMerging(
             resolutionResult, context, MethodResolutionResult::lookupInvokeDirectTarget);
@@ -207,7 +207,7 @@
       return ConstraintWithTarget.ALWAYS;
     }
     MethodResolutionResult resolutionResult =
-        appView.appInfo().unsafeResolveMethodDueToDexFormat(lookup);
+        appView.appInfo().unsafeResolveMethodDueToDexFormatLegacy(lookup);
     DexEncodedMethod target =
         singleTargetWhileVerticalClassMerging(
             resolutionResult, context, MethodResolutionResult::lookupInvokeStaticTarget);
@@ -364,7 +364,8 @@
 
     // Perform resolution and derive inlining constraints based on the accessibility of the
     // resolution result.
-    MethodResolutionResult resolutionResult = appView.appInfo().resolveMethod(method, isInterface);
+    MethodResolutionResult resolutionResult =
+        appView.appInfo().resolveMethodLegacy(method, isInterface);
     if (!resolutionResult.isVirtualTarget()) {
       return ConstraintWithTarget.NEVER;
     }
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 09b614e..d572d2f 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
@@ -290,7 +290,10 @@
     }
 
     SingleResolutionResult<?> resolutionResult =
-        appView.appInfo().unsafeResolveMethodDueToDexFormat(invokedMethod).asSingleResolution();
+        appView
+            .appInfo()
+            .unsafeResolveMethodDueToDexFormatLegacy(invokedMethod)
+            .asSingleResolution();
     if (resolutionResult == null) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java
index 1a35898..e111ec0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java
@@ -91,7 +91,7 @@
       SingleResolutionResult<?> resolutionResult =
           appView
               .appInfo()
-              .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit())
+              .resolveMethodLegacy(invoke.getInvokedMethod(), invoke.getInterfaceBit())
               .asSingleResolution();
       if (resolutionResult == null
           || resolutionResult.isAccessibleFrom(method, appView).isPossiblyFalse()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NaturalIntLoopRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/NaturalIntLoopRemover.java
index c82a122..e55a1b0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NaturalIntLoopRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NaturalIntLoopRemover.java
@@ -30,7 +30,7 @@
 public class NaturalIntLoopRemover {
 
   public void run(AppView<?> appView, IRCode code) {
-    if (!appView.testing().enableExperimentalLoopUnrolling) {
+    if (!appView.options().enableLoopUnrolling) {
       return;
     }
     boolean loopRemoved = false;
@@ -87,6 +87,9 @@
     if (!analyzeLoopExit(loopBody, comparison, builder)) {
       return false;
     }
+    if (!analyzePhiUses(loopBody, comparison, builder)) {
+      return false;
+    }
 
     NaturalIntLoopWithKnowIterations loop = builder.build();
 
@@ -98,6 +101,42 @@
   }
 
   /**
+   * The loop unroller removes phis corresponding to the loop backjump. There are three scenarios:
+   * (1) The loop has a single exit point analyzed, phis used outside the loop are replaced by the
+   *     value at the end of the loop body.
+   * (2) The phis are unused outside the loop, and they are simply removed.
+   * (3) The loop has multiple exits and the phis are used outside the loop, this would require
+   *     dealing with complex merge point and postponing phis after the loop, we bail out.
+   */
+  private boolean analyzePhiUses(
+      Set<BasicBlock> loopBody, If comparison, NaturalIntLoopWithKnowIterations.Builder builder) {
+    // Check for single exit scenario.
+    Set<BasicBlock> successors = Sets.newIdentityHashSet();
+    for (BasicBlock basicBlock : loopBody) {
+      successors.addAll(basicBlock.getSuccessors());
+    }
+    successors.removeAll(loopBody);
+    if (successors.size() == 1) {
+      assert successors.iterator().next() == builder.getLoopExit();
+      return true;
+    }
+    // Check phis are unused outside the loop.
+    for (Phi phi : comparison.getBlock().getPhis()) {
+      for (Instruction use : phi.uniqueUsers()) {
+        if (!loopBody.contains(use.getBlock())) {
+          return false;
+        }
+      }
+      for (Phi phiUse : phi.uniquePhiUsers()) {
+        if (!loopBody.contains(phiUse.getBlock())) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  /**
    * Verifies the loop is well formed: the comparison on the int iterator should jump to a loop exit
    * on one side and to the loop body on the other side.
    */
@@ -305,6 +344,10 @@
         this.loopBodyEntry = loopBodyEntry;
       }
 
+      public BasicBlock getLoopExit() {
+        return loopExit;
+      }
+
       public void setLoopBody(Set<BasicBlock> loopBody) {
         this.loopBody = loopBody;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
index 5966a8c..a3c270a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
@@ -512,7 +512,7 @@
           appView
               .appInfo()
               .withClassHierarchy()
-              .unsafeResolveMethodDueToDexFormat(invoke.getInvokedMethod())
+              .unsafeResolveMethodDueToDexFormatLegacy(invoke.getInvokedMethod())
               .getResolvedProgramMethod();
       if (resolvedMethod != null) {
         markClassAsInitialized(resolvedMethod.getHolderType());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java
index 20b26b0..afed4c2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java
@@ -23,13 +23,12 @@
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.base.Suppliers;
+import com.android.tools.r8.utils.LazyBox;
 import com.google.common.collect.ImmutableList;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
 import java.util.Collections;
 import java.util.ListIterator;
-import java.util.function.Supplier;
 
 public class RuntimeWorkaroundCodeRewriter {
 
@@ -87,8 +86,8 @@
       return;
     }
     DexItemFactory factory = options.itemFactory;
-    final Supplier<DexMethod> javaLangLangSignum =
-        Suppliers.memoize(
+    LazyBox<DexMethod> javaLangLangSignum =
+        new LazyBox<>(
             () ->
                 factory.createMethod(
                     factory.createString("Ljava/lang/Long;"),
@@ -118,7 +117,7 @@
           Value longValue = firstMaterializing.inValues().get(0);
           InvokeStatic invokeLongSignum =
               new InvokeStatic(
-                  javaLangLangSignum.get(), null, Collections.singletonList(longValue));
+                  javaLangLangSignum.computeIfAbsent(), null, Collections.singletonList(longValue));
           ensureThrowingInstructionBefore(code, firstMaterializing, it, invokeLongSignum);
           return;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SimpleDominatingEffectAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/SimpleDominatingEffectAnalysis.java
index 75e94fb..9027ee1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SimpleDominatingEffectAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SimpleDominatingEffectAnalysis.java
@@ -219,16 +219,20 @@
       result = ResultState.NOT_SATISFIED;
     }
 
-    public void addSatisfyingInstruction(Instruction instruction) {
+    public SimpleEffectAnalysisResultBuilder addSatisfyingInstruction(Instruction instruction) {
       satisfyingInstructions.add(instruction);
+      return this;
     }
 
-    public void setFailingBlocksForPartialResults(List<BasicBlock> basicBlocks) {
+    public SimpleEffectAnalysisResultBuilder setFailingBlocksForPartialResults(
+        List<BasicBlock> basicBlocks) {
       this.failingBlocksForPartialResults = basicBlocks;
+      return this;
     }
 
-    public void setResult(ResultState result) {
+    public SimpleEffectAnalysisResultBuilder setResult(ResultState result) {
       this.result = result;
+      return this;
     }
 
     public SimpleEffectAnalysisResult build() {
@@ -246,63 +250,69 @@
   public static SimpleEffectAnalysisResult run(IRCode code, InstructionAnalysis analysis) {
     SimpleEffectAnalysisResultBuilder builder = SimpleEffectAnalysisResult.builder();
     IntBox visitedInstructions = new IntBox();
-    new StatefulDepthFirstSearchWorkList<BasicBlock, ResultStateWithPartialBlocks>() {
+    TraversalContinuation<Void, ResultStateWithPartialBlocks> runResult =
+        new StatefulDepthFirstSearchWorkList<BasicBlock, ResultStateWithPartialBlocks, Void>() {
 
-      @Override
-      protected TraversalContinuation<?, ?> process(
-          DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks> node,
-          Function<BasicBlock, DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks>>
-              childNodeConsumer) {
-        InstructionEffect effect = NO_EFFECT;
-        for (Instruction instruction : node.getNode().getInstructions()) {
-          if (visitedInstructions.getAndIncrement() > analysis.maxNumberOfInstructions()) {
-            builder.fail();
-            return doBreak();
-          }
-          effect = analysis.analyze(instruction);
-          if (!effect.isNoEffect()) {
-            if (effect.isDesired()) {
-              builder.addSatisfyingInstruction(instruction);
+          @Override
+          protected TraversalContinuation<Void, ResultStateWithPartialBlocks> process(
+              DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks> node,
+              Function<BasicBlock, DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks>>
+                  childNodeConsumer) {
+            InstructionEffect effect = NO_EFFECT;
+            for (Instruction instruction : node.getNode().getInstructions()) {
+              if (visitedInstructions.getAndIncrement() > analysis.maxNumberOfInstructions()) {
+                return doBreak();
+              }
+              effect = analysis.analyze(instruction);
+              if (!effect.isNoEffect()) {
+                if (effect.isDesired()) {
+                  builder.addSatisfyingInstruction(instruction);
+                }
+                break;
+              }
             }
-            break;
-          }
-        }
-        if (effect.isNoEffect()) {
-          List<BasicBlock> successors = analysis.getSuccessors(node.getNode());
-          for (BasicBlock successor : successors) {
-            DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks> childNode =
-                childNodeConsumer.apply(successor);
-            if (childNode.hasState()) {
-              // If we see a block where the children have not been processed we cannot guarantee
-              // all paths having the effect since - ex. we could have a non-terminating loop.
-              builder.fail();
-              return doBreak();
+            if (effect.isNoEffect()) {
+              List<BasicBlock> successors = analysis.getSuccessors(node.getNode());
+              for (BasicBlock successor : successors) {
+                DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks> childNode =
+                    childNodeConsumer.apply(successor);
+                if (childNode.hasState()) {
+                  // If we see a block where the children have not been processed we cannot
+                  // guarantee all paths having the effect since - ex. we could have a
+                  // non-terminating loop.
+                  return doBreak();
+                }
+              }
             }
+            node.setState(
+                new ResultStateWithPartialBlocks(effect.toResultState(), ImmutableList.of()));
+            return doContinue();
           }
-        }
-        node.setState(new ResultStateWithPartialBlocks(effect.toResultState(), ImmutableList.of()));
-        return doContinue();
-      }
 
-      @Override
-      protected TraversalContinuation<?, ?> joiner(
-          DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks> node,
-          List<DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks>> childNodes) {
-        ResultStateWithPartialBlocks resultState = node.getState();
-        if (resultState.state.isNotComputed()) {
-          resultState = resultState.joinChildren(childNodes);
-        } else {
-          assert resultState.state.isSatisfied() || resultState.state.isNotSatisfied();
-          assert childNodes.isEmpty();
-        }
-        node.setState(resultState);
-        if (node.getNode().isEntry()) {
-          builder.setResult(resultState.state);
-          builder.setFailingBlocksForPartialResults(resultState.failingBlocks);
-        }
-        return doContinue();
-      }
-    }.run(code.entryBlock());
+          @Override
+          protected TraversalContinuation<Void, ResultStateWithPartialBlocks> joiner(
+              DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks> node,
+              List<DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks>> childNodes) {
+            ResultStateWithPartialBlocks resultState = node.getState();
+            if (resultState.state.isNotComputed()) {
+              resultState = resultState.joinChildren(childNodes);
+            } else {
+              assert resultState.state.isSatisfied() || resultState.state.isNotSatisfied();
+              assert childNodes.isEmpty();
+            }
+            node.setState(resultState);
+            return doContinue(resultState);
+          }
+        }.run(code.entryBlock());
+
+    if (runResult.isBreak()) {
+      builder.fail();
+    } else {
+      ResultStateWithPartialBlocks resultState = runResult.asContinue().getValue();
+      builder
+          .setResult(resultState.state)
+          .setFailingBlocksForPartialResults(resultState.failingBlocks);
+    }
 
     return builder.build();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index ccbd132..7607596 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -29,13 +29,13 @@
 import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
 import com.android.tools.r8.ir.optimize.string.StringOptimizer;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.LazyBox;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Supplier;
 
 public final class ClassInliner {
 
@@ -135,7 +135,7 @@
       MethodProcessor methodProcessor,
       MethodProcessingContext methodProcessingContext,
       Inliner inliner,
-      Supplier<InliningOracle> defaultOracle) {
+      LazyBox<InliningOracle> defaultOracle) {
 
     // Collect all the new-instance and static-get instructions in the code before inlining.
     List<Instruction> roots =
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 0b876a7..10cdd99 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
@@ -65,6 +65,7 @@
 import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
 import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.LazyBox;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.Timing;
@@ -79,7 +80,6 @@
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.function.Function;
-import java.util.function.Supplier;
 
 final class InlineCandidateProcessor {
 
@@ -218,7 +218,7 @@
    *
    * @return null if all users are eligible, or the first ineligible user.
    */
-  InstructionOrPhi areInstanceUsersEligible(Supplier<InliningOracle> defaultOracle) {
+  InstructionOrPhi areInstanceUsersEligible(LazyBox<InliningOracle> defaultOracle) {
     // No Phi users.
     if (eligibleInstance.hasPhiUsers()) {
       return eligibleInstance.firstPhiUser(); // Not eligible.
@@ -294,7 +294,7 @@
           SingleResolutionResult<?> resolutionResult =
               appView
                   .appInfo()
-                  .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit())
+                  .resolveMethodLegacy(invoke.getInvokedMethod(), invoke.getInterfaceBit())
                   .asSingleResolution();
           if (resolutionResult == null
               || resolutionResult.isAccessibleFrom(method, appView).isPossiblyFalse()) {
@@ -1028,7 +1028,7 @@
     // 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.
     MethodResolutionResult resolutionResult =
-        appView.appInfo().resolveMethodOnClass(eligibleClass, callee);
+        appView.appInfo().resolveMethodOnClassLegacy(eligibleClass, callee);
     if (resolutionResult.isSingleResolution()
         && !resolutionResult.getSingleTarget().isNonPrivateVirtualMethod()) {
       return false;
@@ -1073,7 +1073,7 @@
       InvokeMethod invoke,
       SingleResolutionResult<?> resolutionResult,
       ProgramMethod singleTarget,
-      Supplier<InliningOracle> defaultOracle,
+      LazyBox<InliningOracle> defaultOracle,
       Set<Instruction> indirectUsers) {
     if (!((invoke.isInvokeDirect() && !invoke.isInvokeConstructor(dexItemFactory))
         || invoke.isInvokeInterface()
@@ -1092,7 +1092,7 @@
     }
 
     // Check if the method is inline-able by standard inliner.
-    InliningOracle oracle = defaultOracle.get();
+    InliningOracle oracle = defaultOracle.computeIfAbsent();
     if (!oracle.passesInliningConstraints(
         invoke,
         resolutionResult,
@@ -1176,7 +1176,10 @@
       NonEmptyParameterUsage nonEmptyUsage = usage.asNonEmpty();
       for (DexMethod invokedMethod : nonEmptyUsage.getMethodCallsWithParameterAsReceiver()) {
         SingleResolutionResult<?> resolutionResult =
-            appView.appInfo().resolveMethodOn(eligibleClass, invokedMethod).asSingleResolution();
+            appView
+                .appInfo()
+                .resolveMethodOnLegacy(eligibleClass, invokedMethod)
+                .asSingleResolution();
         if (resolutionResult == null || !resolutionResult.getResolvedHolder().isProgramClass()) {
           return false;
         }
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 ebad797..e4bbb6c 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
@@ -250,7 +250,7 @@
     SingleResolutionResult<?> resolutionResult =
         appView
             .appInfo()
-            .resolveMethodOnClassHolder(invoke.getInvokedMethod())
+            .resolveMethodOnClassHolderLegacy(invoke.getInvokedMethod())
             .asSingleResolution();
     if (resolutionResult == null) {
       return state.abandonClassInliningInCurrentContexts(receiverRoot);
@@ -289,7 +289,7 @@
     SingleResolutionResult<?> resolutionResult =
         appView
             .appInfo()
-            .resolveMethodOnInterfaceHolder(invoke.getInvokedMethod())
+            .resolveMethodOnInterfaceHolderLegacy(invoke.getInvokedMethod())
             .asSingleResolution();
     if (resolutionResult == null) {
       return state.abandonClassInliningInCurrentContexts(receiverRoot);
@@ -305,7 +305,7 @@
     SingleResolutionResult<?> resolutionResult =
         appView
             .appInfo()
-            .unsafeResolveMethodDueToDexFormat(invoke.getInvokedMethod())
+            .unsafeResolveMethodDueToDexFormatLegacy(invoke.getInvokedMethod())
             .asSingleResolution();
     if (resolutionResult != null
         && resolutionResult.getResolvedMethod().getReference()
@@ -331,7 +331,7 @@
     SingleResolutionResult<?> resolutionResult =
         appView
             .appInfo()
-            .resolveMethodOnClassHolder(invoke.getInvokedMethod())
+            .resolveMethodOnClassHolderLegacy(invoke.getInvokedMethod())
             .asSingleResolution();
     if (resolutionResult == null) {
       return state.abandonClassInliningInCurrentContexts(receiverRoot);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index cfec469..ed028ee 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -461,7 +461,7 @@
       ProgramMethod checkNotZeroMethod =
           appView
               .appInfo()
-              .resolveMethodOnClassHolder(checkNotZeroMethodReference)
+              .resolveMethodOnClassHolderLegacy(checkNotZeroMethodReference)
               .getResolvedProgramMethod();
       if (checkNotZeroMethod != null) {
         EnumUnboxerMethodClassification classification =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
index 0d6fa85..f2fbb4c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -160,7 +160,8 @@
         DexEncodedMethod singleTarget =
             appView
                 .appInfo()
-                .resolveMethodOnClass(enumFieldType.getClassType(), factory.objectMembers.toString)
+                .resolveMethodOnClassLegacy(
+                    enumFieldType.getClassType(), factory.objectMembers.toString)
                 .getSingleTarget();
         if (singleTarget != null && singleTarget.getReference() != factory.enumMembers.toString) {
           continue;
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 8d2e339..c02b953 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
@@ -965,7 +965,7 @@
     MethodResolutionResult resolutionResult =
         appView
             .appInfo()
-            .resolveMethodOnClass(clazz, appView.dexItemFactory().objectMembers.finalize);
+            .resolveMethodOnClassLegacy(clazz, appView.dexItemFactory().objectMembers.finalize);
     DexEncodedMethod target = resolutionResult.getSingleTarget();
     return target != null
         && target.getReference() != dexItemFactory.enumMembers.finalize
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 9d4ae82..bd7edf7 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
@@ -49,7 +49,7 @@
       DexClass clazz = appInfo.definitionFor(classType);
       if (clazz != null && clazz.isEffectivelyFinal(appView)) {
         SingleResolutionResult<?> resolutionResult =
-            appInfo.resolveMethodOn(clazz, toStringMethodReference).asSingleResolution();
+            appInfo.resolveMethodOnLegacy(clazz, toStringMethodReference).asSingleResolution();
         if (resolutionResult != null
             && !resolutionResult.getResolvedMethod().getOptimizationInfo().mayHaveSideEffects()) {
           return false;
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 ef2f42d..d2ec4d6 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
@@ -174,7 +174,10 @@
     }
 
     SingleResolutionResult<?> resolutionResult =
-        appView.appInfo().resolveMethodOn(superClass, method.getReference()).asSingleResolution();
+        appView
+            .appInfo()
+            .resolveMethodOnLegacy(superClass, method.getReference())
+            .asSingleResolution();
     if (resolutionResult == null) {
       return null;
     }
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 9402a62..afadf9f 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
@@ -576,11 +576,14 @@
 
   public static class APIConverterConstructorCfCodeProvider extends SyntheticCfCodeProvider {
 
-    DexField wrapperField;
+    private final DexField wrapperField;
+    private final DexType superType;
 
-    public APIConverterConstructorCfCodeProvider(AppView<?> appView, DexField wrapperField) {
+    public APIConverterConstructorCfCodeProvider(
+        AppView<?> appView, DexField wrapperField, DexType superType) {
       super(appView, wrapperField.holder);
       this.wrapperField = wrapperField;
+      this.superType = superType;
     }
 
     @Override
@@ -592,9 +595,7 @@
           new CfInvoke(
               Opcodes.INVOKESPECIAL,
               factory.createMethod(
-                  factory.objectType,
-                  factory.createProto(factory.voidType),
-                  factory.constructorMethodName),
+                  superType, factory.createProto(factory.voidType), factory.constructorMethodName),
               false));
       instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
       instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.type), 1));
diff --git a/src/main/java/com/android/tools/r8/lightir/ByteArrayIterator.java b/src/main/java/com/android/tools/r8/lightir/ByteArrayIterator.java
new file mode 100644
index 0000000..c1f72dc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/ByteArrayIterator.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2022, 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.lightir;
+
+import it.unimi.dsi.fastutil.bytes.ByteIterator;
+
+/** Simple implementation of an iterator over a primitive byte array. */
+public class ByteArrayIterator implements ByteIterator {
+
+  private final int size;
+  private final byte[] buffer;
+  private int index = 0;
+
+  public ByteArrayIterator(byte[] bytes) {
+    size = bytes.length;
+    buffer = bytes;
+  }
+
+  @Override
+  public boolean hasNext() {
+    return index < size;
+  }
+
+  @Override
+  public byte nextByte() {
+    return buffer[index++];
+  }
+
+  @Override
+  public Byte next() {
+    return nextByte();
+  }
+
+  @Override
+  public int skip(int i) {
+    int actual = index + i <= size ? i : size - index;
+    index += actual;
+    return actual;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/ByteArrayWriter.java b/src/main/java/com/android/tools/r8/lightir/ByteArrayWriter.java
new file mode 100644
index 0000000..7636ed4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/ByteArrayWriter.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2022, 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.lightir;
+
+import java.io.ByteArrayOutputStream;
+
+/** Simple implementation to construct a primitive byte array. */
+public class ByteArrayWriter implements ByteWriter {
+
+  // Backing is just the default capacity reallocating java byte array.
+  private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+  @Override
+  public void put(int u1) {
+    assert ByteUtils.isU1(u1);
+    buffer.write(u1);
+  }
+
+  public byte[] toByteArray() {
+    return buffer.toByteArray();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/ByteUtils.java b/src/main/java/com/android/tools/r8/lightir/ByteUtils.java
new file mode 100644
index 0000000..40a80d2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/ByteUtils.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2022, 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.lightir;
+
+/** Simple utilities for byte encodings. */
+public class ByteUtils {
+
+  public static boolean isU1(int value) {
+    return 0 <= value && value <= 0xFF;
+  }
+
+  // Lossy truncation of an integer value to its lowest byte.
+  private static int truncateToU1(int value) {
+    return value & 0xFF;
+  }
+
+  public static int ensureU1(int value) {
+    assert isU1(value);
+    return truncateToU1(value);
+  }
+
+  public static int fromU1(byte value) {
+    return value & 0xFF;
+  }
+
+  public static int intEncodingSize(int value) {
+    return 4;
+  }
+
+  public static void writeEncodedInt(int value, ByteWriter writer) {
+    assert 4 == intEncodingSize(value);
+    writer.put(truncateToU1(value >> 24));
+    writer.put(truncateToU1(value >> 16));
+    writer.put(truncateToU1(value >> 8));
+    writer.put(truncateToU1(value));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/ByteWriter.java b/src/main/java/com/android/tools/r8/lightir/ByteWriter.java
new file mode 100644
index 0000000..0a2a75e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/ByteWriter.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2022, 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.lightir;
+
+/** Most primitive interface for providing consumer to the LIRWriter. */
+public interface ByteWriter {
+
+  /** Put a byte value, must represent an unsigned byte (int between 0 and 255). */
+  void put(int u1);
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRBasicInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LIRBasicInstructionCallback.java
new file mode 100644
index 0000000..a08c036
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LIRBasicInstructionCallback.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2022, 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.lightir;
+
+public interface LIRBasicInstructionCallback {
+
+  /**
+   * Most basic callback for interpreting LIR.
+   *
+   * @param opcode The opcode of the instruction (See {@code LIROpcodes} for values).
+   * @param operandsOffsetInBytes The offset into the byte stream at which the instruction's payload
+   *     starts.
+   * @param operandsSizeInBytes The total size of the instruction's payload (excluding the opcode
+   *     itself an any payload size encoding).
+   */
+  void onInstruction(int opcode, int operandsOffsetInBytes, int operandsSizeInBytes);
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java b/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
new file mode 100644
index 0000000..7ced9ef
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2022, 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.lightir;
+
+import com.android.tools.r8.graph.DexItem;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+
+public class LIRBuilder {
+
+  private final ByteArrayWriter byteWriter = new ByteArrayWriter();
+  private final LIRWriter writer = new LIRWriter(byteWriter);
+  private final Reference2IntMap<DexItem> constants;
+
+  public LIRBuilder() {
+    constants = new Reference2IntOpenHashMap<>();
+  }
+
+  private int getConstantIndex(DexItem item) {
+    int nextIndex = constants.size();
+    Integer oldIndex = constants.putIfAbsent(item, nextIndex);
+    return oldIndex != null ? oldIndex : nextIndex;
+  }
+
+  public LIRBuilder addNop() {
+    writer.writeOneByteInstruction(LIROpcodes.NOP);
+    return this;
+  }
+
+  public LIRBuilder addConstNull() {
+    writer.writeOneByteInstruction(LIROpcodes.ACONST_NULL);
+    return this;
+  }
+
+  public LIRBuilder addConstInt(int value) {
+    if (0 <= value && value <= 5) {
+      writer.writeOneByteInstruction(LIROpcodes.ICONST_0 + value);
+    } else {
+      writer.writeInstruction(LIROpcodes.ICONST, ByteUtils.intEncodingSize(value));
+      ByteUtils.writeEncodedInt(value, writer::writeOperand);
+    }
+    return this;
+  }
+
+  public LIRCode build() {
+    int constantsCount = constants.size();
+    DexItem[] constantTable = new DexItem[constantsCount];
+    constants.forEach((item, index) -> constantTable[index] = item);
+    return new LIRCode(constantTable, byteWriter.toByteArray());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRCode.java b/src/main/java/com/android/tools/r8/lightir/LIRCode.java
new file mode 100644
index 0000000..c91dbd9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LIRCode.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2022, 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.lightir;
+
+import com.android.tools.r8.graph.DexItem;
+
+public class LIRCode implements Iterable<LIRInstructionView> {
+
+  private final DexItem[] constants;
+  private final byte[] instructions;
+
+  public static LIRBuilder builder() {
+    return new LIRBuilder();
+  }
+
+  // Should be constructed using LIRBuilder.
+  LIRCode(DexItem[] constants, byte[] instructions) {
+    this.constants = constants;
+    this.instructions = instructions;
+  }
+
+  @Override
+  public LIRIterator iterator() {
+    return new LIRIterator(new ByteArrayIterator(instructions));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRInstructionView.java b/src/main/java/com/android/tools/r8/lightir/LIRInstructionView.java
new file mode 100644
index 0000000..59051e3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LIRInstructionView.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2022, 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.lightir;
+
+/**
+ * Abstract view of a LIR instruction.
+ *
+ * <p>The view should not be considered a representation of an instruction as the underlying data
+ * can change. The view callbacks allow interpreting the instruction at different levels of
+ * abstraction depending on need.
+ */
+public interface LIRInstructionView {
+
+  void accept(LIRBasicInstructionCallback eventCallback);
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRIterator.java b/src/main/java/com/android/tools/r8/lightir/LIRIterator.java
new file mode 100644
index 0000000..e05d521
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LIRIterator.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2022, 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.lightir;
+
+import it.unimi.dsi.fastutil.bytes.ByteIterator;
+import java.util.Iterator;
+
+/**
+ * Basic iterator over the light IR.
+ *
+ * <p>This iterator is internally a zero-allocation parser with the "elements" as a view onto the
+ * current state.
+ */
+public class LIRIterator implements Iterator<LIRInstructionView>, LIRInstructionView {
+
+  private final ByteIterator iterator;
+
+  private int currentByteIndex = 0;
+  private int currentOpcode = -1;
+  private int currentOperandSize = 0;
+
+  public LIRIterator(ByteIterator iterator) {
+    this.iterator = iterator;
+  }
+
+  @Override
+  public boolean hasNext() {
+    return iterator.hasNext();
+  }
+
+  @Override
+  public LIRInstructionView next() {
+    currentOpcode = u1();
+    if (LIROpcodes.isOneByteInstruction(currentOpcode)) {
+      currentOperandSize = 0;
+    } else {
+      // Any instruction that is not a single byte has a two-byte header. The second byte is the
+      // size of the variable width operand payload.
+      currentOperandSize = u1();
+      skip(currentOperandSize);
+    }
+    return this;
+  }
+
+  @Override
+  public void accept(LIRBasicInstructionCallback eventCallback) {
+    int operandsOffset = currentByteIndex - currentOperandSize;
+    eventCallback.onInstruction(currentOpcode, operandsOffset, currentOperandSize);
+  }
+
+  private void skip(int i) {
+    currentByteIndex += i;
+    iterator.skip(i);
+  }
+
+  private int u1() {
+    ++currentByteIndex;
+    return ByteUtils.fromU1(iterator.nextByte());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java b/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java
new file mode 100644
index 0000000..85cc9f1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java
@@ -0,0 +1,182 @@
+// Copyright (c) 2022, 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.lightir;
+
+/**
+ * Constants related to LIR.
+ *
+ * <p>The constants generally follow the bytecode values as defined by the classfile format.
+ */
+public interface LIROpcodes {
+
+  static boolean isOneByteInstruction(int opcode) {
+    assert opcode >= NOP;
+    return opcode <= DCONST_1;
+  }
+
+  // Instructions maintaining the same opcode as defined in CF.
+  int NOP = 0;
+  int ACONST_NULL = 1;
+  int ICONST_M1 = 2;
+  int ICONST_0 = 3;
+  int ICONST_1 = 4;
+  int ICONST_2 = 5;
+  int ICONST_3 = 6;
+  int ICONST_4 = 7;
+  int ICONST_5 = 8;
+  int LCONST_0 = 9;
+  int LCONST_1 = 10;
+  int FCONST_0 = 11;
+  int FCONST_1 = 12;
+  int FCONST_2 = 13;
+  int DCONST_0 = 14;
+  int DCONST_1 = 15;
+  // int BIPUSH = 16;
+  // int SIPUSH = 17;
+  int LDC = 18;
+  // int ILOAD = 21;
+  // int LLOAD = 22;
+  // int FLOAD = 23;
+  // int DLOAD = 24;
+  // int ALOAD = 25;
+  int IALOAD = 46;
+  int LALOAD = 47;
+  int FALOAD = 48;
+  int DALOAD = 49;
+  int AALOAD = 50;
+  int BALOAD = 51;
+  int CALOAD = 52;
+  int SALOAD = 53;
+  // int ISTORE = 54;
+  // int LSTORE = 55;
+  // int FSTORE = 56;
+  // int DSTORE = 57;
+  // int ASTORE = 58;
+  int IASTORE = 79;
+  int LASTORE = 80;
+  int FASTORE = 81;
+  int DASTORE = 82;
+  int AASTORE = 83;
+  int BASTORE = 84;
+  int CASTORE = 85;
+  int SASTORE = 86;
+  // int POP = 87;
+  // int POP2 = 88;
+  // int DUP = 89;
+  // int DUP_X1 = 90;
+  // int DUP_X2 = 91;
+  // int DUP2 = 92;
+  // int DUP2_X1 = 93;
+  // int DUP2_X2 = 94;
+  // int SWAP = 95;
+  int IADD = 96;
+  int LADD = 97;
+  int FADD = 98;
+  int DADD = 99;
+  int ISUB = 100;
+  int LSUB = 101;
+  int FSUB = 102;
+  int DSUB = 103;
+  int IMUL = 104;
+  int LMUL = 105;
+  int FMUL = 106;
+  int DMUL = 107;
+  int IDIV = 108;
+  int LDIV = 109;
+  int FDIV = 110;
+  int DDIV = 111;
+  int IREM = 112;
+  int LREM = 113;
+  int FREM = 114;
+  int DREM = 115;
+  int INEG = 116;
+  int LNEG = 117;
+  int FNEG = 118;
+  int DNEG = 119;
+  int ISHL = 120;
+  int LSHL = 121;
+  int ISHR = 122;
+  int LSHR = 123;
+  int IUSHR = 124;
+  int LUSHR = 125;
+  int IAND = 126;
+  int LAND = 127;
+  int IOR = 128;
+  int LOR = 129;
+  int IXOR = 130;
+  int LXOR = 131;
+  // int IINC = 132;
+  int I2L = 133;
+  int I2F = 134;
+  int I2D = 135;
+  int L2I = 136;
+  int L2F = 137;
+  int L2D = 138;
+  int F2I = 139;
+  int F2L = 140;
+  int F2D = 141;
+  int D2I = 142;
+  int D2L = 143;
+  int D2F = 144;
+  int I2B = 145;
+  int I2C = 146;
+  int I2S = 147;
+  int LCMP = 148;
+  int FCMPL = 149;
+  int FCMPG = 150;
+  int DCMPL = 151;
+  int DCMPG = 152;
+  int IFEQ = 153;
+  int IFNE = 154;
+  int IFLT = 155;
+  int IFGE = 156;
+  int IFGT = 157;
+  int IFLE = 158;
+  int IF_ICMPEQ = 159;
+  int IF_ICMPNE = 160;
+  int IF_ICMPLT = 161;
+  int IF_ICMPGE = 162;
+  int IF_ICMPGT = 163;
+  int IF_ICMPLE = 164;
+  int IF_ACMPEQ = 165;
+  int IF_ACMPNE = 166;
+  int GOTO = 167;
+  // int JSR = 168;
+  // int RET = 169;
+  int TABLESWITCH = 170;
+  int LOOKUPSWITCH = 171;
+  int IRETURN = 172;
+  int LRETURN = 173;
+  int FRETURN = 174;
+  int DRETURN = 175;
+  int ARETURN = 176;
+  int RETURN = 177;
+  int GETSTATIC = 178;
+  int PUTSTATIC = 179;
+  int GETFIELD = 180;
+  int PUTFIELD = 181;
+  int INVOKEVIRTUAL = 182;
+  int INVOKESPECIAL = 183;
+  int INVOKESTATIC = 184;
+  int INVOKEINTERFACE = 185;
+  int INVOKEDYNAMIC = 186;
+  int NEW = 187;
+  int NEWARRAY = 188;
+  int ANEWARRAY = 189;
+  int ARRAYLENGTH = 190;
+  int ATHROW = 191;
+  int CHECKCAST = 192;
+  int INSTANCEOF = 193;
+  int MONITORENTER = 194;
+  int MONITOREXIT = 195;
+  int MULTIANEWARRAY = 197;
+  int IFNULL = 198;
+  int IFNONNULL = 199;
+
+  // Non-CF instructions.
+  int ICONST = 200;
+  int LCONST = 201;
+  int FCONST = 202;
+  int DCONST = 203;
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRWriter.java b/src/main/java/com/android/tools/r8/lightir/LIRWriter.java
new file mode 100644
index 0000000..074726d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LIRWriter.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2022, 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.lightir;
+
+/**
+ * Lowest level writer for constructing LIR encoded data.
+ *
+ * <p>This writer deals with just the instruction and operand encodings. For higher level structure,
+ * such as the constant pool, see LIRBuilder.
+ */
+public class LIRWriter {
+
+  private final ByteWriter writer;
+  private int pendingOperandBytes = 0;
+
+  public LIRWriter(ByteWriter writer) {
+    this.writer = writer;
+  }
+
+  public void writeOneByteInstruction(int opcode) {
+    assert LIROpcodes.isOneByteInstruction(opcode);
+    assert pendingOperandBytes == 0;
+    writer.put(ByteUtils.ensureU1(opcode));
+  }
+
+  public void writeInstruction(int opcode, int operandsSizeInBytes) {
+    assert pendingOperandBytes == 0;
+    writer.put(ByteUtils.ensureU1(opcode));
+    writer.put(ByteUtils.ensureU1(operandsSizeInBytes));
+    pendingOperandBytes = operandsSizeInBytes;
+  }
+
+  public void writeOperand(int u1) {
+    assert pendingOperandBytes > 0;
+    pendingOperandBytes--;
+    writer.put(ByteUtils.ensureU1(u1));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 7657adc..0b0d20f 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -47,16 +47,6 @@
 
     private final Map<String, ClassNamingForNameMapper.Builder> mapping = new HashMap<>();
     private LinkedHashSet<MapVersionMappingInformation> mapVersions = new LinkedHashSet<>();
-    private final Set<String> buildForClass;
-
-    private Builder(Set<String> buildForClass) {
-      this.buildForClass = buildForClass;
-    }
-
-    @Override
-    public boolean buildForClass(String typeName) {
-      return buildForClass == null || buildForClass.contains(typeName);
-    }
 
     @Override
     public ClassNamingForNameMapper.Builder classNamingBuilder(
@@ -88,11 +78,7 @@
   }
 
   public static Builder builder() {
-    return new Builder(null);
-  }
-
-  public static Builder builder(Set<String> buildForClass) {
-    return new Builder(buildForClass);
+    return new Builder();
   }
 
   public static ClassNameMapper mapperFromFile(Path path) throws IOException {
@@ -182,7 +168,7 @@
             diagnosticsHandler != null ? diagnosticsHandler : new Reporter(),
             allowEmptyMappedRanges,
             allowExperimentalMapping)) {
-      ClassNameMapper.Builder builder = ClassNameMapper.builder(buildForClass);
+      ClassNameMapper.Builder builder = ClassNameMapper.builder();
       proguardReader.parse(builder);
       return builder.build();
     }
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 ca7fa4e..242b6e1 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -382,7 +382,8 @@
       return;
     }
 
-    MethodResolutionResult resolutionResult = appView.appInfo().resolveMethodOn(holder, method);
+    MethodResolutionResult resolutionResult =
+        appView.appInfo().resolveMethodOnLegacy(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 bf18111..ddd882c 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -106,7 +106,8 @@
       return true;
     }
 
-    MethodResolutionResult resolution = appView.appInfo().unsafeResolveMethodDueToDexFormat(method);
+    MethodResolutionResult resolution =
+        appView.appInfo().unsafeResolveMethodDueToDexFormatLegacy(method);
     assert resolution != null;
 
     if (resolution.isSingleResolution()) {
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMap.java b/src/main/java/com/android/tools/r8/naming/ProguardMap.java
index d1e7463..6b95444 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMap.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMap.java
@@ -16,10 +16,6 @@
     abstract Builder setCurrentMapVersion(MapVersionMappingInformation mapVersion);
 
     abstract ProguardMap build();
-
-    public boolean buildForClass(String typeName) {
-      return true;
-    }
   }
 
   boolean hasMapping(DexType type);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index bbea4c4..f28d4bc 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -261,18 +261,11 @@
       String after = parseType(false);
       skipWhitespace();
       expect(':');
-      if (mapBuilder.buildForClass(after)) {
-        ClassNaming.Builder currentClassBuilder =
-            mapBuilder.classNamingBuilder(after, before, getPosition());
-        skipWhitespace();
-        if (nextLine()) {
-          parseMemberMappings(currentClassBuilder);
-        }
-      } else {
-        do {
-          lineOffset = line.length();
-          nextLine();
-        } while (hasLine() && !isClassMapping());
+      ClassNaming.Builder currentClassBuilder =
+          mapBuilder.classNamingBuilder(after, before, getPosition());
+      skipWhitespace();
+      if (nextLine()) {
+        parseMemberMappings(currentClassBuilder);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 2e373e0..a03ff91 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -116,7 +116,7 @@
         currentResolutionResult =
             appView
                 .appInfo()
-                .resolveMethodOnClass(currentResolvedHolder.getSuperType(), original)
+                .resolveMethodOnClassLegacy(currentResolvedHolder.getSuperType(), original)
                 .asSingleResolution();
       } else {
         break;
@@ -244,15 +244,15 @@
   }
 
   private MethodResolutionResult resolveMethodOnClass(DexMethod method) {
-    return appView.appInfo().resolveMethodOnClass(method.holder, method);
+    return appView.appInfo().resolveMethodOnClassLegacy(method.holder, method);
   }
 
   private MethodResolutionResult resolveMethodOnInterface(DexMethod method) {
-    return appView.appInfo().resolveMethodOnInterface(method.holder, method);
+    return appView.appInfo().resolveMethodOnInterfaceLegacy(method.holder, method);
   }
 
   private MethodResolutionResult resolveMethod(DexMethod method) {
-    return appView.appInfo().unsafeResolveMethodDueToDexFormat(method);
+    return appView.appInfo().unsafeResolveMethodDueToDexFormatLegacy(method);
   }
 
   private void computeMethodRebinding(MethodAccessInfoCollection methodAccessInfoCollection) {
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 b819518..6782981 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
@@ -216,7 +216,7 @@
       DexClass holder = appView.contextIndependentDefinitionFor(reference.getHolderType());
       if (holder != null) {
         SingleResolutionResult<?> resolutionResult =
-            appView.appInfo().resolveMethodOn(holder, reference).asSingleResolution();
+            appView.appInfo().resolveMethodOnLegacy(holder, reference).asSingleResolution();
         if (resolutionResult != null && resolutionResult.getResolvedHolder() != holder) {
           recordNonReboundMethodAccess(
               reference, resolutionResult.getResolvedMethod().getReference());
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 38c08c6..6ae7365 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
@@ -216,7 +216,7 @@
         return;
       }
       SingleResolutionResult<?> resolutionResult =
-          appInfo.resolveMethodOn(holder, method).asSingleResolution();
+          appInfo.resolveMethodOnLegacy(holder, method).asSingleResolution();
       if (resolutionResult == null) {
         return;
       }
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 cbb7761..1b72c3a 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingUtils.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingUtils.java
@@ -22,7 +22,7 @@
       return false;
     }
     SingleResolutionResult<?> resolutionResult =
-        appView.appInfo().resolveMethodOn(clazz, method).asSingleResolution();
+        appView.appInfo().resolveMethodOnLegacy(clazz, method).asSingleResolution();
     return resolutionResult != null
         && resolutionResult.getResolvedHolder().getType() != method.getHolderType();
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index 8055dc4..16e3dfb 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -53,7 +53,10 @@
     }
     // This is a visibility forward, so check for the direct target.
     ProgramMethod targetMethod =
-        appView.appInfo().unsafeResolveMethodDueToDexFormat(target).getResolvedProgramMethod();
+        appView
+            .appInfo()
+            .unsafeResolveMethodDueToDexFormatLegacy(target)
+            .getResolvedProgramMethod();
     if (targetMethod == null || !targetMethod.getAccessFlags().isPublic()) {
       return false;
     }
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
index 150f7ba..d4c83b0 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
@@ -164,7 +164,10 @@
     }
 
     SingleResolutionResult<?> resolutionResult =
-        appView.appInfo().unsafeResolveMethodDueToDexFormat(invokedMethod).asSingleResolution();
+        appView
+            .appInfo()
+            .unsafeResolveMethodDueToDexFormatLegacy(invokedMethod)
+            .asSingleResolution();
     if (resolutionResult == null) {
       // Nothing to propagate; the invoke instruction fails.
       return;
@@ -543,7 +546,7 @@
     SingleResolutionResult<?> resolution =
         appView
             .appInfo()
-            .resolveMethod(bootstrapMethod.asMethod(), bootstrapMethod.isInterface)
+            .resolveMethodLegacy(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/ArgumentPropagatorMethodReprocessingEnqueuer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
index f7f7d06..cd962c3 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
@@ -188,7 +188,7 @@
 
     private void registerInvokeMethod(DexMethod method) {
       SingleResolutionResult<?> resolutionResult =
-          appView.appInfo().unsafeResolveMethodDueToDexFormat(method).asSingleResolution();
+          appView.appInfo().unsafeResolveMethodDueToDexFormatLegacy(method).asSingleResolution();
       if (resolutionResult == null || !resolutionResult.getResolvedHolder().isProgramClass()) {
         return;
       }
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
index b41cdda..675bff8 100644
--- 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
@@ -131,7 +131,7 @@
             interfaceState.forEach(
                 (interfaceMethod, interfaceMethodState) -> {
                   MethodResolutionResult resolutionResult =
-                      appView.appInfo().resolveMethodOnClass(subclass, interfaceMethod);
+                      appView.appInfo().resolveMethodOnClassLegacy(subclass, interfaceMethod);
                   if (resolutionResult.isFailedResolution()) {
                     // TODO(b/190154391): Do we need to propagate argument information to the first
                     //  virtual method above the inaccessible method in the class hierarchy?
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
index 59d8ba7..a1aec5a 100644
--- 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
@@ -60,9 +60,6 @@
     //  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;
 
@@ -87,7 +84,7 @@
       parentState.inactiveUntilUpperBound.forEach(
           (bounds, inactiveMethodStates) -> {
             ClassTypeElement upperBound = bounds.getDynamicUpperBoundType().asClassType();
-            if (upperBound.equalUpToNullability(classType)) {
+            if (shouldActivateMethodStateGuardedByBounds(upperBound, clazz, superclass)) {
               // The upper bound is the current class, thus this inactive information now becomes
               // active.
               if (bounds.hasDynamicLowerBoundType()) {
@@ -103,7 +100,10 @@
               inactiveMethodStates.forEach(
                   (signature, methodState) -> {
                     SingleResolutionResult<?> resolutionResult =
-                        appView.appInfo().resolveMethodOn(clazz, signature).asSingleResolution();
+                        appView
+                            .appInfo()
+                            .resolveMethodOnLegacy(clazz, signature)
+                            .asSingleResolution();
 
                     // Find the first virtual method in the super class hierarchy.
                     while (resolutionResult != null
@@ -111,7 +111,7 @@
                       resolutionResult =
                           appView
                               .appInfo()
-                              .resolveMethodOnClass(
+                              .resolveMethodOnClassLegacy(
                                   resolutionResult.getResolvedHolder().getSuperType(), signature)
                               .asSingleResolution();
                     }
@@ -158,6 +158,20 @@
       }
       return methodState;
     }
+
+    private boolean shouldActivateMethodStateGuardedByBounds(
+        ClassTypeElement upperBound, DexProgramClass currentClass, DexProgramClass superClass) {
+      ClassTypeElement classType =
+          TypeElement.fromDexType(currentClass.getType(), maybeNull(), appView).asClassType();
+      // When propagating argument information for interface methods downwards from an interface to
+      // a non-interface we need to account for the parent classes of the current class.
+      if (superClass.isInterface()
+          && !currentClass.isInterface()
+          && currentClass.getSuperType() != appView.dexItemFactory().objectType) {
+        return classType.lessThanOrEqualUpToNullability(upperBound, appView);
+      }
+      return classType.equalUpToNullability(upperBound);
+    }
   }
 
   // For each class, stores the argument information for each virtual method on this class and all
@@ -224,7 +238,7 @@
                   // TODO(b/190154391): Verify that the bounds are not trivial according to the
                   //  static receiver type.
                   ClassTypeElement upperBound = bounds.getDynamicUpperBoundType().asClassType();
-                  if (isUpperBoundSatisfied(upperBound, clazz, propagationState)) {
+                  if (isUpperBoundSatisfied(upperBound, clazz)) {
                     if (bounds.hasDynamicLowerBoundType()) {
                       // TODO(b/190154391): Verify that the lower bound is a subtype of the current
                       //  class.
@@ -255,10 +269,7 @@
     propagationStates.put(clazz, propagationState);
   }
 
-  private boolean isUpperBoundSatisfied(
-      ClassTypeElement upperBound,
-      DexProgramClass currentClass,
-      PropagationState propagationState) {
+  private boolean isUpperBoundSatisfied(ClassTypeElement upperBound, DexProgramClass currentClass) {
     DexType upperBoundType =
         upperBound.getClassType() == appView.dexItemFactory().objectType
                 && upperBound.getInterfaces().hasSingleKnownInterface()
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/ArgumentPropagatorReprocessingCriteriaCollection.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/ArgumentPropagatorReprocessingCriteriaCollection.java
index 87f26e7..f872fd7 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/ArgumentPropagatorReprocessingCriteriaCollection.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/ArgumentPropagatorReprocessingCriteriaCollection.java
@@ -127,7 +127,7 @@
             SingleResolutionResult<?> resolutionResult =
                 appView
                     .appInfo()
-                    .unsafeResolveMethodDueToDexFormat(invoke.getInvokedMethod())
+                    .unsafeResolveMethodDueToDexFormatLegacy(invoke.getInvokedMethod())
                     .asSingleResolution();
             if (resolutionResult == null
                 || !resolutionResult.getResolvedHolder().isProgramClass()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java
index b0be28f..2bc10c5 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java
@@ -150,7 +150,7 @@
         ProgramMethod resolvedMethod =
             appView
                 .appInfo()
-                .unsafeResolveMethodDueToDexFormat(invoke.getInvokedMethod())
+                .unsafeResolveMethodDueToDexFormatLegacy(invoke.getInvokedMethod())
                 .getResolvedProgramMethod();
         if (resolvedMethod == null || isUnoptimizable(resolvedMethod)) {
           return null;
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
index 14d47ac..cc81166 100644
--- 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
@@ -84,7 +84,11 @@
   }
 
   public boolean isRoot(DexProgramClass clazz) {
-    DexProgramClass superclass = asProgramClassOrNull(appView.definitionFor(clazz.getSuperType()));
+    DexType superType = clazz.getSuperType();
+    if (superType == null) {
+      return true;
+    }
+    DexProgramClass superclass = asProgramClassOrNull(appView.definitionFor(superType));
     if (superclass != null) {
       return false;
     }
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 1bd92a3..f640d08 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
@@ -166,7 +166,7 @@
       DexEncodedMethod definition = subclass.lookupVirtualMethod(method);
       if (definition == null) {
         DexEncodedMethod resolutionTarget =
-            appView.appInfo().resolveMethodOnClass(subclass, method).getSingleTarget();
+            appView.appInfo().resolveMethodOnClassLegacy(subclass, method).getSingleTarget();
         if (resolutionTarget == null || resolutionTarget.isAbstract()) {
           // The fact that this class does not declare the bridge (or the bridge is abstract) should
           // not prevent us from hoisting the bridge.
@@ -228,7 +228,7 @@
 
     // The targeted method must be present on the new holder class for this to be feasible.
     MethodResolutionResult resolutionResult =
-        appView.appInfo().resolveMethodOnClass(clazz, methodToInvoke);
+        appView.appInfo().resolveMethodOnClassLegacy(clazz, methodToInvoke);
     if (!resolutionResult.isSingleResolution()) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java
new file mode 100644
index 0000000..7572341
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2022, 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.interfaces.analysis;
+
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import java.util.function.Function;
+
+public class BottomCfFrameState extends CfFrameState {
+
+  private static final BottomCfFrameState INSTANCE = new BottomCfFrameState();
+
+  private BottomCfFrameState() {}
+
+  static BottomCfFrameState getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public CfFrameState markInitialized(FrameType uninitializedType, DexType initializedType) {
+    return error();
+  }
+
+  @Override
+  public CfFrameState pop() {
+    return error();
+  }
+
+  @Override
+  public CfFrameState pop(Function<FrameType, CfFrameState> fn) {
+    return error();
+  }
+
+  @Override
+  public CfFrameState pop(AppView<?> appView, FrameType expectedType) {
+    return error();
+  }
+
+  @Override
+  public CfFrameState pop(
+      AppView<?> appView, FrameType expectedType, Function<FrameType, CfFrameState> fn) {
+    return error();
+  }
+
+  @Override
+  public CfFrameState pop(AppView<?> appView, FrameType... expectedTypes) {
+    return error();
+  }
+
+  @Override
+  public CfFrameState popAndInitialize(
+      AppView<?> appView, DexMethod constructor, ProgramMethod context) {
+    return error();
+  }
+
+  @Override
+  public CfFrameState popInitialized(AppView<?> appView, DexType expectedType) {
+    return error();
+  }
+
+  @Override
+  public CfFrameState popInitialized(AppView<?> appView, DexType... expectedTypes) {
+    return error();
+  }
+
+  @Override
+  public CfFrameState push(DexType type) {
+    return new ConcreteCfFrameState().push(type);
+  }
+
+  @Override
+  public CfFrameState push(FrameType frameType) {
+    return new ConcreteCfFrameState().push(frameType);
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    return this == other;
+  }
+
+  @Override
+  public int hashCode() {
+    return System.identityHashCode(this);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java
new file mode 100644
index 0000000..ec899a2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2022, 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.interfaces.analysis;
+
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.AppView;
+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.framework.intraprocedural.AbstractState;
+import java.util.function.Function;
+
+public abstract class CfFrameState extends AbstractState<CfFrameState> {
+
+  public static CfFrameState bottom() {
+    return BottomCfFrameState.getInstance();
+  }
+
+  public static ErroneousCfFrameState error() {
+    return ErroneousCfFrameState.getInstance();
+  }
+
+  @Override
+  public CfFrameState asAbstractState() {
+    return this;
+  }
+
+  public abstract CfFrameState markInitialized(
+      FrameType uninitializedType, DexType initializedType);
+
+  public abstract CfFrameState pop();
+
+  public abstract CfFrameState pop(Function<FrameType, CfFrameState> fn);
+
+  public abstract CfFrameState pop(AppView<?> appView, FrameType expectedType);
+
+  public abstract CfFrameState pop(
+      AppView<?> appView, FrameType expectedType, Function<FrameType, CfFrameState> fn);
+
+  public abstract CfFrameState pop(AppView<?> appView, FrameType... expectedTypes);
+
+  public abstract CfFrameState popAndInitialize(
+      AppView<?> appView, DexMethod constructor, ProgramMethod context);
+
+  public abstract CfFrameState popInitialized(AppView<?> appView, DexType expectedType);
+
+  public abstract CfFrameState popInitialized(AppView<?> appView, DexType... expectedTypes);
+
+  public abstract CfFrameState push(DexType type);
+
+  public abstract CfFrameState push(FrameType frameType);
+
+  @Override
+  public final CfFrameState join(CfFrameState state) {
+    // TODO(b/214496607): Implement join.
+    throw new Unimplemented();
+  }
+
+  @Override
+  public abstract boolean equals(Object other);
+
+  @Override
+  public abstract int hashCode();
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysis.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysis.java
new file mode 100644
index 0000000..feed0d2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysis.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2022, 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.interfaces.analysis;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractTransferFunction;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunctionResult;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.cf.CfBlock;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.cf.CfControlFlowGraph;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.cf.CfIntraproceduralDataflowAnalysis;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class CfOpenClosedInterfacesAnalysis {
+
+  private final AppView<AppInfoWithLiveness> appView;
+
+  public CfOpenClosedInterfacesAnalysis(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+  }
+
+  public void run() {
+    // TODO(b/214496607): Parallelize the analysis.
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      clazz.forEachProgramMethodMatching(DexEncodedMethod::hasCode, this::processMethod);
+    }
+  }
+
+  private void processMethod(ProgramMethod method) {
+    Code code = method.getDefinition().getCode();
+    if (!code.isCfCode()) {
+      assert code.isDefaultInstanceInitializerCode() || code.isThrowNullCode();
+      return;
+    }
+
+    CfCode cfCode = code.asCfCode();
+    CfControlFlowGraph cfg = CfControlFlowGraph.create(cfCode);
+    CfIntraproceduralDataflowAnalysis<CfFrameState> analysis =
+        new CfIntraproceduralDataflowAnalysis<>(
+            CfFrameState.bottom(), cfg, new TransferFunction(method));
+    DataflowAnalysisResult result = analysis.run(cfg.getEntryBlock());
+    // TODO(b/214496607): Determine open interfaces.
+  }
+
+  private class TransferFunction
+      implements AbstractTransferFunction<CfBlock, CfInstruction, CfFrameState> {
+
+    private final ProgramMethod context;
+
+    TransferFunction(ProgramMethod context) {
+      this.context = context;
+    }
+
+    @Override
+    public TransferFunctionResult<CfFrameState> apply(
+        CfInstruction instruction, CfFrameState state) {
+      return instruction.evaluate(state, context, appView, appView.dexItemFactory());
+    }
+
+    @Override
+    public CfFrameState computeBlockEntryState(
+        CfBlock cfBlock, CfBlock predecessor, CfFrameState predecessorExitState) {
+      // TODO(b/214496607): Implement this.
+      throw new Unimplemented();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java
new file mode 100644
index 0000000..63654b3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java
@@ -0,0 +1,169 @@
+// Copyright (c) 2022, 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.interfaces.analysis;
+
+import static com.android.tools.r8.cf.code.CfFrame.getInitializedFrameType;
+
+import com.android.tools.r8.cf.code.CfAssignability;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Objects;
+import java.util.function.Function;
+
+public class ConcreteCfFrameState extends CfFrameState {
+
+  private final Int2ObjectSortedMap<FrameType> locals;
+  private final Deque<FrameType> stack;
+
+  ConcreteCfFrameState() {
+    this(new Int2ObjectAVLTreeMap<>(), new ArrayDeque<>());
+  }
+
+  ConcreteCfFrameState(Int2ObjectSortedMap<FrameType> locals, Deque<FrameType> stack) {
+    this.locals = locals;
+    this.stack = stack;
+  }
+
+  @Override
+  public CfFrameState markInitialized(FrameType uninitializedType, DexType initializedType) {
+    if (uninitializedType.isInitialized()) {
+      return error();
+    }
+    for (Int2ObjectMap.Entry<FrameType> entry : locals.int2ObjectEntrySet()) {
+      FrameType frameType = entry.getValue();
+      FrameType initializedFrameType =
+          getInitializedFrameType(uninitializedType, frameType, initializedType);
+      entry.setValue(initializedFrameType);
+    }
+    // TODO(b/214496607): By using a collection that supports element replacement this could mutate
+    //  the existing stack instead of building a new one.
+    Deque<FrameType> newStack = new ArrayDeque<>();
+    for (FrameType frameType : stack) {
+      FrameType initializedFrameType =
+          getInitializedFrameType(uninitializedType, frameType, initializedType);
+      newStack.addLast(initializedFrameType);
+    }
+    return new ConcreteCfFrameState(locals, newStack);
+  }
+
+  @Override
+  public CfFrameState pop() {
+    if (stack.isEmpty()) {
+      return error();
+    }
+    stack.removeLast();
+    return this;
+  }
+
+  @Override
+  public CfFrameState pop(Function<FrameType, CfFrameState> fn) {
+    if (stack.isEmpty()) {
+      return error();
+    }
+    FrameType frameType = stack.removeLast();
+    return fn.apply(frameType);
+  }
+
+  @Override
+  public CfFrameState pop(AppView<?> appView, FrameType expectedType) {
+    return pop(appView, expectedType, ignore -> this);
+  }
+
+  @Override
+  public CfFrameState pop(
+      AppView<?> appView, FrameType expectedType, Function<FrameType, CfFrameState> fn) {
+    return pop(
+        frameType ->
+            CfAssignability.isAssignable(frameType, expectedType, appView)
+                ? fn.apply(frameType)
+                : error());
+  }
+
+  @Override
+  public CfFrameState pop(AppView<?> appView, FrameType... expectedTypes) {
+    CfFrameState state = this;
+    for (int i = expectedTypes.length - 1; i >= 0; i--) {
+      state = state.pop(appView, expectedTypes[i]);
+    }
+    return state;
+  }
+
+  @Override
+  public CfFrameState popAndInitialize(
+      AppView<?> appView, DexMethod constructor, ProgramMethod context) {
+    return pop(
+        frameType -> {
+          if (frameType.isUninitializedThis()) {
+            if (constructor.getHolderType() == context.getHolderType()
+                || constructor.getHolderType() == context.getHolder().getSuperType()) {
+              return markInitialized(frameType, context.getHolderType());
+            }
+          } else if (frameType.isUninitializedNew()) {
+            DexType uninitializedNewType = frameType.getUninitializedNewType();
+            if (constructor.getHolderType() == uninitializedNewType) {
+              return markInitialized(frameType, uninitializedNewType);
+            }
+          }
+          return error();
+        });
+  }
+
+  @Override
+  public CfFrameState popInitialized(AppView<?> appView, DexType expectedType) {
+    return pop(
+        frameType ->
+            frameType.isInitialized()
+                    && CfAssignability.isAssignable(
+                        frameType.getInitializedType(), expectedType, appView)
+                ? this
+                : error());
+  }
+
+  @Override
+  public CfFrameState popInitialized(AppView<?> appView, DexType... expectedTypes) {
+    CfFrameState state = this;
+    for (int i = expectedTypes.length - 1; i >= 0; i--) {
+      state = state.popInitialized(appView, expectedTypes[i]);
+    }
+    return state;
+  }
+
+  @Override
+  public CfFrameState push(DexType type) {
+    return push(FrameType.initialized(type));
+  }
+
+  @Override
+  public CfFrameState push(FrameType frameType) {
+    stack.push(frameType);
+    return this;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    // TODO(b/214496607): FrameType should implement equals() and hashCode().
+    ConcreteCfFrameState that = (ConcreteCfFrameState) o;
+    return locals.equals(that.locals) && stack.equals(that.stack);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(locals, stack);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java
new file mode 100644
index 0000000..7e71f93
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java
@@ -0,0 +1,91 @@
+// Copyright (c) 2022, 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.interfaces.analysis;
+
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import java.util.function.Function;
+
+/** An analysis state representing that the code does not type check. */
+public class ErroneousCfFrameState extends CfFrameState {
+
+  private static final ErroneousCfFrameState INSTANCE = new ErroneousCfFrameState();
+
+  private ErroneousCfFrameState() {}
+
+  static ErroneousCfFrameState getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public CfFrameState markInitialized(FrameType uninitializedType, DexType initializedType) {
+    return this;
+  }
+
+  @Override
+  public CfFrameState pop() {
+    return this;
+  }
+
+  @Override
+  public CfFrameState pop(Function<FrameType, CfFrameState> fn) {
+    return this;
+  }
+
+  @Override
+  public CfFrameState pop(AppView<?> appView, FrameType expectedType) {
+    return this;
+  }
+
+  @Override
+  public CfFrameState pop(
+      AppView<?> appView, FrameType expectedType, Function<FrameType, CfFrameState> fn) {
+    return this;
+  }
+
+  @Override
+  public CfFrameState pop(AppView<?> appView, FrameType... expectedTypes) {
+    return this;
+  }
+
+  @Override
+  public CfFrameState popAndInitialize(
+      AppView<?> appView, DexMethod constructor, ProgramMethod context) {
+    return this;
+  }
+
+  @Override
+  public CfFrameState popInitialized(AppView<?> appView, DexType expectedType) {
+    return this;
+  }
+
+  @Override
+  public CfFrameState popInitialized(AppView<?> appView, DexType... expectedTypes) {
+    return this;
+  }
+
+  @Override
+  public CfFrameState push(DexType type) {
+    return this;
+  }
+
+  @Override
+  public CfFrameState push(FrameType frameType) {
+    return this;
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    return this == other;
+  }
+
+  @Override
+  public int hashCode() {
+    return System.identityHashCode(this);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
index e17be11..63f4483 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
@@ -160,7 +160,7 @@
         appView.definitionFor(method.getHolder().getSuperType(), method.getHolder());
     if (superClass != null) {
       registry.registerMemberAccess(
-          appView.appInfo().resolveMethodOn(superClass, method.getReference()));
+          appView.appInfo().resolveMethodOnLegacy(superClass, method.getReference()));
     }
 
     // Trace the references in the method and method parameter annotations.
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 1339cbf..e5d00a9 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
@@ -108,7 +108,8 @@
   }
 
   public ProgramMethod registerMethodReference(DexMethod method) {
-    MethodResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
+    MethodResolutionResult resolutionResult =
+        appInfo.unsafeResolveMethodDueToDexFormatLegacy(method);
     registerMemberAccess(resolutionResult, false);
     return resolutionResult.isSingleResolution()
         ? resolutionResult.asSingleResolution().getResolvedProgramMethod()
@@ -209,7 +210,7 @@
   @Override
   public void registerInvokeVirtual(DexMethod invokedMethod) {
     registerMemberAccessForInvoke(
-        appInfo.resolveMethod(
+        appInfo.resolveMethodLegacy(
             graphLens.lookupInvokeVirtual(invokedMethod, methodContext, codeLens).getReference(),
             false));
   }
@@ -217,21 +218,21 @@
   @Override
   public void registerInvokeDirect(DexMethod invokedMethod) {
     registerMemberAccessForInvoke(
-        appInfo.unsafeResolveMethodDueToDexFormat(
+        appInfo.unsafeResolveMethodDueToDexFormatLegacy(
             graphLens.lookupInvokeDirect(invokedMethod, methodContext, codeLens).getReference()));
   }
 
   @Override
   public void registerInvokeStatic(DexMethod invokedMethod) {
     registerMemberAccessForInvoke(
-        appInfo.unsafeResolveMethodDueToDexFormat(
+        appInfo.unsafeResolveMethodDueToDexFormatLegacy(
             graphLens.lookupInvokeStatic(invokedMethod, methodContext, codeLens).getReference()));
   }
 
   @Override
   public void registerInvokeInterface(DexMethod invokedMethod) {
     registerMemberAccessForInvoke(
-        appInfo.resolveMethod(
+        appInfo.resolveMethodLegacy(
             graphLens.lookupInvokeInterface(invokedMethod, methodContext, codeLens).getReference(),
             true));
   }
@@ -239,7 +240,7 @@
   @Override
   public void registerInvokeSuper(DexMethod invokedMethod) {
     registerMemberAccessForInvoke(
-        appInfo.unsafeResolveMethodDueToDexFormat(
+        appInfo.unsafeResolveMethodDueToDexFormatLegacy(
             graphLens.lookupInvokeSuper(invokedMethod, methodContext, codeLens).getReference()));
   }
 
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index 82a04e6..890e4d3 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -222,7 +222,7 @@
                         resultsForLine.add(Pair.create(retracedElement, currentList.get()));
                         contexts.add(retracedElement.getContext());
                       } else {
-                        currentList.empty();
+                        currentList.clear();
                       }
                     }
                     if (currentList.isSet()) {
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapReaderWithFiltering.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapReaderWithFiltering.java
index 279a038..d42da83 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapReaderWithFiltering.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapReaderWithFiltering.java
@@ -4,8 +4,11 @@
 
 package com.android.tools.r8.retrace.internal;
 
+import static com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.LineParserState.COMPLETE_CLASS_MAPPING;
+import static com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.LineParserState.IS_COMMENT_SOURCE_FILE;
 import static java.lang.Integer.MAX_VALUE;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.LineReader;
 import java.io.IOException;
 import java.io.InputStream;
@@ -15,12 +18,182 @@
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
+import java.util.Set;
 
 public abstract class ProguardMapReaderWithFiltering implements LineReader {
 
+  // The LineParserState encodes a simple state that the line parser can be in, where the
+  // (successful) transitions allowed are:
+
+  // BEGINNING -> BEGINNING_NO_WHITESPACE -> SEEN_ORIGINAL_CLASS || IS_COMMENT_START
+
+  // IS_COMMENT_START -> IS_COMMENT_SOURCE_FILE
+
+  // SEEN_ORIGINAL_CLASS -> SEEN_ARROW -> SEEN_OBFUSCATED_CLASS -> COMPLETE_CLASS_MAPPING
+  //
+  // From all states there is a transition on invalid input to NOT_CLASS_MAPPING_OR_SOURCE_FILE.
+  // The terminal states are:
+  // { IS_COMMENT_SOURCE_FILE, COMPLETE_CLASS_MAPPING, NOT_CLASS_MAPPING_OR_SOURCE_FILE }
+  //
+  public enum LineParserState {
+    BEGINNING,
+    BEGINNING_NO_WHITESPACE,
+    SEEN_ORIGINAL_CLASS,
+    SEEN_ARROW,
+    SEEN_OBFUSCATED_CLASS,
+    COMPLETE_CLASS_MAPPING,
+    IS_COMMENT_START,
+    IS_COMMENT_SOURCE_FILE,
+    NOT_CLASS_MAPPING_OR_SOURCE_FILE;
+
+    private boolean isTerminal() {
+      return this == NOT_CLASS_MAPPING_OR_SOURCE_FILE
+          || this == COMPLETE_CLASS_MAPPING
+          || this == IS_COMMENT_SOURCE_FILE;
+    }
+
+    private static int currentIndex;
+    private static int endIndex;
+    private static byte[] bytes;
+    private static final byte[] SOURCE_FILE_BYTES = "sourceFile".getBytes();
+
+    public static LineParserState computeState(byte[] bytes, int startIndex, int endIndex) {
+      currentIndex = startIndex;
+      LineParserState.endIndex = endIndex;
+      LineParserState.bytes = bytes;
+      LineParserState currentState = BEGINNING;
+      while (!currentState.isTerminal()) {
+        currentState = currentState.computeNextState();
+      }
+      return currentState;
+    }
+
+    private LineParserState computeNextState() {
+      assert this != NOT_CLASS_MAPPING_OR_SOURCE_FILE;
+      switch (this) {
+        case BEGINNING:
+          return readUntilNoWhiteSpace()
+              ? BEGINNING_NO_WHITESPACE
+              : NOT_CLASS_MAPPING_OR_SOURCE_FILE;
+        case BEGINNING_NO_WHITESPACE:
+          if (isCommentChar()) {
+            return IS_COMMENT_START;
+          } else {
+            int readLength = readCharactersNoWhiteSpaceUntil(' ');
+            return readLength > 0 ? SEEN_ORIGINAL_CLASS : NOT_CLASS_MAPPING_OR_SOURCE_FILE;
+          }
+        case SEEN_ORIGINAL_CLASS:
+          return readArrow() ? SEEN_ARROW : NOT_CLASS_MAPPING_OR_SOURCE_FILE;
+        case SEEN_ARROW:
+          int colonIndex = readCharactersNoWhiteSpaceUntil(':');
+          return colonIndex > 0 ? SEEN_OBFUSCATED_CLASS : NOT_CLASS_MAPPING_OR_SOURCE_FILE;
+        case SEEN_OBFUSCATED_CLASS:
+          boolean read = readColon();
+          if (!read) {
+            return NOT_CLASS_MAPPING_OR_SOURCE_FILE;
+          }
+          boolean noWhiteSpace = readUntilNoWhiteSpace();
+          return (!noWhiteSpace || isCommentChar())
+              ? COMPLETE_CLASS_MAPPING
+              : NOT_CLASS_MAPPING_OR_SOURCE_FILE;
+        case IS_COMMENT_START:
+          if (readCharactersUntil('{')
+              && readCharactersUntil(':')
+              && readSingleOrDoubleQuote()
+              && readSourceFile()) {
+            return IS_COMMENT_SOURCE_FILE;
+          } else {
+            return NOT_CLASS_MAPPING_OR_SOURCE_FILE;
+          }
+        default:
+          assert isTerminal();
+          throw new Unreachable("Should not compute next state on terminal state");
+      }
+    }
+
+    private boolean readColon() {
+      return read(':');
+    }
+
+    private boolean readCharactersUntil(char ch) {
+      while (currentIndex < endIndex) {
+        if (bytes[currentIndex++] == ch) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    private int readCharactersNoWhiteSpaceUntil(char ch) {
+      int startIndex = currentIndex;
+      while (currentIndex < endIndex) {
+        byte readByte = bytes[currentIndex];
+        if (readByte == ch) {
+          return currentIndex - startIndex;
+        }
+        if (Character.isWhitespace(readByte)) {
+          return -1;
+        }
+        currentIndex++;
+      }
+      return -1;
+    }
+
+    private boolean readUntilNoWhiteSpace() {
+      while (currentIndex < endIndex) {
+        if (!Character.isWhitespace(bytes[currentIndex])) {
+          return true;
+        }
+        currentIndex++;
+      }
+      return false;
+    }
+
+    private boolean readArrow() {
+      return readSpace() && read('-') && read('>') && readSpace();
+    }
+
+    private boolean readSpace() {
+      return read(' ');
+    }
+
+    private boolean read(char ch) {
+      return bytes[currentIndex++] == ch;
+    }
+
+    private boolean isCommentChar() {
+      return bytes[currentIndex] == '#';
+    }
+
+    private boolean readSourceFile() {
+      if (endIndex - currentIndex < SOURCE_FILE_BYTES.length) {
+        return false;
+      }
+      int endSourceFileIndex = currentIndex + SOURCE_FILE_BYTES.length;
+      int sourceFileByteIndex = 0;
+      for (; currentIndex < endSourceFileIndex; currentIndex++) {
+        if (SOURCE_FILE_BYTES[sourceFileByteIndex++] != bytes[currentIndex]) {
+          return false;
+        }
+      }
+      return readSingleOrDoubleQuote();
+    }
+
+    private boolean readSingleOrDoubleQuote() {
+      byte readByte = bytes[currentIndex++];
+      return readByte == '\'' || readByte == '"';
+    }
+  }
+
   private int startIndex = 0;
   private int endIndex = 0;
 
+  private final Set<String> filter;
+
+  protected ProguardMapReaderWithFiltering(Set<String> filter) {
+    this.filter = filter;
+  }
+
   public abstract byte[] read() throws IOException;
 
   public abstract int getStartIndex();
@@ -29,15 +202,43 @@
 
   public abstract boolean exceedsBuffer();
 
+  private boolean isInsideClassOfInterest = false;
+  private boolean seenFirstClass = false;
+
   @Override
   public String readLine() throws IOException {
-    byte[] bytes = readLineFromMultipleReads();
-    if (bytes == null) {
-      return null;
+    while (true) {
+      byte[] bytes = readLineFromMultipleReads();
+      if (bytes == null) {
+        return null;
+      }
+      if (filter == null) {
+        return new String(bytes, startIndex, endIndex - startIndex, StandardCharsets.UTF_8);
+      }
+      LineParserState lineParserState = LineParserState.computeState(bytes, startIndex, endIndex);
+      if (lineParserState == COMPLETE_CLASS_MAPPING) {
+        seenFirstClass = true;
+        String classMapping = getBufferAsString(bytes);
+        String obfuscatedClassName = getObfuscatedClassName(classMapping);
+        isInsideClassOfInterest = filter.contains(obfuscatedClassName);
+        return classMapping;
+      } else if (lineParserState == IS_COMMENT_SOURCE_FILE) {
+        return getBufferAsString(bytes);
+      } else if (isInsideClassOfInterest || !seenFirstClass) {
+        return getBufferAsString(bytes);
+      }
     }
+  }
+
+  private String getBufferAsString(byte[] bytes) {
     return new String(bytes, startIndex, endIndex - startIndex, StandardCharsets.UTF_8);
   }
 
+  private String getObfuscatedClassName(String classMapping) {
+    int arrowIndex = classMapping.indexOf(">");
+    return classMapping.substring(arrowIndex + 2, classMapping.length() - 1);
+  }
+
   private byte[] readLineFromMultipleReads() throws IOException {
     startIndex = 0;
     endIndex = 0;
@@ -82,7 +283,9 @@
     private int currentPosition = 0;
     private int temporaryBufferPosition = 0;
 
-    public ProguardMapReaderWithFilteringMappedBuffer(Path mappingFile) throws IOException {
+    public ProguardMapReaderWithFilteringMappedBuffer(
+        Path mappingFile, Set<String> classNamesOfInterest) throws IOException {
+      super(classNamesOfInterest);
       fileChannel = FileChannel.open(mappingFile, StandardOpenOption.READ);
       channelSize = fileChannel.size();
       readFromChannel();
@@ -160,7 +363,9 @@
     private int endIndex = 0;
     private int endReadIndex = 0;
 
-    public ProguardMapReaderWithFilteringInputBuffer(InputStream inputStream) {
+    public ProguardMapReaderWithFilteringInputBuffer(
+        InputStream inputStream, Set<String> classNamesOfInterest) {
+      super(classNamesOfInterest);
       this.inputStream = inputStream;
     }
 
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
index cb6991e..a5c1dd6 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
@@ -64,17 +64,16 @@
   @Override
   public ProguardMappingProvider build() {
     try {
+      Set<String> buildForClass = allowLookupAllClasses ? null : allowedLookup;
       LineReader reader =
           proguardMapProducer.isFileBacked()
-              ? new ProguardMapReaderWithFilteringMappedBuffer(proguardMapProducer.getPath())
-              : new ProguardMapReaderWithFilteringInputBuffer(proguardMapProducer.get());
+              ? new ProguardMapReaderWithFilteringMappedBuffer(
+                  proguardMapProducer.getPath(), buildForClass)
+              : new ProguardMapReaderWithFilteringInputBuffer(
+                  proguardMapProducer.get(), buildForClass);
       return new ProguardMappingProviderImpl(
           ClassNameMapper.mapperFromLineReaderWithFiltering(
-              reader,
-              diagnosticsHandler,
-              true,
-              allowExperimental,
-              allowLookupAllClasses ? null : allowedLookup));
+              reader, diagnosticsHandler, true, allowExperimental, buildForClass));
     } catch (Exception e) {
       throw new InvalidMappingFileException(e);
     }
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 34425b3..5a6ef63 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -1388,7 +1388,7 @@
       return cachedItem;
     }
     SingleResolutionResult<?> resolution =
-        resolveMethodOn(initialResolutionHolder, method).asSingleResolution();
+        resolveMethodOnLegacy(initialResolutionHolder, method).asSingleResolution();
     if (resolution == null
         || resolution.isAccessibleForVirtualDispatchFrom(context.getHolder(), this).isFalse()) {
       return null;
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 98562f9..47baa0b 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2299,7 +2299,8 @@
   private SingleResolutionResult<?> resolveMethod(
       DexMethod method, ProgramDefinition context, KeepReason reason) {
     // Record the references in case they are not program types.
-    MethodResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
+    MethodResolutionResult resolutionResult =
+        appInfo.unsafeResolveMethodDueToDexFormatLegacy(method);
     if (resolutionResult.isFailedResolution()) {
       markFailedMethodResolutionTargets(
           method, resolutionResult.asFailedResolution(), context, reason);
@@ -2313,7 +2314,7 @@
   private SingleResolutionResult<?> resolveMethod(
       DexMethod method, ProgramDefinition context, KeepReason reason, boolean interfaceInvoke) {
     // Record the references in case they are not program types.
-    MethodResolutionResult resolutionResult = appInfo.resolveMethod(method, interfaceInvoke);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodLegacy(method, interfaceInvoke);
     if (resolutionResult.isSingleResolution()) {
       recordMethodReference(
           method, resolutionResult.getResolutionPair().asProgramDerivedContext(context));
@@ -2794,7 +2795,8 @@
             (resolutionSearchKey, contexts) -> {
               SingleResolutionResult<?> singleResolution =
                   appInfo
-                      .resolveMethod(resolutionSearchKey.method, resolutionSearchKey.isInterface)
+                      .resolveMethodLegacy(
+                          resolutionSearchKey.method, resolutionSearchKey.isInterface)
                       .asSingleResolution();
               if (singleResolution == null) {
                 assert false : "Should not be null";
@@ -2859,7 +2861,7 @@
       markLibraryOrClasspathOverrideLive(
           instantiation,
           libraryClass,
-          appInfo.resolveMethodOn(libraryClass, method.getReference()));
+          appInfo.resolveMethodOnLegacy(libraryClass, method.getReference()));
 
       // Due to API conversion, some overrides can be hidden since they will be rewritten. See
       // class comment of DesugaredLibraryAPIConverter and vivifiedType logic.
@@ -2876,7 +2878,7 @@
         markLibraryOrClasspathOverrideLive(
             instantiation,
             libraryClass,
-            appInfo.resolveMethodOn(instantiation.asClass(), methodToResolve));
+            appInfo.resolveMethodOnLegacy(instantiation.asClass(), methodToResolve));
       }
     }
   }
@@ -2941,6 +2943,9 @@
           markFieldAsLive(field, clazz, reason);
         }
       }
+      if (clazz.superType == null) {
+        break;
+      }
       clazz = getProgramClassOrNull(clazz.superType, clazz);
     } while (clazz != null && !objectAllocationInfoCollection.isInstantiatedDirectly(clazz));
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/ObjectAllocationInfoCollectionUtils.java b/src/main/java/com/android/tools/r8/shaking/ObjectAllocationInfoCollectionUtils.java
index b7b2513..a578485 100644
--- a/src/main/java/com/android/tools/r8/shaking/ObjectAllocationInfoCollectionUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/ObjectAllocationInfoCollectionUtils.java
@@ -62,7 +62,8 @@
                 SingleResolutionResult resolution =
                     appView
                         .appInfo()
-                        .resolveMethodOn(clazz, appView.dexItemFactory().objectMembers.finalize)
+                        .resolveMethodOnLegacy(
+                            clazz, appView.dexItemFactory().objectMembers.finalize)
                         .asSingleResolution();
                 if (resolution != null && resolution.getResolvedHolder().isProgramClass()) {
                   return TraversalContinuation.doBreak();
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 3b96973..471f865 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -91,6 +91,7 @@
 import java.util.Set;
 import java.util.Stack;
 import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -129,6 +130,7 @@
     private final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
     private final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
     private final Set<DexMember<?, ?>> identifierNameStrings = Sets.newIdentityHashSet();
+    private final Map<DexMethod, ProgramMethod> keptMethodBridges = new ConcurrentHashMap<>();
     private final Queue<DelayedRootSetActionItem> delayedRootSetActionItems =
         new ConcurrentLinkedQueue<>();
     private final InternalOptions options;
@@ -420,7 +422,7 @@
         DexEncodedMethod target =
             appView
                 .appInfo()
-                .unsafeResolveMethodDueToDexFormat(referenceInSubType)
+                .unsafeResolveMethodDueToDexFormatLegacy(referenceInSubType)
                 .getSingleTarget();
         // But, the resolution should not be landed on the current type we are visiting.
         if (target == null || target.getHolderType() == type) {
@@ -574,7 +576,9 @@
           visitAllSuperInterfaces(iface);
         }
         if (!clazz.isInterface()) {
-          visitAllSuperInterfaces(clazz.superType);
+          if (clazz.superType != null) {
+            visitAllSuperInterfaces(clazz.superType);
+          }
           return;
         }
         if (originalClazz == clazz) {
@@ -601,7 +605,7 @@
         SingleResolutionResult<?> resolutionResult =
             appView
                 .appInfo()
-                .resolveMethodOn(originalClazz, method.getReference())
+                .resolveMethodOnLegacy(originalClazz, method.getReference())
                 .asSingleResolution();
         if (resolutionResult == null || !resolutionResult.isVirtualTarget()) {
           return;
@@ -614,16 +618,24 @@
           // TODO(b/143643942): For fullmode, this check should probably be removed.
           return;
         }
-        ProgramMethod resolutionMethod =
-            new ProgramMethod(
-                resolutionResult.getResolvedHolder().asProgramClass(),
-                resolutionResult.getResolvedMethod());
-        ProgramMethod methodToKeep =
-            canInsertForwardingMethod(originalClazz, resolutionMethod.getDefinition())
-                ? new ProgramMethod(
-                    originalClazz,
-                    resolutionMethod.getDefinition().toForwardingMethod(originalClazz, appView))
-                : resolutionMethod;
+        ProgramMethod resolutionMethod = resolutionResult.getResolvedProgramMethod();
+        ProgramMethod methodToKeep;
+        if (canInsertForwardingMethod(originalClazz, resolutionMethod.getDefinition())) {
+          DexMethod methodToKeepReference =
+              resolutionMethod.getReference().withHolder(originalClazz, appView.dexItemFactory());
+          methodToKeep =
+              keptMethodBridges.computeIfAbsent(
+                  methodToKeepReference,
+                  k ->
+                      new ProgramMethod(
+                          originalClazz,
+                          resolutionMethod
+                              .getDefinition()
+                              .toForwardingMethod(originalClazz, appView)));
+          assert methodToKeepReference.equals(methodToKeep.getReference());
+        } else {
+          methodToKeep = resolutionMethod;
+        }
 
         delayedRootSetActionItems.add(
             new InterfaceMethodSyntheticBridgeAction(
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 9797f72..5aa62c3 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -853,7 +853,7 @@
         // Conservatively find all possible targets for this method.
         LookupResultSuccess lookupResult =
             appInfo
-                .resolveMethodOnInterface(method.getHolderType(), method.getReference())
+                .resolveMethodOnInterfaceLegacy(method.getHolderType(), method.getReference())
                 .lookupVirtualDispatchTargets(target, appInfo)
                 .asLookupResultSuccess();
         assert lookupResult != null;
@@ -1568,7 +1568,7 @@
     // Returns the method that shadows the given method, or null if method is not shadowed.
     private DexEncodedMethod findMethodInTarget(DexEncodedMethod method) {
       MethodResolutionResult resolutionResult =
-          appInfo.resolveMethodOn(target, method.getReference());
+          appInfo.resolveMethodOnLegacy(target, method.getReference());
       if (!resolutionResult.isSingleResolution()) {
         // May happen in case of missing classes, or if multiple implementations were found.
         abortMerge = true;
@@ -2133,8 +2133,8 @@
 
         MethodResolutionResult resolutionResult =
             isInterface.isUnknown()
-                ? appView.appInfo().unsafeResolveMethodDueToDexFormat(method)
-                : appView.appInfo().resolveMethod(method, isInterface.isTrue());
+                ? appView.appInfo().unsafeResolveMethodDueToDexFormatLegacy(method)
+                : appView.appInfo().resolveMethodLegacy(method, isInterface.isTrue());
         if (!resolutionResult.isSingleResolution()
             || !resolutionResult.asSingleResolution().getResolvedMethod().isPublic()) {
           setResult(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 cee58c4..f6d8354 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -569,7 +569,7 @@
     SynthesizingContext outerContext = internalGetOuterContext(context, appView);
     DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory());
     DexClass clazz = appView.definitionFor(type);
-    assert clazz != null;
+    assert clazz != null : "Missing existing fixed class " + type;
     assert isSyntheticClass(type);
     assert clazz.isProgramClass();
     return clazz.asProgramClass();
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 c5ffc7a..1d74048 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
@@ -246,7 +246,7 @@
 
       DexClassAndMethod superTarget =
           appInfo()
-              .resolveMethodOn(method.getHolder(), method.getReference())
+              .resolveMethodOnLegacy(method.getHolder(), method.getReference())
               .lookupInvokeSpecialTarget(method.getHolder(), appInfo());
       if (superTarget != null
           && !superTarget.isProgramMethod()
@@ -267,7 +267,8 @@
           method -> {
             DexClassAndMethod resolvedMethod =
                 appInfo()
-                    .resolveMethodOn(superType, method.getReference(), superType != clazz.superType)
+                    .resolveMethodOnLegacy(
+                        superType, method.getReference(), superType != clazz.superType)
                     .getResolutionPair();
             if (resolvedMethod != null
                 && !resolvedMethod.isProgramMethod()
@@ -311,7 +312,7 @@
         assert lookupResult.getType().isStatic();
         DexMethod rewrittenMethod = lookupResult.getReference();
         handleRewrittenMethodResolution(
-            rewrittenMethod, appInfo().unsafeResolveMethodDueToDexFormat(rewrittenMethod));
+            rewrittenMethod, appInfo().unsafeResolveMethodDueToDexFormatLegacy(rewrittenMethod));
       }
 
       @Override
@@ -320,7 +321,7 @@
         assert lookupResult.getType().isSuper();
         DexMethod rewrittenMethod = lookupResult.getReference();
         MethodResolutionResult resolutionResult =
-            appInfo().unsafeResolveMethodDueToDexFormat(rewrittenMethod);
+            appInfo().unsafeResolveMethodDueToDexFormatLegacy(rewrittenMethod);
         if (resolutionResult.isFailedResolution()
             && resolutionResult.asFailedResolution().hasMethodsCausingError()) {
           handleRewrittenMethodResolution(rewrittenMethod, resolutionResult);
@@ -349,8 +350,8 @@
         handleRewrittenMethodResolution(
             method,
             lookupResult.getType().isInterface()
-                ? appInfo().resolveMethodOnInterfaceHolder(method)
-                : appInfo().resolveMethodOnClassHolder(method));
+                ? appInfo().resolveMethodOnInterfaceHolderLegacy(method)
+                : appInfo().resolveMethodOnClassHolderLegacy(method));
       }
 
       private void handleRewrittenMethodResolution(
diff --git a/src/main/java/com/android/tools/r8/utils/Box.java b/src/main/java/com/android/tools/r8/utils/Box.java
index 2e6f17c..1134718 100644
--- a/src/main/java/com/android/tools/r8/utils/Box.java
+++ b/src/main/java/com/android/tools/r8/utils/Box.java
@@ -5,65 +5,43 @@
 package com.android.tools.r8.utils;
 
 import java.util.Comparator;
-import java.util.Objects;
 import java.util.function.Supplier;
 
-public class Box<T> {
-
-  private T value;
+public class Box<T> extends BoxBase<T> {
 
   public Box() {}
 
   public Box(T initialValue) {
-    set(initialValue);
+    super(initialValue);
   }
 
+  @Override
+  public void clear() {
+    super.clear();
+  }
+
+  @Override
   public T computeIfAbsent(Supplier<T> supplier) {
-    if (value == null) {
-      value = supplier.get();
-    }
-    return value;
+    return super.computeIfAbsent(supplier);
   }
 
+  @Override
   public T get() {
-    return value;
+    return super.get();
   }
 
-  public void set(T value) {
-    this.value = value;
-  }
-
-  public void setMin(T element, Comparator<T> comparator) {
-    if (!isSet() || comparator.compare(element, get()) < 0) {
-      set(element);
-    }
-  }
-
-  public boolean isSet() {
-    return value != null;
-  }
-
+  @Override
   public T getAndSet(T newValue) {
-    T oldValue = value;
-    value = newValue;
-    return oldValue;
+    return super.getAndSet(newValue);
   }
 
   @Override
-  public boolean equals(Object object) {
-    if (object == null || getClass() != object.getClass()) {
-      return false;
-    }
-    Box<?> box = (Box<?>) object;
-    return Objects.equals(value, box.value);
+  public void set(T value) {
+    super.set(value);
   }
 
   @Override
-  public int hashCode() {
-    return Objects.hashCode(value);
-  }
-
-  public void empty() {
-    value = null;
+  public void setMin(T value, Comparator<T> comparator) {
+    super.setMin(value, comparator);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/BoxBase.java b/src/main/java/com/android/tools/r8/utils/BoxBase.java
new file mode 100644
index 0000000..d38d92b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/BoxBase.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import java.util.Comparator;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+public abstract class BoxBase<T> {
+
+  private T value;
+
+  public BoxBase() {}
+
+  public BoxBase(T initialValue) {
+    set(initialValue);
+  }
+
+  void clear() {
+    set(null);
+  }
+
+  T computeIfAbsent(Supplier<T> supplier) {
+    if (!isSet()) {
+      set(supplier.get());
+    }
+    return value;
+  }
+
+  T get() {
+    return value;
+  }
+
+  T getAndSet(T newValue) {
+    T oldValue = value;
+    value = newValue;
+    return oldValue;
+  }
+
+  void set(T value) {
+    this.value = value;
+  }
+
+  void setMin(T value, Comparator<T> comparator) {
+    if (!isSet() || comparator.compare(value, get()) < 0) {
+      set(value);
+    }
+  }
+
+  public boolean isSet() {
+    return value != null;
+  }
+
+  @Override
+  public boolean equals(Object object) {
+    if (object == null || getClass() != object.getClass()) {
+      return false;
+    }
+    BoxBase<?> box = (BoxBase<?>) object;
+    return Objects.equals(value, box.value);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(value);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java b/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
index c9a56da..c16b39f 100644
--- a/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
+++ b/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
@@ -13,12 +13,13 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
 
-public abstract class DepthFirstSearchWorkListBase<N, T extends DFSNodeImpl<N>> {
+public abstract class DepthFirstSearchWorkListBase<N, T extends DFSNodeImpl<N>, TB, TC> {
 
   public interface DFSNode<N> {
     N getNode();
@@ -103,36 +104,48 @@
   }
 
   private final ArrayDeque<T> workList = new ArrayDeque<>();
+  // This map is necessary ensure termination since we embed nodes into nodes with state.
+  private final Map<N, T> nodeToNodeWithStateMap = new IdentityHashMap<>();
 
   abstract T createDfsNode(N node);
 
   /** The initial processing of a node during forward search */
-  abstract TraversalContinuation<?, ?> internalOnVisit(T node);
+  abstract TraversalContinuation<TB, TC> internalOnVisit(T node);
 
   /** The joining of state during backtracking of the algorithm. */
-  abstract TraversalContinuation<?, ?> internalOnJoin(T node);
+  abstract TraversalContinuation<TB, TC> internalOnJoin(T node);
+
+  abstract List<TC> getFinalStateForRoots(Collection<N> roots);
 
   final T internalEnqueueNode(N value) {
-    T dfsNode = createDfsNode(value);
+    T dfsNode = nodeToNodeWithStateMap.computeIfAbsent(value, this::createDfsNode);
     if (dfsNode.isNotProcessed()) {
       workList.addLast(dfsNode);
     }
     return dfsNode;
   }
 
+  protected T getNodeStateForNode(N value) {
+    return nodeToNodeWithStateMap.get(value);
+  }
+
+  public final TraversalContinuation<TB, TC> run(N root) {
+    return run(Collections.singletonList(root)).map(Function.identity(), results -> results.get(0));
+  }
+
   @SafeVarargs
-  public final TraversalContinuation<?, ?> run(N... roots) {
+  public final TraversalContinuation<TB, List<TC>> run(N... roots) {
     return run(Arrays.asList(roots));
   }
 
-  public final TraversalContinuation<?, ?> run(Collection<N> roots) {
+  public final TraversalContinuation<TB, List<TC>> run(Collection<N> roots) {
     roots.forEach(this::internalEnqueueNode);
-    TraversalContinuation<?, ?> continuation = TraversalContinuation.doContinue();
     while (!workList.isEmpty()) {
       T node = workList.removeLast();
       if (node.isFinished()) {
         continue;
       }
+      TraversalContinuation<TB, TC> continuation;
       if (node.isNotProcessed()) {
         workList.addLast(node);
         node.setWaiting();
@@ -143,14 +156,15 @@
         node.setFinished();
       }
       if (continuation.shouldBreak()) {
-        return continuation;
+        return TraversalContinuation.doBreak(continuation.asBreak().getValue());
       }
     }
-    return continuation;
+
+    return TraversalContinuation.doContinue(getFinalStateForRoots(roots));
   }
 
-  public abstract static class DepthFirstSearchWorkList<N>
-      extends DepthFirstSearchWorkListBase<N, DFSNodeImpl<N>> {
+  public abstract static class DepthFirstSearchWorkList<N, TB, TC>
+      extends DepthFirstSearchWorkListBase<N, DFSNodeImpl<N>, TB, TC> {
 
     /**
      * The initial processing of the node when visiting the first time during the depth first
@@ -161,7 +175,7 @@
      *     before but not finished there is a cycle.
      * @return A value describing if the DFS algorithm should continue to run.
      */
-    protected abstract TraversalContinuation<?, ?> process(
+    protected abstract TraversalContinuation<TB, TC> process(
         DFSNode<N> node, Function<N, DFSNode<N>> childNodeConsumer);
 
     @Override
@@ -170,18 +184,18 @@
     }
 
     @Override
-    TraversalContinuation<?, ?> internalOnVisit(DFSNodeImpl<N> node) {
+    TraversalContinuation<TB, TC> internalOnVisit(DFSNodeImpl<N> node) {
       return process(node, this::internalEnqueueNode);
     }
 
     @Override
-    protected TraversalContinuation<?, ?> internalOnJoin(DFSNodeImpl<N> node) {
+    protected TraversalContinuation<TB, TC> internalOnJoin(DFSNodeImpl<N> node) {
       return TraversalContinuation.doContinue();
     }
   }
 
-  public abstract static class StatefulDepthFirstSearchWorkList<N, S>
-      extends DepthFirstSearchWorkListBase<N, DFSNodeWithStateImpl<N, S>> {
+  public abstract static class StatefulDepthFirstSearchWorkList<N, S, TB>
+      extends DepthFirstSearchWorkListBase<N, DFSNodeWithStateImpl<N, S>, TB, S> {
 
     private final Map<DFSNodeWithStateImpl<N, S>, List<DFSNodeWithState<N, S>>> childStateMap =
         new IdentityHashMap<>();
@@ -195,7 +209,7 @@
      *     before but not finished there is a cycle.
      * @return A value describing if the DFS algorithm should continue to run.
      */
-    protected abstract TraversalContinuation<?, ?> process(
+    protected abstract TraversalContinuation<TB, S> process(
         DFSNodeWithState<N, S> node, Function<N, DFSNodeWithState<N, S>> childNodeConsumer);
 
     /**
@@ -205,7 +219,7 @@
      * @param childStates The already computed child states.
      * @return A value describing if the DFS algorithm should continue to run.
      */
-    protected abstract TraversalContinuation<?, ?> joiner(
+    protected abstract TraversalContinuation<TB, S> joiner(
         DFSNodeWithState<N, S> node, List<DFSNodeWithState<N, S>> childStates);
 
     @Override
@@ -214,7 +228,7 @@
     }
 
     @Override
-    TraversalContinuation<?, ?> internalOnVisit(DFSNodeWithStateImpl<N, S> node) {
+    TraversalContinuation<TB, S> internalOnVisit(DFSNodeWithStateImpl<N, S> node) {
       List<DFSNodeWithState<N, S>> childStates = new ArrayList<>();
       List<DFSNodeWithState<N, S>> removedChildStates = childStateMap.put(node, childStates);
       assert removedChildStates == null;
@@ -228,7 +242,7 @@
     }
 
     @Override
-    protected TraversalContinuation<?, ?> internalOnJoin(DFSNodeWithStateImpl<N, S> node) {
+    protected TraversalContinuation<TB, S> internalOnJoin(DFSNodeWithStateImpl<N, S> node) {
       return joiner(
           node,
           childStateMap.computeIfAbsent(
@@ -238,5 +252,10 @@
                 return new ArrayList<>();
               }));
     }
+
+    @Override
+    List<S> getFinalStateForRoots(Collection<N> roots) {
+      return ListUtils.map(roots, root -> getNodeStateForNode(root).state);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
deleted file mode 100644
index 00412d9..0000000
--- a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
+++ /dev/null
@@ -1,313 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.utils;
-
-import com.android.tools.r8.ArchiveClassFileProvider;
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.Keep;
-import com.android.tools.r8.dexsplitter.DexSplitter.FeatureJar;
-import com.android.tools.r8.origin.PathOrigin;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-/**
- * Provides a mappings of classes to modules. The structure of the input file is as follows:
- * packageOrClass:module
- *
- * <p>Lines with a # prefix are ignored.
- *
- * <p>We will do most specific matching, i.e.,
- * <pre>
- *   com.google.foobar.*:feature2
- *   com.google.*:base
- * </pre>
- * will put everything in the com.google namespace into base, except classes in com.google.foobar
- * that will go to feature2. Class based mappings takes precedence over packages (since they are
- * more specific):
- * <pre>
- *   com.google.A:feature2
- *   com.google.*:base
- *  </pre>
- * Puts A into feature2, and all other classes from com.google into base.
- *
- * <p>Note that this format does not allow specifying inter-module dependencies, this is simply a
- * placement tool.
- */
-@Keep
-public final class FeatureClassMapping {
-
-  Map<String, String> parsedRules = new HashMap<>(); // Already parsed rules.
-  Map<String, String> parseNonClassRules = new HashMap<>();
-  boolean usesOnlyExactMappings = true;
-
-  Set<FeaturePredicate> mappings = new HashSet<>();
-
-  Path mappingFile;
-  String baseName = DEFAULT_BASE_NAME;
-
-  static final String DEFAULT_BASE_NAME = "base";
-
-  static final String COMMENT = "#";
-  static final String SEPARATOR = ":";
-
-  public String getBaseName() {
-    return baseName;
-  }
-
-  private static class SpecificationOrigin extends PathOrigin {
-
-    public SpecificationOrigin(Path path) {
-      super(path);
-    }
-
-    @Override
-    public String part() {
-      return "specification file '" + super.part() + "'";
-    }
-  }
-
-  private static class JarFileOrigin extends PathOrigin {
-
-    public JarFileOrigin(Path path) {
-      super(path);
-    }
-
-    @Override
-    public String part() {
-      return "jar file '" + super.part() + "'";
-    }
-  }
-
-  public static FeatureClassMapping fromSpecification(Path file) throws FeatureMappingException {
-    return fromSpecification(file, new DiagnosticsHandler() {});
-  }
-
-  public static FeatureClassMapping fromSpecification(Path file, DiagnosticsHandler reporter)
-      throws FeatureMappingException {
-    FeatureClassMapping mapping = new FeatureClassMapping();
-    List<String> lines = null;
-    try {
-      lines = FileUtils.readAllLines(file);
-    } catch (IOException e) {
-      ExceptionDiagnostic error = new ExceptionDiagnostic(e, new SpecificationOrigin(file));
-      reporter.error(error);
-      throw new AbortException(error);
-    }
-    for (int i = 0; i < lines.size(); i++) {
-      String line = lines.get(i);
-      mapping.parseAndAdd(line, i);
-    }
-    return mapping;
-  }
-
-  public static class Internal {
-    private static List<String> getClassFileDescriptors(String jar, DiagnosticsHandler reporter) {
-      Path jarPath = Paths.get(jar);
-      try {
-        return new ArchiveClassFileProvider(jarPath).getClassDescriptors()
-            .stream()
-            .map(DescriptorUtils::descriptorToJavaType)
-            .collect(Collectors.toList());
-      } catch (IOException e) {
-        ExceptionDiagnostic error = new ExceptionDiagnostic(e, new JarFileOrigin(jarPath));
-        reporter.error(error);
-        throw new AbortException(error);
-      }
-    }
-
-    private static List<String> getNonClassFiles(String jar, DiagnosticsHandler reporter) {
-      try (ZipFile zipfile = new ZipFile(jar, StandardCharsets.UTF_8)) {
-          return zipfile.stream()
-              .filter(entry -> !ZipUtils.isClassFile(entry.getName()))
-              .map(ZipEntry::getName)
-              .collect(Collectors.toList());
-        } catch (IOException e) {
-        ExceptionDiagnostic error = new ExceptionDiagnostic(e, new JarFileOrigin(Paths.get(jar)));
-        reporter.error(error);
-        throw new AbortException(error);
-        }
-    }
-
-    public static FeatureClassMapping fromJarFiles(
-        List<FeatureJar> featureJars, List<String> baseJars, String baseName,
-        DiagnosticsHandler reporter)
-        throws FeatureMappingException {
-      FeatureClassMapping mapping = new FeatureClassMapping();
-      if (baseName != null) {
-        mapping.baseName = baseName;
-      }
-      for (FeatureJar featureJar : featureJars) {
-        for (String javaType : getClassFileDescriptors(featureJar.getJar(), reporter)) {
-          mapping.addMapping(javaType, featureJar.getOutputName());
-        }
-        for (String nonClass : getNonClassFiles(featureJar.getJar(), reporter)) {
-          mapping.addNonClassMapping(nonClass, featureJar.getOutputName());
-        }
-      }
-      for (String baseJar : baseJars) {
-        for (String javaType : getClassFileDescriptors(baseJar, reporter)) {
-          mapping.addBaseMapping(javaType);
-        }
-        for (String nonClass : getNonClassFiles(baseJar, reporter)) {
-          mapping.addBaseNonClassMapping(nonClass);
-        }
-      }
-      assert mapping.usesOnlyExactMappings;
-      return mapping;
-    }
-
-  }
-
-  private FeatureClassMapping() {}
-
-  public void addBaseMapping(String clazz) throws FeatureMappingException {
-    addMapping(clazz, baseName);
-  }
-
-  public void addBaseNonClassMapping(String name) {
-    addNonClassMapping(name, baseName);
-  }
-
-  public void addMapping(String clazz, String feature) throws FeatureMappingException {
-    addRule(clazz, feature, 0);
-  }
-
-  public void addNonClassMapping(String name, String feature) {
-    // If a non-class file is present in multiple features put the resource in the base.
-    parseNonClassRules.put(name, parseNonClassRules.containsKey(name) ? baseName : feature);
-  }
-
-  FeatureClassMapping(List<String> lines) throws FeatureMappingException {
-    for (int i = 0; i < lines.size(); i++) {
-      String line = lines.get(i);
-      parseAndAdd(line, i);
-    }
-  }
-
-  public String featureForClass(String clazz) {
-    if (usesOnlyExactMappings) {
-      return parsedRules.getOrDefault(clazz, baseName);
-    } else {
-      FeaturePredicate bestMatch = null;
-      for (FeaturePredicate mapping : mappings) {
-        if (mapping.match(clazz)) {
-          if (bestMatch == null || bestMatch.predicate.length() < mapping.predicate.length()) {
-            bestMatch = mapping;
-          }
-        }
-      }
-      if (bestMatch == null) {
-        return baseName;
-      }
-      return bestMatch.feature;
-    }
-  }
-
-  public String featureForNonClass(String nonClass) {
-    return parseNonClassRules.getOrDefault(nonClass, baseName);
-  }
-
-  private void parseAndAdd(String line, int lineNumber) throws FeatureMappingException {
-    if (line.startsWith(COMMENT)) {
-      return; // Ignore comments
-    }
-    if (line.isEmpty()) {
-      return; // Ignore blank lines
-    }
-
-    if (!line.contains(SEPARATOR)) {
-      error("Mapping lines must contain a " + SEPARATOR, lineNumber);
-    }
-    String[] values = line.split(SEPARATOR);
-    if (values.length != 2) {
-      error("Mapping lines can only contain one " + SEPARATOR, lineNumber);
-    }
-
-    String predicate = values[0];
-    String feature = values[1];
-    addRule(predicate, feature, lineNumber);
-  }
-
-  private void addRule(String predicate, String feature, int lineNumber)
-      throws FeatureMappingException {
-    if (parsedRules.containsKey(predicate)) {
-      if (!parsedRules.get(predicate).equals(feature)) {
-        error("Redefinition of predicate " + predicate + "not allowed", lineNumber);
-      }
-      return; // Already have this rule.
-    }
-    parsedRules.put(predicate, feature);
-    FeaturePredicate featurePredicate = new FeaturePredicate(predicate, feature);
-    mappings.add(featurePredicate);
-    usesOnlyExactMappings &= featurePredicate.isExactmapping();
-  }
-
-  private void error(String error, int line) throws FeatureMappingException {
-    throw new FeatureMappingException(
-        "Invalid mappings specification: " + error + "\n in file " + mappingFile + ":" + line);
-  }
-
-  @Keep
-  public static class FeatureMappingException extends Exception {
-    FeatureMappingException(String message) {
-      super(message);
-    }
-  }
-
-  /** A feature predicate can either be a wildcard or class predicate. */
-  private static class FeaturePredicate {
-    private static Pattern identifier = Pattern.compile("[A-Za-z_\\-][A-Za-z0-9_$\\-]*");
-    final String predicate;
-    final String feature;
-    final boolean isCatchAll;
-    // False implies class predicate.
-    final boolean isWildcard;
-
-    FeaturePredicate(String predicate, String feature) throws FeatureMappingException {
-      isWildcard = predicate.endsWith(".*");
-      isCatchAll =  predicate.equals("*");
-      if (isCatchAll) {
-        this.predicate = "";
-      } else if (isWildcard) {
-        String packageName = predicate.substring(0, predicate.length() - 2);
-        if (!DescriptorUtils.isValidJavaType(packageName)) {
-          throw new FeatureMappingException(packageName + " is not a valid identifier");
-        }
-        // Prefix of a fully-qualified class name, including a terminating dot.
-        this.predicate = predicate.substring(0, predicate.length() - 1);
-      } else {
-        if (!DescriptorUtils.isValidJavaType(predicate)) {
-          throw new FeatureMappingException(predicate + " is not a valid identifier");
-        }
-        this.predicate = predicate;
-      }
-      this.feature = feature;
-    }
-
-    boolean match(String className) {
-      if (isCatchAll) {
-        return true;
-      } else if (isWildcard) {
-        return className.startsWith(predicate);
-      } else {
-        return className.equals(predicate);
-      }
-    }
-
-    boolean isExactmapping() {
-      return !isWildcard && !isCatchAll;
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/utils/IntBox.java b/src/main/java/com/android/tools/r8/utils/IntBox.java
index 9a31ebd..79684cd 100644
--- a/src/main/java/com/android/tools/r8/utils/IntBox.java
+++ b/src/main/java/com/android/tools/r8/utils/IntBox.java
@@ -53,6 +53,15 @@
     value += i;
   }
 
+  public int incrementAndGet() {
+    return incrementAndGet(1);
+  }
+
+  public int incrementAndGet(int i) {
+    increment(i);
+    return get();
+  }
+
   public void set(int value) {
     this.value = value;
   }
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 8942e41..a2c6aae 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -360,6 +360,7 @@
   public boolean enableRedundantFieldLoadElimination = true;
   // TODO(b/138917494): Disable until we have numbers on potential performance penalties.
   public boolean enableRedundantConstNumberOptimization = false;
+  public boolean enableLoopUnrolling = true;
 
   public String synthesizedClassPrefix = "";
 
@@ -1671,6 +1672,10 @@
     public void disableApiCallerIdentification() {
       enableApiCallerIdentification = false;
     }
+
+    public void disableSubbingOfClasses() {
+      enableStubbingOfClasses = false;
+    }
   }
 
   public static class ProtoShrinkingOptions {
@@ -1815,7 +1820,6 @@
     public boolean enableEnumUnboxingDebugLogs = false;
     public boolean forceRedundantConstNumberRemoval = false;
     public boolean enableExperimentalDesugaredLibraryKeepRuleGenerator = false;
-    public boolean enableExperimentalLoopUnrolling = false;
     public boolean invertConditionals = false;
     public boolean placeExceptionalBlocksLast = false;
     public boolean dontCreateMarkerInD8 = false;
diff --git a/src/main/java/com/android/tools/r8/utils/LazyBox.java b/src/main/java/com/android/tools/r8/utils/LazyBox.java
index 38d1f2a..0a25b99 100644
--- a/src/main/java/com/android/tools/r8/utils/LazyBox.java
+++ b/src/main/java/com/android/tools/r8/utils/LazyBox.java
@@ -6,7 +6,7 @@
 
 import java.util.function.Supplier;
 
-public class LazyBox<T> extends Box<T> {
+public class LazyBox<T> extends BoxBase<T> {
 
   private final Supplier<T> supplier;
 
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 78f612a..f277168 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -72,7 +72,6 @@
 import com.android.tools.r8.retrace.internal.RetraceUtils;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
-import com.google.common.base.Suppliers;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
@@ -87,7 +86,6 @@
 import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.function.Function;
-import java.util.function.Supplier;
 
 public class LineNumberOptimizer {
 
@@ -502,8 +500,8 @@
       // Create a supplier which creates a new, cached ClassNaming.Builder on-demand.
       DexType originalType = appView.graphLens().getOriginalType(clazz.type);
       DexString renamedDescriptor = namingLens.lookupDescriptor(clazz.getType());
-      Supplier<ClassNaming.Builder> onDemandClassNamingBuilder =
-          Suppliers.memoize(
+      LazyBox<ClassNaming.Builder> onDemandClassNamingBuilder =
+          new LazyBox<>(
               () ->
                   classNameMapperBuilder.classNamingBuilder(
                       DescriptorUtils.descriptorToJavaType(renamedDescriptor.toString()),
@@ -516,14 +514,14 @@
         String sourceFile = originalSourceFile.toString();
         if (!RetraceUtils.hasPredictableSourceFileName(clazz.toSourceString(), sourceFile)) {
           onDemandClassNamingBuilder
-              .get()
+              .computeIfAbsent()
               .addMappingInformation(FileNameInformation.build(sourceFile), Unreachable::raise);
         }
       }
 
       if (isSyntheticClass) {
         onDemandClassNamingBuilder
-            .get()
+            .computeIfAbsent()
             .addMappingInformation(
                 CompilerSynthesizedMappingInformation.builder().build(), Unreachable::raise);
       }
@@ -621,13 +619,13 @@
           }
 
           MemberNaming memberNaming = new MemberNaming(originalSignature, obfuscatedName);
-          onDemandClassNamingBuilder.get().addMemberEntry(memberNaming);
+          onDemandClassNamingBuilder.computeIfAbsent().addMemberEntry(memberNaming);
 
           // Add simple "a() -> b" mapping if we won't have any other with concrete line numbers
           if (mappedPositions.isEmpty()) {
             MappedRange range =
                 onDemandClassNamingBuilder
-                    .get()
+                    .computeIfAbsent()
                     .addMappedRange(null, originalSignature, null, obfuscatedName);
             methodMappingInfo.forEach(
                 info -> range.addMappingInformation(info, Unreachable::raise));
@@ -702,7 +700,7 @@
               obfuscatedRange =
                   new Range(firstPosition.obfuscatedLine, lastPosition.obfuscatedLine);
             }
-            ClassNaming.Builder classNamingBuilder = onDemandClassNamingBuilder.get();
+            ClassNaming.Builder classNamingBuilder = onDemandClassNamingBuilder.computeIfAbsent();
             MappedRange lastMappedRange =
                 getMappedRangesForPosition(
                     appView.options().dexItemFactory(),
@@ -895,11 +893,11 @@
   private static void addClassToClassNaming(
       DexType originalType,
       DexString renamedClassName,
-      Supplier<Builder> onDemandClassNamingBuilder) {
+      LazyBox<Builder> onDemandClassNamingBuilder) {
     // We do know we need to create a ClassNaming.Builder if the class itself had been renamed.
     if (originalType.descriptor != renamedClassName) {
       // Not using return value, it's registered in classNameMapperBuilder
-      onDemandClassNamingBuilder.get();
+      onDemandClassNamingBuilder.computeIfAbsent();
     }
   }
 
@@ -908,7 +906,7 @@
       NamingLens namingLens,
       DexProgramClass clazz,
       DexType originalType,
-      Supplier<Builder> onDemandClassNamingBuilder) {
+      LazyBox<Builder> onDemandClassNamingBuilder) {
     clazz.forEachField(
         dexEncodedField -> {
           DexField dexField = dexEncodedField.getReference();
@@ -918,7 +916,7 @@
             FieldSignature originalSignature =
                 FieldSignature.fromDexField(originalField, originalField.holder != originalType);
             MemberNaming memberNaming = new MemberNaming(originalSignature, renamedName.toString());
-            onDemandClassNamingBuilder.get().addMemberEntry(memberNaming);
+            onDemandClassNamingBuilder.computeIfAbsent().addMemberEntry(memberNaming);
           }
         });
   }
diff --git a/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java b/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
index 19f89d8..c33d020 100644
--- a/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
+++ b/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.errors.Unreachable;
+import java.util.function.Function;
 
 /** Two value continuation value to indicate the continuation of a loop/traversal. */
 /* This class is used for building up api class member traversals. */
@@ -25,14 +26,29 @@
     return null;
   }
 
+  public <TBx, TCx> TraversalContinuation<TBx, TCx> map(
+      Function<TB, TBx> mapBreak, Function<TC, TCx> mapContinue) {
+    if (isBreak()) {
+      return new Break<>(mapBreak.apply(asBreak().getValue()));
+    } else {
+      assert isContinue();
+      return new Continue<>(mapContinue.apply(asContinue().getValue()));
+    }
+  }
+
   public static class Continue<TB, TC> extends TraversalContinuation<TB, TC> {
-    private static final TraversalContinuation<?, ?> CONTINUE_NO_VALUE =
+    private static final TraversalContinuation.Continue<?, ?> CONTINUE_NO_VALUE =
         new Continue<Object, Object>(null) {
           @Override
           public Object getValue() {
             return new Unreachable(
                 "Invalid attempt at getting a value from a no-value continue state.");
           }
+
+          @Override
+          public Object getValueOrDefault(Object defaultValue) {
+            return defaultValue;
+          }
         };
 
     private final TC value;
@@ -45,6 +61,10 @@
       return value;
     }
 
+    public TC getValueOrDefault(TC defaultValue) {
+      return value;
+    }
+
     @Override
     public boolean isContinue() {
       return true;
@@ -57,13 +77,18 @@
   }
 
   public static class Break<TB, TC> extends TraversalContinuation<TB, TC> {
-    private static final TraversalContinuation<?, ?> BREAK_NO_VALUE =
+    private static final TraversalContinuation.Break<?, ?> BREAK_NO_VALUE =
         new Break<Object, Object>(null) {
           @Override
           public Object getValue() {
             return new Unreachable(
                 "Invalid attempt at getting a value from a no-value break state.");
           }
+
+          @Override
+          public Object getValueOrDefault(Object defaultValue) {
+            return defaultValue;
+          }
         };
 
     private final TB value;
@@ -76,6 +101,10 @@
       return value;
     }
 
+    public TB getValueOrDefault(TB defaultValue) {
+      return value;
+    }
+
     @Override
     public boolean isBreak() {
       return true;
@@ -87,29 +116,34 @@
     }
   }
 
-  public static TraversalContinuation<?, ?> breakIf(boolean condition) {
+  public static <TB, TC> TraversalContinuation<TB, TC> breakIf(boolean condition) {
     return continueIf(!condition);
   }
 
-  public static TraversalContinuation<?, ?> continueIf(boolean condition) {
+  public static <TB, TC> TraversalContinuation<TB, TC> continueIf(boolean condition) {
     return condition ? doContinue() : doBreak();
   }
 
-  @SuppressWarnings("unchecked")
-  public static <TB, TC> TraversalContinuation<TB, TC> doContinue() {
-    return (TraversalContinuation<TB, TC>) Continue.CONTINUE_NO_VALUE;
+  public TraversalContinuation<TB, TC> ifContinueThen(
+      Function<TraversalContinuation.Continue<TB, TC>, TraversalContinuation<TB, TC>> fn) {
+    return isContinue() ? fn.apply(asContinue()) : this;
   }
 
-  public static <TB, TC> TraversalContinuation<TB, TC> doContinue(TC value) {
+  @SuppressWarnings("unchecked")
+  public static <TB, TC> TraversalContinuation.Continue<TB, TC> doContinue() {
+    return (TraversalContinuation.Continue<TB, TC>) Continue.CONTINUE_NO_VALUE;
+  }
+
+  public static <TB, TC> TraversalContinuation.Continue<TB, TC> doContinue(TC value) {
     return new Continue<>(value);
   }
 
   @SuppressWarnings("unchecked")
-  public static <TB, TC> TraversalContinuation<TB, TC> doBreak() {
-    return (TraversalContinuation<TB, TC>) Break.BREAK_NO_VALUE;
+  public static <TB, TC> TraversalContinuation.Break<TB, TC> doBreak() {
+    return (TraversalContinuation.Break<TB, TC>) Break.BREAK_NO_VALUE;
   }
 
-  public static <TB, TC> TraversalContinuation<TB, TC> doBreak(TB value) {
+  public static <TB, TC> TraversalContinuation.Break<TB, TC> doBreak(TB value) {
     return new Break<>(value);
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/TraversalUtils.java b/src/main/java/com/android/tools/r8/utils/TraversalUtils.java
new file mode 100644
index 0000000..25e1be0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/TraversalUtils.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
+
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class TraversalUtils {
+
+  public static <BT, CT> BT getFirst(
+      Function<Function<BT, TraversalContinuation<BT, CT>>, TraversalContinuation<BT, CT>>
+          traversal) {
+    return traversal.apply(TraversalContinuation::doBreak).asBreak().getValue();
+  }
+
+  public static <BT, CT> boolean isSingleton(
+      Consumer<Function<CT, TraversalContinuation<BT, CT>>> traversal) {
+    return isSizeExactly(traversal, 1);
+  }
+
+  public static <BT, CT> boolean isSizeExactly(
+      Consumer<Function<CT, TraversalContinuation<BT, CT>>> traversal, int value) {
+    IntBox counter = new IntBox();
+    traversal.accept(
+        ignoreArgument(() -> TraversalContinuation.breakIf(counter.incrementAndGet() > value)));
+    return counter.get() == value;
+  }
+
+  public static <BT, CT> boolean isSizeGreaterThan(
+      Consumer<Function<CT, TraversalContinuation<BT, CT>>> traversal, int value) {
+    IntBox counter = new IntBox();
+    traversal.accept(
+        ignoreArgument(() -> TraversalContinuation.breakIf(counter.incrementAndGet() > value)));
+    return counter.get() > value;
+  }
+
+  public static <S, BT, CT> TraversalContinuation<BT, CT> traverseIterable(
+      Iterable<S> iterable,
+      BiFunction<? super S, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    TraversalContinuation<BT, CT> traversalContinuation =
+        TraversalContinuation.doContinue(initialValue);
+    for (S element : iterable) {
+      traversalContinuation =
+          fn.apply(element, traversalContinuation.asContinue().getValueOrDefault(null));
+      if (traversalContinuation.isBreak()) {
+        break;
+      }
+    }
+    return traversalContinuation;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/classhierarchy/MethodOverridesCollector.java b/src/main/java/com/android/tools/r8/utils/classhierarchy/MethodOverridesCollector.java
index 7dc7b3e..32ab490 100644
--- a/src/main/java/com/android/tools/r8/utils/classhierarchy/MethodOverridesCollector.java
+++ b/src/main/java/com/android/tools/r8/utils/classhierarchy/MethodOverridesCollector.java
@@ -111,7 +111,7 @@
           SingleResolutionResult<?> resolutionResult =
               appView
                   .appInfo()
-                  .resolveMethodOnClass(implementer, interfaceMethod)
+                  .resolveMethodOnClassLegacy(implementer, interfaceMethod)
                   .asSingleResolution();
           if (resolutionResult == null || !resolutionResult.getResolvedHolder().isProgramClass()) {
             continue;
diff --git a/src/main/keep.txt b/src/main/keep.txt
index e376924..deb8e6d 100644
--- a/src/main/keep.txt
+++ b/src/main/keep.txt
@@ -12,7 +12,6 @@
 -keep public class com.android.tools.r8.D8 { public static void main(java.lang.String[]); }
 -keep public class com.android.tools.r8.R8 { public static void main(java.lang.String[]); }
 -keep public class com.android.tools.r8.ExtractMarker { public static void main(java.lang.String[]); }
--keep public class com.android.tools.r8.dexsplitter.DexSplitter { public static void main(java.lang.String[]); }
 
 -keep public class com.android.tools.r8.Version { public static final java.lang.String LABEL; }
 -keep public class com.android.tools.r8.Version { public static java.lang.String getVersionString(); }
diff --git a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
index 318b3c2..8ba7823 100644
--- a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
+++ b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
@@ -111,7 +111,7 @@
         testBuilder
             .run(parameters.getRuntime(), "MySubscriber")
             .applyIf(
-                parameters.asDexRuntime().getVersion().isOlderThan(DexVm.Version.V13_MASTER),
+                parameters.asDexRuntime().getVersion().isOlderThan(DexVm.Version.V13_0_0),
                 b ->
                     b.assertFailureWithErrorThatMatches(
                         anyOf(
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index cf27d33..1ec4d18 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -123,13 +123,10 @@
     return self();
   }
 
-  // TODO(b/183125319): Make this the default as part of API support in D8.
   public D8TestBuilder internalEnableMappingOutput() {
     assert proguardMapOutputBuilder == null;
     proguardMapOutputBuilder = new StringBuilder();
-    // TODO(b/183125319): Use the API once supported in D8.
-    addOptionsModification(
-        o -> o.proguardMapConsumer = (s, h) -> proguardMapOutputBuilder.append(s));
+    getBuilder().setProguardMapConsumer((s, h) -> proguardMapOutputBuilder.append(s));
     return self();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index c860721..10c5358 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -102,7 +102,7 @@
           DexVm.Version.V9_0_0,
           DexVm.Version.V10_0_0,
           DexVm.Version.V12_0_0,
-          DexVm.Version.V13_MASTER);
+          DexVm.Version.V13_0_0);
 
   private static final String JUNIT_TEST_RUNNER = "org.junit.runner.JUnitCore";
   private static final String JUNIT_JAR = "third_party/junit/junit-4.13-beta-2.jar";
@@ -186,13 +186,13 @@
           .put(
               "098-ddmc",
               TestCondition.match(
-                  TestCondition.runtimes(DexVm.Version.V12_0_0, DexVm.Version.V13_MASTER)))
+                  TestCondition.runtimes(DexVm.Version.V12_0_0, DexVm.Version.V13_0_0)))
           // TODO(b/197079442): Triage - fails with "java.lang.NoSuchMethodException:
           //  org.apache.harmony.dalvik.ddmc.DdmVmInternal.enableRecentAllocations [boolean]"
           .put(
               "145-alloc-tracking-stress",
               TestCondition.match(
-                  TestCondition.runtimes(DexVm.Version.V12_0_0, DexVm.Version.V13_MASTER)))
+                  TestCondition.runtimes(DexVm.Version.V12_0_0, DexVm.Version.V13_0_0)))
           .build();
 
   // Tests that are flaky with the Art version we currently use.
@@ -499,7 +499,7 @@
     ImmutableMap.Builder<DexVm.Version, List<String>> builder = ImmutableMap.builder();
     builder
         .put(
-            DexVm.Version.V13_MASTER,
+            DexVm.Version.V13_0_0,
             ImmutableList.of("454-get-vreg", "457-regs", "543-env-long-ref", "518-null-array-get"))
         .put(
             DexVm.Version.V12_0_0,
@@ -848,7 +848,7 @@
                           DexVm.Version.V5_1_1,
                           DexVm.Version.V6_0_1,
                           DexVm.Version.V7_0_0,
-                          DexVm.Version.V13_MASTER)),
+                          DexVm.Version.V13_0_0)),
                   TestCondition.match(
                       TestCondition.compilers(
                           CompilerUnderTest.R8,
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 8478b17..a7759e3 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -90,7 +90,7 @@
               // TODO(120402963) Triage.
               ImmutableList.of("invokecustom-with-shrinking", "invokecustom2-with-shrinking"))
           .put(
-              Version.V13_MASTER,
+              Version.V13_0_0,
               // TODO(120402963) Triage.
               ImmutableList.of("invokecustom-with-shrinking", "invokecustom2-with-shrinking"))
           .put(Version.DEFAULT, ImmutableList.of())
diff --git a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
index 1d6cb9a..e1064b0 100644
--- a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
@@ -15,7 +15,6 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -35,8 +34,79 @@
   @Rule
   public ExpectedException thrown = ExpectedException.none();
 
+  @Parameters(name = "{0}: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(), tests.keySet());
+  }
+
   private static final String SMALI_DIR = ToolHelper.SMALI_BUILD_DIR;
 
+  private static Map<String, String> tests;
+
+  static {
+    ImmutableMap.Builder<String, String> testsBuilder = ImmutableMap.builder();
+    testsBuilder
+        .put(
+            "arithmetic",
+            StringUtils.lines(
+                "-1", "3", "2", "3", "3.0", "1", "0", "-131580", "-131580", "2", "4", "-2"))
+        .put(
+            "controlflow",
+            StringUtils.lines("2", "1", "2", "1", "2", "1", "2", "1", "2", "1", "2", "1", "2"))
+        .put("fibonacci", StringUtils.lines("55", "55", "55", "55"))
+        .put("fill-array-data", "[1, 2, 3][4, 5, 6]")
+        .put("filled-new-array", "[1, 2, 3][4, 5, 6][1, 2, 3, 4, 5, 6][6, 5, 4, 3, 2, 1]")
+        .put("packed-switch", "12345")
+        .put("sparse-switch", "12345")
+        .put("unreachable-code-1", "777")
+        .put(
+            "multiple-returns",
+            StringUtils.lines("TFtf", "1", "4611686018427387904", "true", "false"))
+        .put("try-catch", "")
+        .put("phi-removal-regression", StringUtils.lines("returnBoolean"))
+        .put(
+            "overlapping-long-registers",
+            StringUtils.lines("-9151314442816847872", "-9151314442816319488"))
+        .put(
+            "type-confusion-regression",
+            StringUtils.lines("java.lang.RuntimeException: Test.<init>()"))
+        .put(
+            "type-confusion-regression2",
+            StringUtils.lines("java.lang.NullPointerException: Attempt to read from null array"))
+        .put(
+            "type-confusion-regression3",
+            StringUtils.lines(
+                "java.lang.NullPointerException: Attempt to read from field 'byte[] Test.a'"
+                    + " on a null object reference"))
+        .put("type-confusion-regression4", "")
+        .put(
+            "type-confusion-regression5", StringUtils.lines("java.lang.RuntimeException: getId()I"))
+        .put("chain-of-loops", StringUtils.lines("java.lang.RuntimeException: f(II)"))
+        .put("new-instance-and-init", StringUtils.lines("Test(0)", "Test(0)", "Test(0)"))
+        .put(
+            "bad-codegen",
+            StringUtils.lines(
+                "java.lang.NullPointerException: Attempt to read from field "
+                    + "'Test Test.a' on a null object reference"))
+        .put(
+            "merge-blocks-regression",
+            StringUtils.lines(
+                "java.lang.NullPointerException: Attempt to invoke virtual"
+                    + " method 'Test Test.bW_()' on a null object reference"))
+        .put("self-is-catch-block", StringUtils.lines("100", "-1"))
+        .put("infinite-loop", "")
+        .put(
+            "regression/33336471",
+            StringUtils.lines(
+                "START", "0", "2", "LOOP", "1", "2", "LOOP", "2", "2", "DONE", "START", "0", "2",
+                "LOOP", "1", "2", "LOOP", "2", "2", "DONE"))
+        .put("regression/33846227", "")
+        .put("illegal-invokes", StringUtils.lines("ICCE", "ICCE"))
+        .build();
+    tests = testsBuilder.build();
+  }
+
   private static Map<String, Set<String>> missingClasses =
       ImmutableMap.of(
           "try-catch", ImmutableSet.of("test.X"),
@@ -44,60 +114,63 @@
           "bad-codegen", ImmutableSet.of("java.util.LTest"));
 
   // Tests where the original smali code fails on Art, but runs after R8 processing.
-  private static Map<DexVm.Version, List<String>> originalFailingOnArtVersions = ImmutableMap.of(
-      Version.V5_1_1, ImmutableList.of(
-          // Smali code contains an empty switch payload.
-          "sparse-switch",
-          "regression/33846227"
-      ),
-      Version.V4_4_4, ImmutableList.of(
-          // Smali code contains an empty switch payload.
-          "sparse-switch",
-          "regression/33846227"
-      ),
-      Version.V4_0_4, ImmutableList.of(
-          // Smali code contains an empty switch payload.
-          "sparse-switch",
-          "regression/33846227"
-      )
-  );
+  private static final Map<DexVm.Version, List<String>> originalFailingOnArtVersions =
+      ImmutableMap.of(
+          Version.V5_1_1,
+              ImmutableList.of(
+                  // Smali code contains an empty switch payload.
+                  "sparse-switch", "regression/33846227"),
+          Version.V4_4_4,
+              ImmutableList.of(
+                  // Smali code contains an empty switch payload.
+                  "sparse-switch", "regression/33846227"),
+          Version.V4_0_4,
+              ImmutableList.of(
+                  // Smali code contains an empty switch payload.
+                  "sparse-switch", "regression/33846227"));
 
   // Tests where the output has a different output than the original on certain VMs.
-  private static Map<DexVm.Version, Map<String, String>> customProcessedOutputExpectation =
+  private static final Map<DexVm.Version, Map<String, String>> customProcessedOutputExpectation =
       ImmutableMap.of(
-          Version.V4_4_4, ImmutableMap.of(
-              "bad-codegen", "java.lang.NullPointerException\n",
-              "type-confusion-regression2", "java.lang.NullPointerException\n",
-              "type-confusion-regression3", "java.lang.NullPointerException\n",
-              "merge-blocks-regression", "java.lang.NullPointerException\n"
-          ),
-          Version.V4_0_4, ImmutableMap.of(
-              "bad-codegen", "java.lang.NullPointerException\n",
-              "type-confusion-regression2", "java.lang.NullPointerException\n",
-              "type-confusion-regression3", "java.lang.NullPointerException\n",
-              "merge-blocks-regression", "java.lang.NullPointerException\n"
-          )
-      );
+          Version.V4_4_4,
+              ImmutableMap.of(
+                  "bad-codegen", "java.lang.NullPointerException\n",
+                  "type-confusion-regression2", "java.lang.NullPointerException\n",
+                  "type-confusion-regression3", "java.lang.NullPointerException\n",
+                  "merge-blocks-regression", "java.lang.NullPointerException\n"),
+          Version.V4_0_4,
+              ImmutableMap.of(
+                  "bad-codegen", "java.lang.NullPointerException\n",
+                  "type-confusion-regression2", "java.lang.NullPointerException\n",
+                  "type-confusion-regression3", "java.lang.NullPointerException\n",
+                  "merge-blocks-regression", "java.lang.NullPointerException\n"),
+          Version.V13_0_0,
+              ImmutableMap.of(
+                  "bad-codegen",
+                      StringUtils.lines(
+                          "java.lang.NullPointerException: Attempt to read from field 'Test Test.a'"
+                              + " on a null object reference in method 'Test TestObject.a(Test,"
+                              + " Test, Test, Test, boolean)'"),
+                  "type-confusion-regression3",
+                      StringUtils.lines(
+                          "java.lang.NullPointerException: Attempt to read from field 'byte[]"
+                              + " Test.a' on a null object reference in method 'int"
+                              + " TestObject.a(Test, Test)'")));
 
   // Tests where the input fails with a verification error on Dalvik instead of the
   // expected runtime exception.
-  private static Map<DexVm.Version, List<String>> dalvikVerificationError = ImmutableMap.of(
-      Version.V4_4_4, ImmutableList.of(
-          // The invokes are in fact invalid, but the test expects the current Art behavior
-          // of throwing an IncompatibleClassChange exception. Dalvik fails to verify.
-          "illegal-invokes"
-      ),
-      Version.V4_0_4, ImmutableList.of(
-          // The invokes are in fact invalid, but the test expects the current Art behavior
-          // of throwing an IncompatibleClassChange exception. Dalvik fails to verify.
-          "illegal-invokes"
-      )
-  );
-
-  // Tests where the original smali code runs on Art, but fails after R8 processing
-  private static Map<String, List<String>> failingOnArtVersions = ImmutableMap.of(
-      // This list is currently empty!
-  );
+  private static final Map<DexVm.Version, List<String>> dalvikVerificationErrors =
+      ImmutableMap.of(
+          Version.V4_4_4,
+              ImmutableList.of(
+                  // The invokes are in fact invalid, but the test expects the current Art behavior
+                  // of throwing an IncompatibleClassChange exception. Dalvik fails to verify.
+                  "illegal-invokes"),
+          Version.V4_0_4,
+              ImmutableList.of(
+                  // The invokes are in fact invalid, but the test expects the current Art behavior
+                  // of throwing an IncompatibleClassChange exception. Dalvik fails to verify.
+                  "illegal-invokes"));
 
   private Set<String> failingOnX8 = ImmutableSet.of(
       // Contains use of register as both an int and a float.
@@ -110,59 +183,23 @@
   @Rule
   public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
 
-  @Parameters(name = "{0}")
-  public static Collection<String[]> data() {
-    return Arrays.asList(new String[][]{
-        {"arithmetic",
-            StringUtils.lines("-1", "3", "2", "3", "3.0", "1", "0", "-131580", "-131580", "2", "4",
-                "-2")},
-        {"controlflow",
-            StringUtils.lines("2", "1", "2", "1", "2", "1", "2", "1", "2", "1", "2", "1", "2")},
-        {"fibonacci", StringUtils.lines("55", "55", "55", "55")},
-        {"fill-array-data", "[1, 2, 3][4, 5, 6]"},
-        {"filled-new-array", "[1, 2, 3][4, 5, 6][1, 2, 3, 4, 5, 6][6, 5, 4, 3, 2, 1]"},
-        {"packed-switch", "12345"},
-        {"sparse-switch", "12345"},
-        {"unreachable-code-1", "777"},
-        {"multiple-returns",
-            StringUtils.lines("TFtf", "1", "4611686018427387904", "true", "false")},
-        {"try-catch", ""},
-        {"phi-removal-regression", StringUtils.lines("returnBoolean")},
-        {"overlapping-long-registers",
-            StringUtils.lines("-9151314442816847872", "-9151314442816319488")},
-        {"type-confusion-regression",
-            StringUtils.lines("java.lang.RuntimeException: Test.<init>()")},
-        {"type-confusion-regression2",
-            StringUtils.lines("java.lang.NullPointerException: Attempt to read from null array")},
-        {"type-confusion-regression3",
-            StringUtils.lines(
-                "java.lang.NullPointerException: Attempt to read from field 'byte[] Test.a'" +
-                    " on a null object reference")},
-        {"type-confusion-regression4", ""},
-        {"type-confusion-regression5", StringUtils.lines("java.lang.RuntimeException: getId()I")},
-        {"chain-of-loops", StringUtils.lines("java.lang.RuntimeException: f(II)")},
-        {"new-instance-and-init", StringUtils.lines("Test(0)", "Test(0)", "Test(0)")},
-        {"bad-codegen",
-            StringUtils.lines("java.lang.NullPointerException: Attempt to read from field " +
-                "'Test Test.a' on a null object reference")},
-        {"merge-blocks-regression",
-            StringUtils.lines("java.lang.NullPointerException: Attempt to invoke virtual"
-                + " method 'Test Test.bW_()' on a null object reference")},
-        {"self-is-catch-block", StringUtils.lines("100", "-1")},
-        {"infinite-loop", ""},
-        {"regression/33336471",
-            StringUtils.lines("START", "0", "2", "LOOP", "1", "2", "LOOP", "2", "2", "DONE",
-                "START", "0", "2", "LOOP", "1", "2", "LOOP", "2", "2", "DONE")},
-        {"regression/33846227", ""},
-        {"illegal-invokes", StringUtils.lines("ICCE", "ICCE")},
-    });
-  }
+  private final TestParameters parameters;
+  private final String directoryName;
+  private final String dexFileName;
+  private final String expectedOutput;
 
-  private String directoryName;
-  private String dexFileName;
-  private String expectedOutput;
-
-  public R8RunSmaliTestsTest(String name, String expectedOutput) {
+  public R8RunSmaliTestsTest(TestParameters parameters, String name) {
+    this.parameters = parameters;
+    String expectedOutput = tests.get(name);
+    if (customProcessedOutputExpectation.containsKey(parameters.asDexRuntime().getVersion())
+        && customProcessedOutputExpectation
+            .get(parameters.asDexRuntime().getVersion())
+            .containsKey(name)) {
+      // If the original and the processed code have different expected output, only run
+      // the code produced by R8.
+      expectedOutput =
+          customProcessedOutputExpectation.get(parameters.asDexRuntime().getVersion()).get(name);
+    }
     this.directoryName = name;
     this.dexFileName = name.substring(name.lastIndexOf('/') + 1) + ".dex";
     this.expectedOutput = expectedOutput;
@@ -171,56 +208,40 @@
   @Test
   public void SmaliTest() throws Exception {
     Path originalDexFile = Paths.get(SMALI_DIR, directoryName, dexFileName);
-    Path outputPath = temp.getRoot().toPath().resolve("classes.dex");
+    // Path outputPath = temp.getRoot().toPath().resolve("classes.dex");
 
     if (failingOnX8.contains(directoryName)) {
       thrown.expect(CompilationFailedException.class);
     }
 
-    testForR8(Backend.DEX)
+    Version version = parameters.asDexRuntime().getVersion();
+    boolean dalvikVerificationError =
+        dalvikVerificationErrors.containsKey(version)
+            && dalvikVerificationErrors.get(version).contains(directoryName);
+    boolean originalFailing =
+        (originalFailingOnArtVersions.containsKey(version)
+            && originalFailingOnArtVersions.get(version).contains(directoryName));
+    testForR8(parameters.getBackend())
         .addKeepAllClassesRule()
         .addProgramDexFileData(Files.readAllBytes(originalDexFile))
         .addDontWarn(missingClasses.getOrDefault(directoryName, Collections.emptySet()))
+        .setMinApi(parameters.getApiLevel())
         .compile()
-        .writeToZip(outputPath);
+        .run(parameters.getRuntime(), "Test")
+        .applyIf(
+            dalvikVerificationError,
+            r -> r.assertFailureWithErrorThatThrows(VerifyError.class),
+            r -> r.assertSuccessWithOutput(expectedOutput));
 
-    if (!ToolHelper.artSupported()) {
-      return;
+    // Also run the original DEX if possible.
+    if (!dalvikVerificationError && !originalFailing) {
+      String originalOutput =
+          ToolHelper.runArtNoVerificationErrors(
+              ImmutableList.of(originalDexFile.toString()),
+              "Test",
+              null,
+              parameters.getRuntime().asDex().getVm());
+      assertEquals(expectedOutput, originalOutput);
     }
-
-    String mainClass = "Test";
-    String generated = outputPath.toString();
-    String output = "";
-
-    DexVm.Version dexVmVersion = ToolHelper.getDexVm().getVersion();
-    if (dalvikVerificationError.containsKey(dexVmVersion)
-        && dalvikVerificationError.get(dexVmVersion).contains(directoryName)) {
-      try {
-        ToolHelper.runArtNoVerificationErrors(generated, mainClass);
-      } catch (AssertionError e) {
-        assert e.toString().contains("VerifyError");
-      }
-      return;
-    } else if (originalFailingOnArtVersions.containsKey(dexVmVersion)
-        && originalFailingOnArtVersions.get(dexVmVersion).contains(directoryName)) {
-      // If the original smali code fails on the target VM, only run the code produced by R8.
-      output = ToolHelper.runArtNoVerificationErrors(generated, mainClass);
-    } else if (customProcessedOutputExpectation.containsKey(dexVmVersion)
-        && customProcessedOutputExpectation.get(dexVmVersion).containsKey(directoryName)) {
-      // If the original and the processed code have different expected output, only run
-      // the code produced by R8.
-      expectedOutput =
-          customProcessedOutputExpectation.get(dexVmVersion).get(directoryName);
-      output = ToolHelper.runArtNoVerificationErrors(generated, mainClass);
-    } else {
-      if (failingOnArtVersions.containsKey(dexVmVersion)
-          && failingOnArtVersions.get(dexVmVersion).contains(directoryName)) {
-        thrown.expect(Throwable.class);
-      }
-      output =
-          ToolHelper
-              .checkArtOutputIdentical(originalDexFile.toString(), generated, mainClass, null);
-    }
-    assertEquals(expectedOutput, output);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index e6cd425..7fe1ae0 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -310,7 +310,7 @@
                 // TODO(b/120402963): Triage.
                 "invokecustom", "invokecustom2"))
         .put(
-            Version.V13_MASTER,
+            Version.V13_0_0,
             ImmutableList.of(
                 // TODO(b/120402963): Triage.
                 "invokecustom", "invokecustom2"))
diff --git a/src/test/java/com/android/tools/r8/SanityCheck.java b/src/test/java/com/android/tools/r8/SanityCheck.java
index df1486c..c461475 100644
--- a/src/test/java/com/android/tools/r8/SanityCheck.java
+++ b/src/test/java/com/android/tools/r8/SanityCheck.java
@@ -117,7 +117,6 @@
 
   @Test
   public void testJarsContent() throws Exception {
-    checkJarContent(ToolHelper.D8_JAR);
     checkJarContent(ToolHelper.R8_JAR);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 21ca89e..097e4bc 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -785,7 +785,7 @@
     return computeAppViewWithClassHierarchy(app, keepConfig, null);
   }
 
-  private static AppView<AppInfoWithClassHierarchy> computeAppViewWithClassHierarchy(
+  protected static AppView<AppInfoWithClassHierarchy> computeAppViewWithClassHierarchy(
       AndroidApp app,
       Function<DexItemFactory, ProguardConfiguration> keepConfig,
       Consumer<InternalOptions> optionsConsumer)
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 719dbd8..48f9fc5 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -331,6 +331,15 @@
     }
   }
 
+  public CR addBootClasspathFiles(Path... files) {
+    return addBootClasspathFiles(Arrays.asList(files));
+  }
+
+  public CR addBootClasspathFiles(Collection<Path> files) {
+    additionalBootClasspath.addAll(files);
+    return self();
+  }
+
   public CR addDesugaredCoreLibraryRunClassPath(
       Function<AndroidApiLevel, Path> classPathSupplier, AndroidApiLevel minAPILevel) {
     addRunClasspathFiles(classPathSupplier.apply(minAPILevel));
diff --git a/src/test/java/com/android/tools/r8/TestCondition.java b/src/test/java/com/android/tools/r8/TestCondition.java
index cd0a1e7..b1a6207 100644
--- a/src/test/java/com/android/tools/r8/TestCondition.java
+++ b/src/test/java/com/android/tools/r8/TestCondition.java
@@ -53,7 +53,7 @@
           return ART_V10_0_0;
         case V12_0_0:
           return ART_V12_0_0;
-        case V13_MASTER:
+        case V13_0_0:
           return ART_V13_0_0;
         case DEFAULT:
           return ART_DEFAULT;
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 7bcdfb6..f219969 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -161,7 +161,6 @@
 
   public static final long BOT_MAX_HEAP_SIZE = 7908360192L;
 
-  public static final Path D8_JAR = Paths.get(LIBS_DIR, "d8.jar");
   public static final Path R8_JAR = Paths.get(LIBS_DIR, "r8.jar");
   public static final Path R8_WITH_DEPS_JAR = Paths.get(LIBS_DIR, "r8_with_deps.jar");
   public static final Path R8_WITHOUT_DEPS_JAR =
@@ -204,12 +203,16 @@
     return Paths.get(getDesugarLibraryJsonDir(), "desugar_jdk_libs.json");
   }
 
+  public static Path getDesugarLibJsonForTestingWithPath() {
+    return Paths.get(getDesugarLibraryJsonDir(), "desugar_jdk_libs_path.json");
+  }
+
   public static Path getCHMOnlyDesugarLibJsonForTesting() {
     return Paths.get(getDesugarLibraryJsonDir(), "chm_only_desugar_jdk_libs.json");
   }
 
   public static Path getDesugarLibJsonForTestingAlternative3() {
-    return Paths.get(getDesugarLibraryJsonDir(), "desugar_jdk_libs_alternative_3.json");
+    return Paths.get(getDesugarLibraryJsonDir(), "desugar_jdk_libs_path_alternative_3.json");
   }
 
   public static boolean isLocalDevelopment() {
@@ -271,8 +274,8 @@
     ART_10_0_0_HOST(Version.V10_0_0, Kind.HOST),
     ART_12_0_0_TARGET(Version.V12_0_0, Kind.TARGET),
     ART_12_0_0_HOST(Version.V12_0_0, Kind.HOST),
-    ART_13_0_0_TARGET(Version.V13_MASTER, Kind.TARGET),
-    ART_13_0_0_HOST(Version.V13_MASTER, Kind.HOST);
+    ART_13_0_0_TARGET(Version.V13_0_0, Kind.TARGET),
+    ART_13_0_0_HOST(Version.V13_0_0, Kind.HOST);
 
     private static final ImmutableMap<String, DexVm> SHORT_NAME_MAP =
         Arrays.stream(DexVm.values()).collect(ImmutableMap.toImmutableMap(
@@ -290,7 +293,7 @@
       V9_0_0("9.0.0"),
       V10_0_0("10.0.0"),
       V12_0_0("12.0.0"),
-      V13_MASTER("13.0.0");
+      V13_0_0("13.0.0");
 
       /** This should generally be the latest DEX VM fully supported. */
       // TODO(b/204855476): Rename to DEFAULT alias once the checked in VM is removed.
@@ -352,7 +355,7 @@
       }
 
       public static Version last() {
-        return V13_MASTER;
+        return V13_0_0;
       }
 
       static {
@@ -623,7 +626,7 @@
   private static final Map<DexVm, String> ART_DIRS =
       ImmutableMap.<DexVm, String>builder()
           .put(DexVm.ART_DEFAULT, "art")
-          .put(DexVm.ART_13_0_0_HOST, "host/art-13-master")
+          .put(DexVm.ART_13_0_0_HOST, "host/art-13-dev")
           .put(DexVm.ART_12_0_0_HOST, "host/art-12.0.0-beta4")
           .put(DexVm.ART_10_0_0_HOST, "art-10.0.0")
           .put(DexVm.ART_9_0_0_HOST, "art-9.0.0")
@@ -673,14 +676,21 @@
           "core-oj-hostdex.jar",
           "apache-xml-hostdex.jar");
 
+  private static final List<String> NEWER_ART_BOOT_LIBS =
+      ImmutableList.of(
+          "core-libart-hostdex.jar",
+          "core-oj-hostdex.jar",
+          "core-icu4j-hostdex.jar",
+          "apache-xml-hostdex.jar");
+
   private static final Map<DexVm, List<String>> BOOT_LIBS;
 
   static {
     ImmutableMap.Builder<DexVm, List<String>> builder = ImmutableMap.builder();
     builder
         .put(DexVm.ART_DEFAULT, ART_BOOT_LIBS)
-        .put(DexVm.ART_13_0_0_HOST, ART_BOOT_LIBS)
-        .put(DexVm.ART_12_0_0_HOST, ART_BOOT_LIBS)
+        .put(DexVm.ART_13_0_0_HOST, NEWER_ART_BOOT_LIBS)
+        .put(DexVm.ART_12_0_0_HOST, NEWER_ART_BOOT_LIBS)
         .put(DexVm.ART_10_0_0_HOST, ART_BOOT_LIBS)
         .put(DexVm.ART_9_0_0_HOST, ART_BOOT_LIBS)
         .put(DexVm.ART_8_1_0_HOST, ART_BOOT_LIBS)
@@ -1027,7 +1037,7 @@
 
   public static AndroidApiLevel getMinApiLevelForDexVm(DexVm dexVm) {
     switch (dexVm.version) {
-      case V13_MASTER:
+      case V13_0_0:
         return AndroidApiLevel.T;
       case V12_0_0:
         return AndroidApiLevel.S;
@@ -1996,7 +2006,7 @@
         "b/144975341",
         vm.version == DexVm.Version.V10_0_0
             || vm.version == DexVm.Version.V12_0_0
-            || vm.version == DexVm.Version.V13_MASTER);
+            || vm.version == DexVm.Version.V13_0_0);
     if (vm.isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
       // Run default dex2oat for tests on dalvik runtimes.
       vm = DexVm.ART_DEFAULT;
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassInstanceInitTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassInstanceInitTest.java
index 784fce6..61a9e78 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassInstanceInitTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassInstanceInitTest.java
@@ -9,7 +9,6 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
-import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -58,10 +57,7 @@
 
   @Test
   public void testD8Debug() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeTrue(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8()
         .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
@@ -74,10 +70,7 @@
 
   @Test
   public void testD8Release() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isCfRuntime()
-            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8()
         .setMode(CompilationMode.RELEASE)
         // TODO(b/213552119): Remove when enabled by default.
@@ -92,10 +85,6 @@
 
   @Test
   public void testR8() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
     testForR8(parameters.getBackend())
         .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java
index 8651a23..ec4a09e 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
-import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -16,7 +15,6 @@
 import com.android.tools.r8.TestCompilerBuilder;
 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.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
@@ -54,10 +52,7 @@
 
   @Test
   public void testD8Debug() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeTrue(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8()
         .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
@@ -70,10 +65,7 @@
 
   @Test
   public void testD8Release() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isCfRuntime()
-            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8()
         .setMode(CompilationMode.RELEASE)
         // TODO(b/213552119): Remove when enabled by default.
@@ -88,10 +80,6 @@
 
   @Test
   public void testR8() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
     testForR8(parameters.getBackend())
         .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java
index 1397da7..b999875 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
-import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -17,7 +16,6 @@
 import com.android.tools.r8.TestCompilerBuilder;
 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.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -62,10 +60,7 @@
 
   @Test
   public void testD8Debug() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeTrue(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8()
         .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
@@ -78,10 +73,7 @@
 
   @Test
   public void testD8Release() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isCfRuntime()
-            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8()
         .setMode(CompilationMode.RELEASE)
         // TODO(b/213552119): Remove when enabled by default.
@@ -96,10 +88,6 @@
 
   @Test
   public void testR8() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
     testForR8(parameters.getBackend())
         .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockInheritedClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockInheritedClassTest.java
index 3d6d30c..78c5fc1 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockInheritedClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockInheritedClassTest.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
-import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -16,7 +15,6 @@
 import com.android.tools.r8.TestCompilerBuilder;
 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.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -61,10 +59,7 @@
 
   @Test
   public void testD8Debug() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeTrue(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8()
         .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
@@ -77,10 +72,7 @@
 
   @Test
   public void testD8Release() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isCfRuntime()
-            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8()
         .setMode(CompilationMode.RELEASE)
         // TODO(b/213552119): Remove when enabled by default.
@@ -95,10 +87,6 @@
 
   @Test
   public void testR8() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
     testForR8(parameters.getBackend())
         .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockSuperChainClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockSuperChainClassTest.java
index c2a6d4d..d6911db 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockSuperChainClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockSuperChainClassTest.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
-import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -16,7 +15,6 @@
 import com.android.tools.r8.TestCompilerBuilder;
 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.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -71,10 +69,7 @@
 
   @Test
   public void testD8Debug() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeTrue(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8()
         .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
@@ -92,10 +87,7 @@
 
   @Test
   public void testD8Release() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isCfRuntime()
-            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8()
         .setMode(CompilationMode.RELEASE)
         // TODO(b/213552119): Remove when enabled by default.
@@ -115,10 +107,6 @@
 
   @Test
   public void testR8() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
     testForR8(parameters.getBackend())
         .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoOutlineForFullyMockedTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoOutlineForFullyMockedTest.java
index 85429ea..a3183b0 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoOutlineForFullyMockedTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoOutlineForFullyMockedTest.java
@@ -11,7 +11,6 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.NeverInline;
@@ -21,7 +20,6 @@
 import com.android.tools.r8.TestCompilerBuilder;
 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.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
@@ -67,10 +65,7 @@
 
   @Test
   public void testD8() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeTrue(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     String result;
     if (parameters.isDexRuntime()
         && parameters.getApiLevel().isGreaterThanOrEqualTo(libraryApiLevel)) {
@@ -90,10 +85,6 @@
 
   @Test
   public void testR8() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
     testForR8(parameters.getBackend())
         .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java
index e8d6257..57eaa67 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java
@@ -13,7 +13,6 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -23,7 +22,6 @@
 import com.android.tools.r8.TestCompilerBuilder;
 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.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -77,10 +75,7 @@
 
   @Test
   public void testD8Debug() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isCfRuntime()
-            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8()
         .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
@@ -93,9 +88,7 @@
 
   @Test
   public void testD8Release() throws Exception {
-    assumeFalse(
-        parameters.isCfRuntime()
-            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8()
         .setMode(CompilationMode.RELEASE)
         .apply(this::setupTestBuilder)
@@ -108,9 +101,6 @@
 
   @Test
   public void testR8() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeTrue(
-        parameters.isCfRuntime() || parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
     testForR8(parameters.getBackend())
         .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java
index fb98821..9793b9b 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java
@@ -10,7 +10,6 @@
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -20,7 +19,6 @@
 import com.android.tools.r8.TestCompilerBuilder;
 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.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -90,10 +88,7 @@
 
   @Test
   public void testD8Debug() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeTrue(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8(parameters.getBackend())
         .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
@@ -120,10 +115,7 @@
 
   @Test
   public void testD8Release() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isCfRuntime()
-            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8(parameters.getBackend())
         .setMode(CompilationMode.RELEASE)
         .apply(this::setupTestBuilder)
@@ -138,10 +130,6 @@
 
   @Test
   public void testR8() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
     testForR8(parameters.getBackend())
         .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java
index 2411e94..0b2f270 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java
@@ -8,7 +8,6 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
-import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -17,7 +16,6 @@
 import com.android.tools.r8.TestCompilerBuilder;
 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.apimodel.ApiModelMockClassTest.TestClass;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -69,10 +67,7 @@
 
   @Test
   public void testD8Debug() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeTrue(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8(parameters.getBackend())
         .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
@@ -85,10 +80,7 @@
 
   @Test
   public void testD8Release() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isCfRuntime()
-            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8(parameters.getBackend())
         .setMode(CompilationMode.RELEASE)
         .apply(this::setupTestBuilder)
@@ -101,10 +93,6 @@
 
   @Test
   public void testR8() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
     testForR8(parameters.getBackend())
         .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java
index cf440c9..0fbd97c 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java
@@ -13,7 +13,6 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -23,7 +22,6 @@
 import com.android.tools.r8.TestCompilerBuilder;
 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.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -87,10 +85,7 @@
 
   @Test
   public void testD8Debug() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeTrue(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8(parameters.getBackend())
         .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
@@ -103,9 +98,7 @@
 
   @Test
   public void testD8Release() throws Exception {
-    assumeFalse(
-        parameters.isCfRuntime()
-            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8(parameters.getBackend())
         .setMode(CompilationMode.RELEASE)
         .apply(this::setupTestBuilder)
@@ -118,10 +111,6 @@
 
   @Test
   public void testR8() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
     testForR8(parameters.getBackend())
         .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodProtectedTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodProtectedTest.java
index d63e01b..39b1db2 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodProtectedTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodProtectedTest.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
-import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -19,7 +18,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.lang.reflect.Method;
@@ -93,10 +91,7 @@
 
   @Test
   public void testD8Debug() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeTrue(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8()
         .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
@@ -108,10 +103,7 @@
 
   @Test
   public void testD8Release() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isCfRuntime()
-            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8()
         .setMode(CompilationMode.RELEASE)
         .apply(this::setupTestBuilder)
@@ -123,10 +115,6 @@
 
   @Test
   public void testR8() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
     testForR8(parameters.getBackend())
         .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodUnknownTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodUnknownTest.java
index 940864c..fdd796b 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodUnknownTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodUnknownTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.apimodel;
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
-import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -14,7 +13,6 @@
 import com.android.tools.r8.TestCompilerBuilder;
 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.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import org.junit.Test;
@@ -56,10 +54,7 @@
 
   @Test
   public void testD8Debug() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeTrue(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8()
         .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
@@ -73,10 +68,7 @@
 
   @Test
   public void testD8Release() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isCfRuntime()
-            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8()
         .setMode(CompilationMode.RELEASE)
         .apply(this::setupTestBuilder)
@@ -90,10 +82,6 @@
 
   @Test
   public void testR8() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
     testForR8(parameters.getBackend())
         .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlinePackagePrivateTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlinePackagePrivateTest.java
index 94ae936..abf4efd 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlinePackagePrivateTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlinePackagePrivateTest.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
-import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -16,7 +15,6 @@
 import com.android.tools.r8.TestCompilerBuilder;
 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.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import org.junit.Test;
@@ -45,9 +43,7 @@
 
   @Test
   public void testD8BootClassPath() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
     assumeTrue(parameters.isDexRuntime());
-    assumeTrue(parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
     compileOnD8()
         .addBootClasspathClasses(LibraryClass.class)
         .run(parameters.getRuntime(), Main.class)
@@ -92,10 +88,7 @@
 
   @Test
   public void testD8Debug() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeTrue(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8()
         .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
@@ -109,10 +102,7 @@
 
   @Test
   public void testD8Release() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeTrue(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    assumeTrue(parameters.isDexRuntime());
     testForD8()
         .apply(this::setupTestBuilder)
         .compile()
@@ -125,10 +115,6 @@
 
   @Test
   public void testR8() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
     testForR8(parameters.getBackend())
         .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
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 e4a1a1a..57aaddc 100644
--- a/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java
@@ -79,7 +79,7 @@
     AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(I.class, I.class);
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory());
     MethodResolutionResult resolutionResult =
-        appView.appInfo().resolveMethodOnInterfaceHolder(method);
+        appView.appInfo().resolveMethodOnInterfaceHolderLegacy(method);
     DexType typeI = buildType(I.class, appView.dexItemFactory());
     DexType typeL = buildType(L.class, appView.dexItemFactory());
     DexType typeA = buildType(A.class, appView.dexItemFactory());
@@ -121,7 +121,7 @@
     AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(I.class, I.class);
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory());
     MethodResolutionResult resolutionResult =
-        appView.appInfo().resolveMethodOnInterfaceHolder(method);
+        appView.appInfo().resolveMethodOnInterfaceHolderLegacy(method);
     DexType typeI = buildType(I.class, appView.dexItemFactory());
     DexType typeL = buildType(L.class, appView.dexItemFactory());
     DexType typeA = buildType(A.class, appView.dexItemFactory());
@@ -162,7 +162,7 @@
     AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(J.class, J.class);
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory());
     MethodResolutionResult resolutionResult =
-        appView.appInfo().resolveMethodOnInterfaceHolder(method);
+        appView.appInfo().resolveMethodOnInterfaceHolderLegacy(method);
     DexType typeI = buildType(I.class, appView.dexItemFactory());
     DexType typeB = buildType(A.class, appView.dexItemFactory());
     DexProgramClass classI = appView.definitionForProgramType(typeI);
@@ -200,7 +200,7 @@
     AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(J.class, A.class);
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory());
     MethodResolutionResult resolutionResult =
-        appView.appInfo().resolveMethodOnInterfaceHolder(method);
+        appView.appInfo().resolveMethodOnInterfaceHolderLegacy(method);
     DexType typeI = buildType(I.class, appView.dexItemFactory());
     DexType typeB = buildType(A.class, appView.dexItemFactory());
     DexProgramClass classI = appView.definitionForProgramType(typeI);
@@ -240,7 +240,7 @@
     AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(I.class, I.class);
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory());
     MethodResolutionResult resolutionResult =
-        appView.appInfo().resolveMethodOnInterfaceHolder(method);
+        appView.appInfo().resolveMethodOnInterfaceHolderLegacy(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/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
index a5e76eb..f3a22bf 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
@@ -5,11 +5,10 @@
 package com.android.tools.r8.debuginfo;
 
 import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileNameAndLineNumber;
-import static com.android.tools.r8.utils.InternalOptions.LineNumberOptimization.ON;
 import static junit.framework.TestCase.assertEquals;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.SourceFileEnvironment;
@@ -74,7 +73,6 @@
         .addProgramClasses(MAIN)
         .setMinApi(parameters.getApiLevel())
         .internalEnableMappingOutput()
-        // TODO(b/191038746): Enable LineNumberOptimization for release builds for DEX PC Output.
         .applyIf(
             apiLevelSupportsPcAndSourceFileOutput(),
             builder ->
@@ -92,7 +90,6 @@
                               return true;
                             }
                           };
-                      options.lineNumberOptimization = ON;
                     }))
         .run(parameters.getRuntime(), MAIN)
         .inspectFailure(
@@ -150,10 +147,7 @@
     List<DexDebugEntry> entries =
         new DexDebugEntryBuilder(main.getMethod(), inspector.getFactory()).build();
     Set<Integer> lines = entries.stream().map(e -> e.line).collect(Collectors.toSet());
-    // Check some of the lines in main are present (not 27 as it may be optimized out).
-    assertTrue(lines.contains(22));
-    assertTrue(lines.contains(23));
-    assertTrue(lines.contains(25));
+    assertFalse(lines.isEmpty());
   }
 
   private void checkExpectedStackTrace(StackTrace stackTrace) {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ApiLevelBackportsTest.java b/src/test/java/com/android/tools/r8/desugar/backports/ApiLevelBackportsTest.java
index d0db2c9..8eff1c5 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/ApiLevelBackportsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ApiLevelBackportsTest.java
@@ -102,7 +102,7 @@
         .setMinApi(AndroidApiLevel.ANDROID_PLATFORM)
         .run(parameters.getRuntime(), TestMathMultiplyExactLongInt.class)
         .applyIf(
-            parameters.getDexRuntimeVersion().isOlderThan(Version.V13_MASTER),
+            parameters.getDexRuntimeVersion().isOlderThan(Version.V13_0_0),
             b ->
                 b.assertFailureWithErrorThatMatches(
                     containsString(
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 93ea904..fbfcb18 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
@@ -7,16 +7,10 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.ImmutableList;
 import java.io.BufferedReader;
 import java.io.StringReader;
 import java.io.UncheckedIOException;
@@ -54,28 +48,6 @@
     return StringUtils.lines("Hello", "Larry", "Page", "Caught java.io.UncheckedIOException");
   }
 
-  DesugaredLibrarySpecification configurationAlternative3(
-      InternalOptions options, boolean libraryCompilation, TestParameters parameters) {
-    // Parse the current configuration and amend the configuration for BufferedReader.lines. The
-    // configuration is the same for both program and library.
-    return DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecification(
-        StringResource.fromFile(ToolHelper.getDesugarLibJsonForTestingAlternative3()),
-        options.dexItemFactory(),
-        options.reporter,
-        libraryCompilation,
-        parameters.getApiLevel().getLevel());
-  }
-
-  private void configurationForProgramCompilation(InternalOptions options) {
-    setDesugaredLibrarySpecificationForTesting(
-        options, configurationAlternative3(options, false, parameters));
-  }
-
-  private void configurationForLibraryCompilation(InternalOptions options) {
-    setDesugaredLibrarySpecificationForTesting(
-        options, configurationAlternative3(options, true, parameters));
-  }
-
   @Test
   public void testBufferedReaderD8Cf() throws Exception {
     Assume.assumeTrue(
@@ -85,7 +57,6 @@
     // Use D8 to desugar with Java classfile output.
     Path jar =
         testForD8(Backend.CF)
-            .addOptionsModification(this::configurationForProgramCompilation)
             .addInnerClasses(BufferedReaderTest.class)
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
@@ -108,13 +79,7 @@
           .disableDesugaring()
           .compile()
           .addDesugaredCoreLibraryRunClassPath(
-              (apiLevel, keepRules, shrink) ->
-                  buildDesugaredLibrary(
-                      apiLevel,
-                      keepRules,
-                      shrink,
-                      ImmutableList.of(),
-                      this::configurationForLibraryCompilation),
+              this::buildDesugaredLibrary,
               parameters.getApiLevel(),
               desugaredLibraryKeepRules,
               shrinkDesugaredLibrary)
@@ -122,8 +87,7 @@
           .assertSuccessWithOutput(expectedOutput());
     } else {
       // Build the desugared library in class file format.
-      Path desugaredLib =
-          getDesugaredLibraryInCF(parameters, this::configurationForLibraryCompilation);
+      Path desugaredLib = getDesugaredLibraryInCF(parameters, opt -> {});
 
       // Run on the JVM with desugared library on classpath.
       testForJvm()
@@ -143,19 +107,12 @@
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .addLibraryFiles(getLibraryFile())
-        .addOptionsModification(this::configurationForProgramCompilation)
         .addInnerClasses(BufferedReaderTest.class)
         .setMinApi(parameters.getApiLevel())
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
         .addDesugaredCoreLibraryRunClassPath(
-            (apiLevel, keepRules, shrink) ->
-                buildDesugaredLibrary(
-                    apiLevel,
-                    keepRules,
-                    shrink,
-                    ImmutableList.of(),
-                    this::configurationForLibraryCompilation),
+            this::buildDesugaredLibrary,
             parameters.getApiLevel(),
             keepRuleConsumer.get(),
             shrinkDesugaredLibrary)
@@ -172,7 +129,6 @@
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(parameters.getBackend())
         .addLibraryFiles(getLibraryFile())
-        .addOptionsModification(this::configurationForProgramCompilation)
         .addInnerClasses(BufferedReaderTest.class)
         .addKeepMainRule(TestClass.class)
         .setMinApi(parameters.getApiLevel())
@@ -180,13 +136,7 @@
         .enableInliningAnnotations()
         .compile()
         .addDesugaredCoreLibraryRunClassPath(
-            (apiLevel, keepRules, shrink) ->
-                buildDesugaredLibrary(
-                    apiLevel,
-                    keepRules,
-                    shrink,
-                    ImmutableList.of(),
-                    this::configurationForLibraryCompilation),
+            this::buildDesugaredLibrary,
             parameters.getApiLevel(),
             keepRuleConsumer.get(),
             shrinkDesugaredLibrary)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java
index 0874b57..dc9725a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java
@@ -35,6 +35,8 @@
           .put("java/lang/DesugarMath", "java/lang/Math")
           .put("java/io/DesugarBufferedReader", "java/io/BufferedReader")
           .put("java/io/DesugarInputStream", "java/io/InputStream")
+          .put("wrapper/adapter/HybridFileSystemProvider", "java/adapter/HybridFileSystemProvider")
+          .put("wrapper/adapter/HybridFileTypeDetector", "java/adapter/HybridFileTypeDetector")
           .build();
 
   public static void main(String[] args) throws Exception {
@@ -47,9 +49,6 @@
       return ToolHelper.getDesugarJDKLibsBazelGeneratedFile();
     }
     Path desugaredLibJDK11Undesugared = Paths.get("build/libs/desugar_jdk_libs_11_undesugared.jar");
-    if (Files.exists(desugaredLibJDK11Undesugared)) {
-      return desugaredLibJDK11Undesugared;
-    }
     return generateUndesugaredJar(desugaredLibJDK11Undesugared);
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FileTypeDetectorTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FileTypeDetectorTest.java
new file mode 100644
index 0000000..b168992
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FileTypeDetectorTest.java
@@ -0,0 +1,173 @@
+// Copyright (c) 2022, 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.jdk11;
+
+import static com.android.tools.r8.ToolHelper.DexVm.Version.V12_0_0;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.spi.FileTypeDetector;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ServiceLoader;
+import org.junit.Assume;
+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 FileTypeDetectorTest extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED_RESULT_DEFAULT_PNG_TYPE_DETECTOR =
+      StringUtils.lines("false", "text/plain", "image/png", "null", "image/png");
+  private static final String EXPECTED_RESULT_NO_DEFAULT_PNG_TYPE_DETECTOR =
+      StringUtils.lines("false", "text/plain", "null", "null", "image/png");
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    // Skip Android 4.4.4 due to missing libjavacrypto.
+    return buildParameters(
+        BooleanUtils.values(),
+        getTestParameters()
+            .withDexRuntime(Version.V4_0_4)
+            .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+            .withAllApiLevels()
+            .build());
+  }
+
+  public FileTypeDetectorTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  private boolean hasDefaultPNGTypeDetector() {
+    return parameters.getDexRuntimeVersion().compareTo(V12_0_0) < 0;
+  }
+
+  private String getExpectedResult() {
+    return hasDefaultPNGTypeDetector()
+        ? EXPECTED_RESULT_DEFAULT_PNG_TYPE_DETECTOR
+        : EXPECTED_RESULT_NO_DEFAULT_PNG_TYPE_DETECTOR;
+  }
+
+  private LibraryDesugaringTestConfiguration pathConfiguration() {
+    return LibraryDesugaringTestConfiguration.builder()
+        .setMinApi(parameters.getApiLevel())
+        .addDesugaredLibraryConfiguration(
+            StringResource.fromFile(ToolHelper.getDesugarLibJsonForTestingWithPath()))
+        .setMode(shrinkDesugaredLibrary ? CompilationMode.RELEASE : CompilationMode.DEBUG)
+        .withKeepRuleConsumer()
+        .build();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    Assume.assumeTrue(isJDK11DesugaredLibrary());
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(getLibraryFile())
+        .addInnerClasses(getClass())
+        .addProgramClasses(GoogleIcon.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(pathConfiguration())
+        .compile()
+        .withArt6Plus64BitsLib()
+        .withArtFrameworks()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(getExpectedResult());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Assume.assumeTrue(isJDK11DesugaredLibrary());
+    testForR8(Backend.DEX)
+        .addLibraryFiles(getLibraryFile())
+        .addInnerClasses(getClass())
+        .addProgramClasses(GoogleIcon.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClass.class)
+        .enableCoreLibraryDesugaring(pathConfiguration())
+        .compile()
+        .withArt6Plus64BitsLib()
+        .withArtFrameworks()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(getExpectedResult());
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) throws Throwable {
+      // FileTypeDetector usage through ServiceLoader.
+      ServiceLoader<FileTypeDetector> serviceLoader = ServiceLoader.load(FileTypeDetector.class);
+      Iterator<FileTypeDetector> iterator = serviceLoader.iterator();
+      System.out.println(iterator.hasNext());
+      while (iterator.hasNext()) {
+        FileTypeDetector fileTypeDetector = iterator.next();
+        System.out.println((fileTypeDetector == null));
+      }
+
+      Path emptyText = Files.createTempFile("example", ".txt");
+      Path png = getGoogleIconPng();
+
+      // FileTypeDetector usage through Files.
+      System.out.println(Files.probeContentType(emptyText));
+      System.out.println(Files.probeContentType(png));
+
+      // Custom file type detector usage.
+      FileTypeDetector fileTypeDetector = new PngFileTypeDetector();
+      System.out.println(fileTypeDetector.probeContentType(emptyText));
+      System.out.println(fileTypeDetector.probeContentType(png));
+    }
+
+    private static Path getGoogleIconPng() throws IOException {
+      Path picture = Files.createTempFile("art", ".png");
+      Files.write(picture, GoogleIcon.GOOGLE_ICON_PNG);
+      return picture;
+    }
+  }
+
+  public static class PngFileTypeDetector extends FileTypeDetector {
+
+    private static final byte[] PNG_HEADER = {
+      (byte) 0x89,
+      (byte) 0x50,
+      (byte) 0x4E,
+      (byte) 0x47,
+      (byte) 0x0D,
+      (byte) 0x0A,
+      (byte) 0x1A,
+      (byte) 0x0A
+    };
+
+    private static final int PNG_HEADER_SIZE = PNG_HEADER.length;
+
+    @Override
+    public String probeContentType(final Path path) throws IOException {
+      final byte[] buf = new byte[PNG_HEADER_SIZE];
+
+      try (final InputStream in = Files.newInputStream(path); ) {
+        if (in.read(buf) != PNG_HEADER_SIZE) {
+          return null;
+        }
+      }
+
+      return Arrays.equals(buf, PNG_HEADER) ? "image/png" : null;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java
new file mode 100644
index 0000000..7b78157
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java
@@ -0,0 +1,177 @@
+// Copyright (c) 2022, 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.jdk11;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+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.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.List;
+import org.junit.Assume;
+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 FilesTest extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "bytes written: 11",
+          "String written: Hello World",
+          "bytes read: 11",
+          "String read: Hello World",
+          "null",
+          "true",
+          "unsupported");
+  private static final String EXPECTED_RESULT_26 =
+      StringUtils.lines(
+          "bytes written: 11",
+          "String written: Hello World",
+          "bytes read: 11",
+          "String read: Hello World",
+          "true",
+          "true",
+          "true");
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    // Skip Android 4.4.4 due to missing libjavacrypto.
+    return buildParameters(
+        BooleanUtils.values(),
+        getTestParameters()
+            .withDexRuntime(Version.V4_0_4)
+            .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+            .withAllApiLevels()
+            .build());
+  }
+
+  private String getExpectedResult() {
+    return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O)
+        ? EXPECTED_RESULT_26
+        : EXPECTED_RESULT;
+  }
+
+  public FilesTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  private LibraryDesugaringTestConfiguration pathConfiguration() {
+    return LibraryDesugaringTestConfiguration.builder()
+        .setMinApi(parameters.getApiLevel())
+        .addDesugaredLibraryConfiguration(
+            StringResource.fromFile(ToolHelper.getDesugarLibJsonForTestingWithPath()))
+        .setMode(shrinkDesugaredLibrary ? CompilationMode.RELEASE : CompilationMode.DEBUG)
+        .withKeepRuleConsumer()
+        .build();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    Assume.assumeTrue(isJDK11DesugaredLibrary());
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(getLibraryFile())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(pathConfiguration())
+        .compile()
+        .withArt6Plus64BitsLib()
+        .withArtFrameworks()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(getExpectedResult());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Assume.assumeTrue(isJDK11DesugaredLibrary());
+    testForR8(Backend.DEX)
+        .addLibraryFiles(getLibraryFile())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClass.class)
+        .enableCoreLibraryDesugaring(pathConfiguration())
+        .compile()
+        .withArt6Plus64BitsLib()
+        .withArtFrameworks()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(getExpectedResult());
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) throws Throwable {
+      Path path = Files.createTempFile("example", ".txt");
+      readWrite(path);
+
+      PosixFileAttributeView view = Files.getFileAttributeView(path, PosixFileAttributeView.class);
+      if (view != null) {
+        System.out.println(
+            view.readAttributes().permissions().contains(PosixFilePermission.OWNER_READ));
+      } else {
+        System.out.println("null");
+      }
+
+      BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class);
+      if (attributes != null) {
+        System.out.println(attributes.isRegularFile());
+      } else {
+        System.out.println("null");
+      }
+
+      try {
+        PosixFileAttributes posixAttributes = Files.readAttributes(path, PosixFileAttributes.class);
+        if (attributes != null) {
+          System.out.println(
+              posixAttributes.permissions().contains(PosixFilePermission.OWNER_READ));
+        } else {
+          System.out.println("null");
+        }
+      } catch (UnsupportedOperationException e) {
+        System.out.println("unsupported");
+      }
+    }
+
+    private static void readWrite(Path path) throws IOException {
+      try (SeekableByteChannel channel =
+          Files.newByteChannel(path, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
+        String toWrite = "Hello World";
+
+        // Write the String toWrite into the channel.
+        ByteBuffer byteBuffer = ByteBuffer.wrap(toWrite.getBytes());
+        int write = channel.write(byteBuffer);
+        System.out.println("bytes written: " + write);
+        System.out.println("String written: " + toWrite);
+
+        // Read the String toWrite from the channel.
+        channel.position(0);
+        ByteBuffer byteBuffer2 = ByteBuffer.allocate(write);
+        int read = channel.read(byteBuffer2);
+        System.out.println("bytes read: " + read);
+        System.out.println("String read: " + new String(byteBuffer2.array()));
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/GoogleIcon.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/GoogleIcon.java
new file mode 100644
index 0000000..ce0be4d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/GoogleIcon.java
@@ -0,0 +1,168 @@
+// Copyright (c) 2022, 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.jdk11;
+
+public class GoogleIcon {
+  static final byte[] GOOGLE_ICON_PNG =
+      new byte[] {
+        -119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 1, 0, 0, 0, 1, 0, 8, 3,
+        0, 0, 0, 107, -84, 88, 84, 0, 0, 2, -21, 80, 76, 84, 69, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 10, 33, -74, 0, 0,
+        0, -8, 116, 82, 78, 83, 0, 0, 14, -25, -109, -6, -3, 5, 1, 22, -4, 3, -34, 47, -2, -33, -27,
+        -13, 4, -5, 20, 16, 13, -19, -12, -18, 21, -10, -30, 18, 2, 7, -97, -99, -121, 42, 6, -114,
+        35, -35, -123, 68, -65, -26, -119, -21, -29, 45, 8, -38, 59, -96, -56, -14, -17, 10, -43,
+        108, -16, -85, -55, 28, -22, 52, -46, 115, 112, 93, 26, 17, -52, -58, 36, 75, 27, -103, -15,
+        -83, 23, -95, 84, -64, 78, -66, -105, 34, -127, -117, 99, -60, -101, 40, 79, -54, -91, 44,
+        -126, 89, -24, -128, -7, 15, -39, 70, -80, 33, -111, 24, -53, 88, 110, 60, -122, -87, 90,
+        25, 9, -41, -59, -84, -108, -102, -75, 58, -31, 51, -61, 113, -20, 57, 122, -74, 29, -113,
+        54, 87, -107, -104, -73, 74, 39, -48, -50, -32, 65, -86, -67, 83, -76, -70, -23, 114, 71,
+        -71, 119, -100, 11, 111, 127, -110, -37, 50, 73, 69, 86, -9, -11, 116, 66, -68, 19, 107,
+        -81, 63, -89, -124, 37, -79, 53, 56, -98, -62, 46, 38, 12, 64, 117, 106, 77, 32, 126, -49,
+        -69, -125, 123, -93, -92, 91, -106, 67, 62, -57, 95, 125, -82, -90, 97, 100, 121, 104, -44,
+        -77, -78, -36, 85, 30, 92, 82, 109, 102, -115, 103, 94, 41, -40, -47, -94, 105, 48, 124,
+        -63, 120, -8, 118, 43, 61, -72, -116, 31, -45, -51, 72, 76, -28, -42, -118, 55, 101, -29,
+        -118, -16, -44, 0, 0, 9, 41, 73, 68, 65, 84, 120, 94, -20, -64, -127, 0, 0, 0, 0, -61, -96,
+        -5, 83, -33, 96, 4, -75, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -100, -68, -69, -118, 110, -28,
+        -56, -30, 56, -68, -41, -79, 100, 102, 102, 26, -77, -57, 8, 51, -29, -79, 61, 100, 28, 102,
+        102, 102, 102, 102, 102, 74, -122, 25, 2, 67, 73, 6, -126, -80, 97, 102, -50, 38, 89, 102,
+        -90, -1, -29, -66, -84, -114, -70, -69, 74, 114, 89, -3, -94, 43, 125, -49, 122, -47, -81,
+        79, 67, -99, 62, 125, -117, 92, -107, -102, -42, 41, -42, 71, 89, 99, -17, 52, 15, 9, -48,
+        105, -40, -115, 94, -101, 119, 118, 125, 116, 92, -25, -36, -28, -16, -121, 20, 77, -114,
+        56, -80, -39, -121, 127, -128, -56, 49, -117, -77, 15, 31, -8, 29, 92, 112, 116, 59, 17,
+        -13, 0, -3, -113, -84, -22, 28, 5, -41, 84, 94, 36, -30, 29, 32, 102, -57, -118, -115, 112,
+        85, -16, -121, 65, -60, 59, 64, 73, 118, 49, 92, 86, 51, -79, -111, -120, 117, -128, -14,
+        -82, 22, -72, -52, -102, -34, 66, -60, 58, -64, -70, 126, -48, -78, 86, -121, 23, 86, 91,
+        -95, -22, 88, 57, 17, -21, 0, 1, -66, -102, -93, 31, -11, 118, -34, -32, -84, -128, -40,
+        -40, -128, -84, -25, 111, 53, 7, 66, 42, -12, -77, 17, -66, 54, 43, 125, 7, 18, -15, 14,
+        -80, 56, 25, 54, 40, -52, 44, 33, -115, -112, -20, 56, -56, 116, -1, 3, 9, -72, 6, -120,
+        126, 21, 54, -120, -97, 24, 64, 6, 95, -82, -76, 66, 34, 126, -96, -89, 4, 88, 80, 6, 27,
+        20, 76, 32, -119, 103, 58, 67, 34, -12, 91, -49, 8, -16, -31, 21, -40, 96, -74, 31, 73, -91,
+        -84, -128, -52, 25, 79, 8, -16, 107, -40, -67, 75, 14, -51, -122, -52, 115, -4, 3, -28, -63,
+        -18, -49, -28, -60, 8, -56, -52, -29, 30, 96, 10, -20, 94, -10, 39, 103, 86, 65, -30, 74,
+        57, -17, 0, 93, 96, -41, 119, 22, 57, 21, 121, 12, 18, -55, 75, 56, 7, 104, -75, -62, 110,
+        51, -75, -95, 127, 50, 36, -74, -8, -13, 13, -48, -78, 17, 118, -123, 62, -44, -106, -4, 80,
+        72, 124, -61, 54, -128, -33, 114, 104, -68, 66, 2, -75, 91, 65, -43, 44, -82, 1, -90, 64,
+        35, 106, 12, -75, -51, 39, 12, 18, -117, -104, 6, 8, -103, 10, -115, 90, 82, -79, 3, 50,
+        107, 88, 6, -16, 47, -128, 86, 30, -87, -16, 31, 5, -119, -35, 44, 3, 124, 1, -99, 61, -92,
+        -28, 77, 72, -124, 94, 96, 24, -96, -61, 39, -48, -102, 58, -116, -44, 12, -126, 68, 31,
+        -122, 1, 22, 67, 39, 121, 41, -87, 121, 30, 18, 29, 99, -40, 5, 72, -99, 11, -99, 113, -92,
+        40, -78, 51, 36, -114, -80, 11, -16, 22, -12, -74, -112, -86, -121, 33, 49, -128, 93, -128,
+        116, -24, -35, 36, 85, -67, -29, 33, -54, -19, -64, 44, 64, 76, 71, -24, 61, 77, -54, 102,
+        66, 100, -51, 98, 22, 96, 44, 12, 78, -111, -78, 73, -112, -40, -63, 44, 64, 31, 24, -92,
+        -109, -78, -128, -66, 16, 77, -28, 21, 32, -88, -77, -8, 60, -81, 110, 55, 68, -29, 121, 5,
+        24, 102, -127, -63, 9, 82, -41, 19, -94, 2, 94, 1, 6, -61, -88, -119, -44, -83, -125, 104,
+        31, -81, 0, 31, -63, 104, -19, 82, 82, -74, -76, 24, -126, 50, 63, 86, 1, 50, 96, 116, -75,
+        -114, -44, 29, -123, 32, -84, -108, 85, -128, 63, 65, -80, -119, -44, 77, 100, 31, 96, 28,
+        4, 31, -112, -70, 35, 16, -108, 69, 114, 10, -112, -38, 25, -126, -39, -92, 46, 36, 20, 70,
+        47, 17, -89, 0, -111, -45, 33, 88, 79, -22, -94, -85, 97, 52, -124, 85, -128, -96, 101, 16,
+        36, 60, 70, 106, -28, 1, 95, 99, 21, -128, -10, 65, -12, 38, -87, 91, 15, -93, -31, -68, 2,
+        12, -126, 40, -113, -52, 44, 8, 47, -13, 10, -80, 8, -94, -71, -92, 110, 0, 12, 2, -57, -16,
+        10, -112, 9, -111, -27, 62, 41, 27, 42, 62, 8, -14, 10, -48, 11, 18, -41, 72, -103, 47, 12,
+        70, 19, -81, 0, -65, -126, -60, 92, 127, 82, -75, 19, 6, -83, -52, 2, 116, 42, -122, 40,
+        -76, 27, -87, 122, 26, 122, -59, -89, -103, 5, -96, -82, -112, 24, 74, -86, -6, 65, 47,
+        -109, -72, 5, 120, 7, 18, 9, 1, -92, 104, 20, 116, -110, 94, 96, 23, 96, 78, 4, 36, 54,
+        -109, -102, -76, 48, -24, -68, 65, -20, 2, -56, -49, -127, -62, 105, -92, 100, -42, 84, 104,
+        -43, -44, 49, 12, 112, 9, 50, 63, 37, 37, 99, -95, -77, -127, 24, 6, 8, -38, 15, -119, -124,
+        6, 82, 113, 14, 90, -93, 34, 57, 6, -96, -53, -112, -87, 32, 5, 29, -50, 67, -93, -26, 62,
+        -79, 12, 16, 52, 14, 50, -113, 80, -37, 102, 64, -21, 11, -30, 25, -128, -98, -127, 76, 88,
+        10, -75, 105, 11, 52, -34, 37, -82, 1, -24, 20, 100, 14, 81, 91, 14, 66, 99, 39, -15, 13,
+        -48, 24, -20, -46, -99, -96, -12, -25, -80, 75, 39, -58, 1, -24, 70, 20, 100, -34, 33, -89,
+        86, -62, -18, -108, 63, -21, 0, 52, 31, 50, -127, -109, -56, -119, 30, -80, -37, -58, -2,
+        -117, -111, 108, -56, 88, 63, 38, -121, 78, -62, -18, 36, -79, 15, 64, -93, 33, -11, 93, 7,
+        -110, -118, -47, -84, -125, -29, 39, -111, 7, 4, -96, -95, -112, 90, -66, -99, 36, -58, 46,
+        -125, 13, 114, -97, 34, -113, 8, 64, -61, 33, 101, -15, -51, 33, -125, -21, -85, 97, 119,
+        -94, -127, 60, 36, 0, 45, 78, -128, 84, 85, -59, -116, 88, -78, -95, -100, -57, -105, 67,
+        35, 59, -110, 60, 38, 0, 101, 21, -64, -127, -28, 39, 94, -100, -108, -65, 102, -63, 63,
+        126, -71, -77, 54, 73, 120, 13, -30, 57, 1, 40, -75, 103, 61, -38, 101, 33, -15, 14, 32,
+        -38, 122, 46, 2, -22, 86, 17, -5, 0, -94, -37, 35, -62, -95, 104, -67, 31, -9, 0, 114, 41,
+        -33, 127, 90, 3, 5, 17, -9, -119, 113, 0, -25, 26, 6, -82, -84, -115, 67, 27, 110, 17, -37,
+        0, 74, 18, -69, 13, -36, 80, 52, -6, -3, 21, 93, 95, -37, -107, 121, -19, -85, 93, 48, -118,
+        -24, -19, -63, 1, 68, 67, 96, -76, -101, -68, 41, -64, 83, 16, 12, -9, -86, 0, -25, 32, -8,
+        -42, -101, 2, -8, 77, -121, -32, -65, -34, 20, -32, 118, 20, 4, 103, -67, 41, -64, 35, 16,
+        -83, -10, -90, 0, 95, 65, 20, 81, -25, 69, 1, 70, 66, 98, -101, 23, 5, 120, 29, 18, -106,
+        63, 122, -9, 53, 0, 40, 110, -15, -102, 0, 99, 2, 33, -13, 121, -106, -73, 4, 40, 13, -125,
+        -44, -28, 124, 47, 9, 64, -65, -128, -100, -27, -116, -105, 4, 104, -123, 35, 3, 98, -67,
+        34, 64, -38, -113, 112, -28, 111, 37, -34, 16, -128, 70, -62, -95, -124, 121, -34, 16, -64,
+        111, 63, 28, 43, 10, -14, -4, 0, 84, 110, -123, 99, 39, -22, 60, 63, 0, 45, -124, 19, -63,
+        -37, 61, 63, -128, -33, 93, 56, 81, 53, -48, -29, 3, 80, 98, 46, -100, -23, -31, -47, 1, -4,
+        -90, -11, -34, 27, -78, 16, 78, 101, 123, 96, -128, -46, -70, 110, 99, 31, -49, -84, -40,
+        50, -82, 108, 114, 68, 32, -38, 50, 62, -51, -125, 2, 68, -66, 48, 99, -61, -24, -126, -36,
+        8, -76, -57, -22, 7, -98, 17, 96, 90, -7, -61, -117, -106, 37, -63, 5, 67, 124, -8, 7, -104,
+        48, -1, 78, 33, 92, 54, -28, 1, -17, 0, 23, -122, 55, 5, -62, -108, -43, 126, 124, 3, 44,
+        -23, -46, 28, 10, 7, 44, 113, -63, -5, 11, -18, -68, -97, -79, 106, 116, -6, -94, 71, -29,
+        -31, 68, 58, -41, 0, 33, 111, 108, -124, 68, 84, -14, 111, 50, 126, 59, 111, -63, -19, -24,
+        82, -51, 79, -101, -31, -60, 71, 44, 3, 116, -69, 105, -127, -96, -2, -47, -123, 95, -113,
+        57, 77, -94, -76, 34, 56, 22, 122, -119, 95, -128, 97, 25, 81, 48, -22, 62, 98, 70, 52, 57,
+        -76, -25, 0, 28, 42, -114, 102, 22, -96, -61, -17, 19, 96, 112, 124, -24, -126, 32, 114,
+        -86, -31, 61, 56, 84, -63, 43, -64, 83, -75, 48, -104, -34, -77, 63, -75, -19, -38, 84, 56,
+        114, -112, 83, -128, -109, -58, -1, -79, -74, -53, 105, 82, -14, 86, 25, 28, -88, 76, 101,
+        19, -96, 113, 5, -12, -84, -37, -94, 73, 85, -64, 19, 112, 96, 48, -105, 0, -61, -10, 67,
+        111, -6, 117, 106, -113, 41, -112, 27, -60, 36, -64, -124, 3, -48, -5, 44, -111, -38, -25,
+        114, 95, -56, 88, 67, 88, 4, -56, -6, 39, -12, -6, -92, 82, 123, 61, 25, 12, -103, 76, 14,
+        1, -74, 6, 67, 111, 23, -71, -32, 47, 77, -112, -24, -50, 96, -81, -79, 78, -107, -48, -101,
+        -23, 79, -82, 72, 25, 4, 81, -32, 94, -9, 15, -112, 1, -67, -13, 49, -28, -102, -40, -61,
+        16, -11, 114, -5, 0, -117, -95, 23, -6, 12, -71, 106, -38, -37, 16, 108, 115, -9, 0, 115,
+        -22, -123, -25, 87, -41, 45, -55, -123, -47, 110, 119, 15, -112, 1, -67, -92, 28, 50, -95,
+        91, 18, 12, -2, -22, -17, -34, 1, 46, 4, 10, -61, 96, 77, -23, 2, -125, -16, 7, -18, 29,
+        -96, 31, 12, -14, -55, -100, 65, -48, -85, 14, 112, -21, 0, 89, 81, -48, 43, -18, 68, -26,
+        116, 11, -124, 78, 124, -99, 91, 7, -8, 14, 6, 93, -55, -84, 39, -96, -109, -76, -43, -99,
+        3, 44, 93, 11, -125, 103, -55, -84, 124, -24, 68, 52, -72, 115, -128, -21, -110, -11, -85,
+        89, 105, -63, -48, -22, -40, -24, -50, 1, -118, 96, -12, 36, -103, -106, 1, -83, -49, -3,
+        -36, 57, -64, 40, 24, 88, -9, -110, 105, 35, -95, 53, -118, -36, 56, -128, -80, -87, 2, 44,
+        57, 100, 90, 57, -76, 42, -36, 57, 64, 73, -88, 100, -15, 102, 90, 75, 20, 52, 122, -70,
+        115, -128, -65, 67, -80, -114, 76, 91, 82, 5, -115, 5, -18, 28, -96, 23, 4, -83, 100, 90,
+        98, 2, -20, -114, -5, -72, 115, -128, -97, 65, -80, -127, 76, -101, 83, 5, -69, 126, -60,
+        44, -64, 120, 50, 45, -57, 2, -69, -41, -119, -39, 41, 16, -100, 70, 102, 109, -126, 93, 92,
+        -118, 91, 7, -72, 8, 81, 57, -103, -11, 61, -20, -6, -112, 91, 7, -72, 7, -47, 80, 50, -53,
+        87, 120, 45, -64, -32, 65, 72, 55, 71, -43, 28, -1, 125, -80, -63, 76, 114, -17, 0, -76, 30,
+        -94, 91, 100, 78, 73, 32, 108, -84, -21, -36, 61, 64, -98, 124, -53, 88, 83, -118, -124,
+        -55, 114, 110, 28, 96, 13, 36, 62, 37, 51, 26, 39, -61, 38, -82, -63, -19, 3, -8, -3, 11,
+        18, -13, -55, -124, -77, -80, -63, 72, 6, -13, 3, -98, -123, -124, 37, -97, 92, -42, 82, 5,
+        -101, -9, -120, 65, -128, -60, 56, 72, -60, 61, 73, 46, 74, 29, 2, -101, -32, 68, 14, 1,
+        -24, 69, -56, -44, 111, 34, -41, -4, 27, 54, 73, -1, 33, 22, 1, 124, -62, 32, -45, -9, 107,
+        114, 69, 79, -19, 126, -117, 60, 2, -48, 88, -56, 77, 9, 34, 117, -30, 112, -51, 46, -60,
+        37, 0, -19, -126, 92, 65, 8, -75, 79, -121, -39, -62, 103, 51, 44, 2, 44, -83, -123, 92,
+        -46, -62, 104, 106, -121, 123, 119, -123, -29, -49, 35, 0, 125, 25, 6, 7, -110, -97, 85, 78,
+        16, 51, 37, 30, 54, 87, -25, 17, -85, 0, 52, 33, 28, -114, -124, 23, -107, -112, -126, -57,
+        122, 6, -61, 6, -71, 107, -120, 89, 0, 42, 9, -125, 67, 81, -121, -97, 107, 33, -89, 82,
+        -73, 103, 107, 11, -34, 89, 66, -20, 2, -48, -84, 90, 56, 113, -91, 57, -77, 117, -85, 63,
+        73, 5, -52, 40, 122, 9, 26, -15, 15, -13, -100, 31, 16, 91, 1, -25, -30, -69, 31, -102, -40,
+        -21, -58, 15, 115, 98, -3, -2, 127, -55, -9, -23, 29, -78, 103, 126, -97, -26, 56, -24, -68,
+        124, -113, -19, -4, -128, -111, 27, -47, -74, -64, -66, -31, 101, -35, -101, 42, 43, -25,
+        118, 63, -1, 80, 4, 4, 97, 31, 112, -98, 31, -112, 115, 8, -26, -44, -97, 77, 97, 62, 63,
+        -96, -75, 18, -82, 43, 124, -91, 55, -1, 17, 26, 65, -109, 92, 77, -80, -17, 76, -94, 103,
+        12, 81, -15, -65, -40, -17, 42, -38, -21, -8, -128, 75, -111, 30, 52, 71, 40, -89, -57, 114,
+        11, -44, -123, -33, -100, -105, -24, 113, -77, -60, 126, 120, 124, 102, 56, 20, 36, -36,
+        -51, -53, -113, 33, 1, -61, 0, 34, -97, 77, -33, 12, 104, -22, 8, 71, 66, -21, -101, 94,
+        -19, 113, -80, 63, 9, 56, 7, 16, 61, 22, 50, 120, -2, -120, -15, -57, -26, 46, 91, 59, 57,
+        -82, -70, -90, -90, -70, 99, 97, -16, 39, -51, 71, -45, -13, 62, -34, 51, 33, -123, 4, 28,
+        3, 40, 10, -22, 52, 45, 58, 96, 78, -1, -128, -108, -40, 82, 127, 114, -55, -1, -38, -127,
+        3, 1, 0, 0, 0, -122, 65, -9, -89, -66, -63, 8, 106, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56,
+        -31, -37, 22, 69, 99, -29, 53, -11, 0, 0, 0, 0, 73, 69, 78, 68, -82, 66, 96, -126
+      };
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InputStreamTransferToTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InputStreamTransferToTest.java
index 77f16d2..bee154b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InputStreamTransferToTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InputStreamTransferToTest.java
@@ -4,6 +4,9 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.jdk11;
 
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
+import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
@@ -41,21 +44,24 @@
     this.parameters = parameters;
   }
 
+  private LibraryDesugaringTestConfiguration pathConfiguration() {
+    return LibraryDesugaringTestConfiguration.builder()
+        .setMinApi(parameters.getApiLevel())
+        .addDesugaredLibraryConfiguration(
+            StringResource.fromFile(ToolHelper.getDesugarLibJsonForTestingWithPath()))
+        .setMode(shrinkDesugaredLibrary ? CompilationMode.RELEASE : CompilationMode.DEBUG)
+        .withKeepRuleConsumer()
+        .build();
+  }
+
   @Test
   public void testD8() throws Exception {
     Assume.assumeTrue(isJDK11DesugaredLibrary());
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8(parameters.getBackend())
         .addLibraryFiles(getLibraryFile())
         .addProgramFiles(INPUT_JAR)
         .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-        .compile()
-        .addDesugaredCoreLibraryRunClassPath(
-            this::buildDesugaredLibrary,
-            parameters.getApiLevel(),
-            keepRuleConsumer.get(),
-            shrinkDesugaredLibrary)
+        .enableCoreLibraryDesugaring(pathConfiguration())
         .run(parameters.getRuntime(), MAIN_CLASS)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
@@ -63,19 +69,12 @@
   @Test
   public void testR8() throws Exception {
     Assume.assumeTrue(isJDK11DesugaredLibrary());
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(parameters.getBackend())
         .addLibraryFiles(getLibraryFile())
         .addProgramFiles(INPUT_JAR)
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(MAIN_CLASS)
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-        .compile()
-        .addDesugaredCoreLibraryRunClassPath(
-            this::buildDesugaredLibrary,
-            parameters.getApiLevel(),
-            keepRuleConsumer.get(),
-            shrinkDesugaredLibrary)
+        .enableCoreLibraryDesugaring(pathConfiguration())
         .run(parameters.getRuntime(), MAIN_CLASS)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/PathTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/PathTest.java
new file mode 100644
index 0000000..3065b0f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/PathTest.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2022, 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.jdk11;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
+import com.android.tools.r8.StringResource;
+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.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.Assume;
+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 PathTest extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED_RESULT = StringUtils.lines("x.txt", "dir", "dir/x.txt", "/");
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public PathTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  private LibraryDesugaringTestConfiguration pathConfiguration() {
+    return LibraryDesugaringTestConfiguration.builder()
+        .setMinApi(parameters.getApiLevel())
+        .addDesugaredLibraryConfiguration(
+            StringResource.fromFile(ToolHelper.getDesugarLibJsonForTestingWithPath()))
+        .setMode(shrinkDesugaredLibrary ? CompilationMode.RELEASE : CompilationMode.DEBUG)
+        .withKeepRuleConsumer()
+        .build();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    Assume.assumeTrue(isJDK11DesugaredLibrary());
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(getLibraryFile())
+        .addInnerClasses(PathTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(pathConfiguration())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Assume.assumeTrue(isJDK11DesugaredLibrary());
+    testForR8(Backend.DEX)
+        .addLibraryFiles(getLibraryFile())
+        .addInnerClasses(PathTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClass.class)
+        .enableCoreLibraryDesugaring(pathConfiguration())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      File file = new File("x.txt");
+      Path path1 = file.toPath();
+      System.out.println(path1);
+      Path path2 = Paths.get("dir/");
+      System.out.println(path2);
+      Path resolve = path2.resolve(path1);
+      System.out.println(resolve);
+      System.out.println(resolve.getFileSystem().getSeparator());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StandardCharsetTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StandardCharsetTest.java
index 1b9581a..b55ebae 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StandardCharsetTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StandardCharsetTest.java
@@ -4,7 +4,11 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.jdk11;
 
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
+import com.android.tools.r8.StringResource;
 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.transformers.MethodTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -42,21 +46,24 @@
     this.parameters = parameters;
   }
 
+  private LibraryDesugaringTestConfiguration pathConfiguration() {
+    return LibraryDesugaringTestConfiguration.builder()
+        .setMinApi(parameters.getApiLevel())
+        .addDesugaredLibraryConfiguration(
+            StringResource.fromFile(ToolHelper.getDesugarLibJsonForTestingWithPath()))
+        .setMode(shrinkDesugaredLibrary ? CompilationMode.RELEASE : CompilationMode.DEBUG)
+        .withKeepRuleConsumer()
+        .build();
+  }
+
   @Test
   public void testD8() throws Exception {
     Assume.assumeTrue(isJDK11DesugaredLibrary());
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8(parameters.getBackend())
         .addLibraryFiles(getLibraryFile())
         .addProgramClassFileData(getProgramClassFileData())
         .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-        .compile()
-        .addDesugaredCoreLibraryRunClassPath(
-            this::buildDesugaredLibrary,
-            parameters.getApiLevel(),
-            keepRuleConsumer.get(),
-            shrinkDesugaredLibrary)
+        .enableCoreLibraryDesugaring(pathConfiguration())
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
@@ -64,19 +71,12 @@
   @Test
   public void testR8() throws Exception {
     Assume.assumeTrue(isJDK11DesugaredLibrary());
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(Backend.DEX)
         .addLibraryFiles(getLibraryFile())
         .addProgramClassFileData(getProgramClassFileData())
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(TestClass.class)
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-        .compile()
-        .addDesugaredCoreLibraryRunClassPath(
-            this::buildDesugaredLibrary,
-            parameters.getApiLevel(),
-            keepRuleConsumer.get(),
-            shrinkDesugaredLibrary)
+        .enableCoreLibraryDesugaring(pathConfiguration())
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
index f107b2a..ff80b66 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.ApiLevelRange;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanTopLevelFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecification;
@@ -80,21 +81,18 @@
       MultiAPILevelHumanDesugaredLibrarySpecification humanSpec1,
       MultiAPILevelHumanDesugaredLibrarySpecification humanSpec2) {
     assertTopLevelFlagsEquals(humanSpec1.getTopLevelFlags(), humanSpec2.getTopLevelFlags());
-    assertFlagMapEquals(
-        humanSpec1.getCommonFlagsForTesting(), humanSpec2.getCommonFlagsForTesting());
-    assertFlagMapEquals(
-        humanSpec1.getLibraryFlagsForTesting(), humanSpec2.getLibraryFlagsForTesting());
-    assertFlagMapEquals(
-        humanSpec1.getProgramFlagsForTesting(), humanSpec2.getProgramFlagsForTesting());
+    assertFlagMapEquals(humanSpec1.getCommonFlags(), humanSpec2.getCommonFlags());
+    assertFlagMapEquals(humanSpec1.getLibraryFlags(), humanSpec2.getLibraryFlags());
+    assertFlagMapEquals(humanSpec1.getProgramFlags(), humanSpec2.getProgramFlags());
   }
 
   private void assertFlagMapEquals(
-      Map<Integer, HumanRewritingFlags> commonFlags1,
-      Map<Integer, HumanRewritingFlags> commonFlags2) {
+      Map<ApiLevelRange, HumanRewritingFlags> commonFlags1,
+      Map<ApiLevelRange, HumanRewritingFlags> commonFlags2) {
     assertEquals(commonFlags1.size(), commonFlags2.size());
-    for (int integer : commonFlags1.keySet()) {
-      assertTrue(commonFlags2.containsKey(integer));
-      assertFlagsEquals(commonFlags1.get(integer), commonFlags2.get(integer));
+    for (ApiLevelRange range : commonFlags1.keySet()) {
+      assertTrue(commonFlags2.containsKey(range));
+      assertFlagsEquals(commonFlags1.get(range), commonFlags2.get(range));
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
index 5b700d5..ee0e845 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
@@ -138,7 +138,7 @@
   }
 
   @Test(expected = CompilationFailedException.class)
-  @IgnoreForRangeOfVmVersions(from = Version.V7_0_0, to = Version.V13_MASTER) // No desugaring
+  @IgnoreForRangeOfVmVersions(from = Version.V7_0_0, to = Version.V13_0_0) // No desugaring
   public void testInvokeDefault1() throws Exception {
     ensureSameOutput(
         TestMainDefault1.class.getCanonicalName(),
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java
index 45a3be4..71e7df5 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java
@@ -47,28 +47,6 @@
   }
 
   @Test
-  public void testPropagationFromFeature() throws Exception {
-    ThrowingConsumer<R8TestCompileResult, Exception> ensureGetFromFeatureGone =
-        r8TestCompileResult -> {
-          // Ensure that getFromFeature from FeatureClass is inlined into the run method.
-          ClassSubject clazz = r8TestCompileResult.inspector().clazz(FeatureClass.class);
-          assertThat(clazz.uniqueMethodWithName("getFromFeature"), not(isPresent()));
-        };
-    ProcessResult processResult =
-        testDexSplitter(
-            parameters,
-            ImmutableSet.of(BaseSuperClass.class),
-            ImmutableSet.of(FeatureClass.class),
-            FeatureClass.class,
-            EXPECTED,
-            ensureGetFromFeatureGone,
-            TestShrinkerBuilder::noMinification);
-    // We expect art to fail on this with the dex splitter, see b/122902374
-    assertNotEquals(processResult.exitCode, 0);
-    assertTrue(processResult.stderr.contains("NoClassDefFoundError"));
-  }
-
-  @Test
   public void testOnR8Splitter() throws IOException, CompilationFailedException {
     assumeTrue(parameters.isDexRuntime());
     ProcessResult processResult =
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
index 96220f8..53af2b6 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
@@ -48,31 +48,6 @@
   }
 
   @Test
-  public void testInliningFromFeature() throws Exception {
-    ThrowingConsumer<R8TestCompileResult, Exception> ensureGetFromFeatureGone =
-        r8TestCompileResult -> {
-          // Ensure that getFromFeature from FeatureClass is inlined into the run method.
-          ClassSubject clazz = r8TestCompileResult.inspector().clazz(FeatureClass.class);
-          assertThat(clazz.uniqueMethodWithName("getFromFeature"), not(isPresent()));
-        };
-    Consumer<R8FullTestBuilder> configurator =
-        r8FullTestBuilder ->
-            r8FullTestBuilder.enableNoVerticalClassMergingAnnotations().noMinification();
-    ProcessResult processResult =
-        testDexSplitter(
-            parameters,
-            ImmutableSet.of(BaseSuperClass.class),
-            ImmutableSet.of(FeatureClass.class),
-            FeatureClass.class,
-            EXPECTED,
-            ensureGetFromFeatureGone,
-            configurator);
-    // We expect art to fail on this with the dex splitter, see b/122902374
-    assertNotEquals(processResult.exitCode, 0);
-    assertTrue(processResult.stderr.contains("NoClassDefFoundError"));
-  }
-
-  @Test
   public void testOnR8Splitter() throws IOException, CompilationFailedException {
     assumeTrue(parameters.isDexRuntime());
     ThrowableConsumer<R8FullTestBuilder> configurator =
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
index 38d19da..a2e1b1d 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
@@ -46,28 +46,6 @@
   }
 
   @Test
-  public void testPropagationFromFeature() throws Exception {
-    ThrowingConsumer<R8TestCompileResult, Exception> ensureGetFromFeatureGone =
-        r8TestCompileResult -> {
-          // Ensure that getFromFeature from FeatureClass is inlined into the run method.
-          ClassSubject clazz = r8TestCompileResult.inspector().clazz(FeatureClass.class);
-          assertThat(clazz.uniqueMethodWithName("getFromFeature"), not(isPresent()));
-        };
-    ProcessResult processResult =
-        testDexSplitter(
-            parameters,
-            ImmutableSet.of(BaseSuperClass.class),
-            ImmutableSet.of(FeatureClass.class, FeatureEnum.class),
-            FeatureClass.class,
-            EXPECTED,
-            ensureGetFromFeatureGone,
-            builder -> builder.enableInliningAnnotations().noMinification());
-    // We expect art to fail on this with the dex splitter, see b/122902374
-    assertNotEquals(processResult.exitCode, 0);
-    assertTrue(processResult.stderr.contains("NoClassDefFoundError"));
-  }
-
-  @Test
   public void testOnR8Splitter() throws IOException, CompilationFailedException {
     assumeTrue(parameters.isDexRuntime());
     ProcessResult processResult =
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
index 71ddab8..a7d137f 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
@@ -48,43 +48,6 @@
   }
 
   @Test
-  public void testInliningFromFeature() throws Exception {
-    // Static merging is based on sorting order, we assert that we merged to the feature.
-    ThrowingConsumer<R8TestCompileResult, Exception> ensureMergingToFeature =
-        r8TestCompileResult -> {
-          ClassSubject clazz = r8TestCompileResult.inspector().clazz(AFeatureWithStatic.class);
-          assertEquals(2, clazz.allMethods().size());
-          assertThat(clazz.uniqueMethodWithName("getBase42"), isPresent());
-          assertThat(clazz.uniqueMethodWithName("getFoobar"), isPresent());
-        };
-    Consumer<R8FullTestBuilder> configurator =
-        r8FullTestBuilder ->
-            r8FullTestBuilder
-                .addOptionsModification(
-                    options ->
-                        options.testing.horizontalClassMergingTarget =
-                            (appView, candidates, target) -> candidates.iterator().next())
-                .addHorizontallyMergedClassesInspector(
-                    inspector ->
-                        inspector.assertMergedInto(BaseWithStatic.class, AFeatureWithStatic.class))
-                .enableNoVerticalClassMergingAnnotations()
-                .enableInliningAnnotations()
-                .noMinification();
-    ProcessResult processResult =
-        testDexSplitter(
-            parameters,
-            ImmutableSet.of(BaseClass.class, BaseWithStatic.class),
-            ImmutableSet.of(FeatureClass.class, AFeatureWithStatic.class),
-            FeatureClass.class,
-            EXPECTED,
-            ensureMergingToFeature,
-            configurator);
-    // We expect art to fail on this with the dex splitter.
-    assertNotEquals(processResult.exitCode, 0);
-    assertTrue(processResult.stderr.contains("NoClassDefFoundError"));
-  }
-
-  @Test
   public void testOnR8Splitter() throws IOException, CompilationFailedException {
     assumeTrue(parameters.isDexRuntime());
     ThrowableConsumer<R8FullTestBuilder> configurator =
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
deleted file mode 100644
index e90e0ab..0000000
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
+++ /dev/null
@@ -1,484 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.dexsplitter;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.D8Command;
-import com.android.tools.r8.DexSplitterHelper;
-import com.android.tools.r8.ExtractMarker;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.ResourceException;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
-import com.android.tools.r8.dex.Marker;
-import com.android.tools.r8.dexsplitter.DexSplitter.Options;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.UnsupportedEncodingException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-public class DexSplitterTests {
-
-  private static final String CLASS_DIR =
-      ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR + "classes/dexsplitsample";
-  private static final String CLASS1_CLASS = CLASS_DIR + "/Class1.class";
-  private static final String CLASS2_CLASS = CLASS_DIR + "/Class2.class";
-  private static final String CLASS3_CLASS = CLASS_DIR + "/Class3.class";
-  private static final String CLASS3_INNER_CLASS = CLASS_DIR + "/Class3$InnerClass.class";
-  private static final String CLASS3_SYNTHETIC_CLASS = CLASS_DIR + "/Class3$1.class";
-  private static final String CLASS4_CLASS = CLASS_DIR + "/Class4.class";
-  private static final String CLASS4_LAMBDA_INTERFACE = CLASS_DIR + "/Class4$LambdaInterface.class";
-  private static final String TEXT_FILE =
-      ToolHelper.EXAMPLES_ANDROID_N_DIR + "dexsplitsample/TextFile.txt";
-
-
-  @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
-
-  private Path createInput(boolean dontCreateMarkerInD8)
-      throws IOException, CompilationFailedException {
-    // Initial normal compile to create dex files.
-    Path inputZip = temp.newFolder().toPath().resolve("input.zip");
-    D8Command command =
-        D8Command.builder()
-            .setOutput(inputZip, OutputMode.DexIndexed)
-            .addProgramFiles(Paths.get(CLASS1_CLASS))
-            .addProgramFiles(Paths.get(CLASS2_CLASS))
-            .addProgramFiles(Paths.get(CLASS3_CLASS))
-            .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
-            .addProgramFiles(Paths.get(CLASS3_SYNTHETIC_CLASS))
-            .addProgramFiles(Paths.get(CLASS4_CLASS))
-            .addProgramFiles(Paths.get(CLASS4_LAMBDA_INTERFACE))
-            .build();
-
-    DexSplitterHelper.runD8ForTesting(command, dontCreateMarkerInD8);
-
-    return inputZip;
-  }
-
-  private void testMarker(boolean addMarkerToInput)
-      throws CompilationFailedException, IOException, ResourceException, ExecutionException {
-    Path inputZip = createInput(!addMarkerToInput);
-
-    Path output = temp.newFolder().toPath().resolve("output");
-    Files.createDirectory(output);
-    Path splitSpec = createSplitSpec();
-
-    DexSplitter.main(
-        new String[] {
-          "--input", inputZip.toString(),
-          "--output", output.toString(),
-          "--feature-splits", splitSpec.toString()
-        });
-
-    Path base = output.resolve("base").resolve("classes.dex");
-    Path feature = output.resolve("feature1").resolve("classes.dex");
-
-    for (Path path : new Path[] {inputZip, base, feature}) {
-      Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(path);
-      assertEquals(addMarkerToInput ? 1 : 0, markers.size());
-    }
-  }
-
-  @Test
-  public void testMarkerPreserved()
-      throws CompilationFailedException, IOException, ResourceException, ExecutionException {
-    testMarker(true);
-  }
-
-  @Test
-  public void testMarkerNotAdded()
-      throws CompilationFailedException, IOException, ResourceException, ExecutionException {
-    testMarker(false);
-  }
-
-  /**
-   * To test the file splitting we have 3 classes that we distribute like this: Class1 -> base
-   * Class2 -> feature1 Class3 -> feature1
-   *
-   * <p>Class1 and Class2 works independently of each other, but Class3 extends Class1, and
-   * therefore can't run without the base being loaded.
-   */
-  @Test
-  public void splitFilesNoObfuscation()
-      throws CompilationFailedException, IOException, FeatureMappingException {
-    noObfuscation(false);
-    noObfuscation(true);
-  }
-
-  private void noObfuscation(boolean useOptions)
-      throws IOException, CompilationFailedException, FeatureMappingException {
-    Path inputZip = createInput(false);
-    Path output = temp.newFolder().toPath().resolve("output");
-    Files.createDirectory(output);
-    Path splitSpec = createSplitSpec();
-
-    if (useOptions) {
-      Options options = new Options();
-      options.addInputArchive(inputZip.toString());
-      options.setFeatureSplitMapping(splitSpec.toString());
-      options.setOutput(output.toString());
-      DexSplitter.run(options);
-    } else {
-      DexSplitter.main(
-          new String[] {
-              "--input", inputZip.toString(),
-              "--output", output.toString(),
-              "--feature-splits", splitSpec.toString()
-          });
-    }
-
-    Path base = output.resolve("base").resolve("classes.dex");
-    Path feature = output.resolve("feature1").resolve("classes.dex");
-    validateUnobfuscatedOutput(base, feature);
-  }
-
-  private void validateUnobfuscatedOutput(Path base, Path feature) throws IOException {
-    // Both classes should still work if we give all dex files to the system.
-    for (String className : new String[] {"Class1", "Class2", "Class3"}) {
-      ArtCommandBuilder builder = new ArtCommandBuilder();
-      builder.appendClasspath(base.toString());
-      builder.appendClasspath(feature.toString());
-      builder.setMainClass("dexsplitsample." + className);
-      String out = ToolHelper.runArt(builder);
-      assertEquals(out, className + "\n");
-    }
-    // Individual classes should also work from the individual files.
-    String className = "Class1";
-    ArtCommandBuilder builder = new ArtCommandBuilder();
-    builder.appendClasspath(base.toString());
-    builder.setMainClass("dexsplitsample." + className);
-    String out = ToolHelper.runArt(builder);
-    assertEquals(out, className + "\n");
-
-    className = "Class2";
-    builder = new ArtCommandBuilder();
-    builder.appendClasspath(feature.toString());
-    builder.setMainClass("dexsplitsample." + className);
-    out = ToolHelper.runArt(builder);
-    assertEquals(out, className + "\n");
-
-    className = "Class3";
-    builder = new ArtCommandBuilder();
-    builder.appendClasspath(feature.toString());
-    builder.setMainClass("dexsplitsample." + className);
-    try {
-      ToolHelper.runArt(builder);
-      assertFalse(true);
-    } catch (AssertionError assertionError) {
-      // We expect this to throw since base is not in the path and Class3 depends on Class1
-    }
-
-    className = "Class4";
-    builder = new ArtCommandBuilder();
-    builder.appendClasspath(feature.toString());
-    builder.setMainClass("dexsplitsample." + className);
-    try {
-      ToolHelper.runArt(builder);
-      assertFalse(true);
-    } catch (AssertionError assertionError) {
-      // We expect this to throw since base is not in the path and Class4 includes a lambda that
-      // would have been pushed to base.
-    }
-  }
-
-  private Path createSplitSpec() throws FileNotFoundException, UnsupportedEncodingException {
-    Path splitSpec = temp.getRoot().toPath().resolve("split_spec");
-    try (PrintWriter out = new PrintWriter(splitSpec.toFile(), "UTF-8")) {
-      out.write(
-          "dexsplitsample.Class1:base\n"
-              + "dexsplitsample.Class2:feature1\n"
-              + "dexsplitsample.Class3:feature1\n"
-              + "dexsplitsample.Class4:feature1");
-    }
-    return splitSpec;
-  }
-
-  private List<String> getProguardConf() {
-    return ImmutableList.of(
-        "-keep class dexsplitsample.Class3 {",
-        "  public static void main(java.lang.String[]);",
-        "}");
-  }
-
-  @Test
-  public void splitFilesFromJar()
-      throws IOException, CompilationFailedException, FeatureMappingException {
-    for (boolean useOptions : new boolean[]{false, true}) {
-      for (boolean explicitBase: new boolean[]{false, true}) {
-        for (boolean renameBase: new boolean[]{false, true}) {
-          splitFromJars(useOptions, explicitBase, renameBase);
-        }
-      }
-    }
-  }
-
-  private void splitFromJars(boolean useOptions, boolean explicitBase, boolean renameBase)
-      throws IOException, CompilationFailedException, FeatureMappingException {
-    Path inputZip = createInput(false);
-    Path output = temp.newFolder().toPath().resolve("output");
-    Files.createDirectory(output);
-    Path baseJar = temp.getRoot().toPath().resolve("base.jar");
-    Path featureJar = temp.getRoot().toPath().resolve("feature1.jar");
-    ZipOutputStream baseStream = new ZipOutputStream(Files.newOutputStream(baseJar));
-    String name = "dexsplitsample/Class1.class";
-    baseStream.putNextEntry(new ZipEntry(name));
-    baseStream.write(Files.readAllBytes(Paths.get(CLASS1_CLASS)));
-    baseStream.closeEntry();
-    baseStream.close();
-
-    ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar));
-    name = "dexsplitsample/Class2.class";
-    featureStream.putNextEntry(new ZipEntry(name));
-    featureStream.write(Files.readAllBytes(Paths.get(CLASS2_CLASS)));
-    featureStream.closeEntry();
-    name = "dexsplitsample/Class3.class";
-    featureStream.putNextEntry(new ZipEntry(name));
-    featureStream.write(Files.readAllBytes(Paths.get(CLASS3_CLASS)));
-    featureStream.closeEntry();
-    name = "dexsplitsample/Class3$InnerClass.class";
-    featureStream.putNextEntry(new ZipEntry(name));
-    featureStream.write(Files.readAllBytes(Paths.get(CLASS3_INNER_CLASS)));
-    featureStream.closeEntry();
-    name = "dexsplitsample/Class3$1.class";
-    featureStream.putNextEntry(new ZipEntry(name));
-    featureStream.write(Files.readAllBytes(Paths.get(CLASS3_SYNTHETIC_CLASS)));
-    featureStream.closeEntry();
-    name = "dexsplitsample/Class4";
-    featureStream.putNextEntry(new ZipEntry(name));
-    featureStream.write(Files.readAllBytes(Paths.get(CLASS4_CLASS)));
-    featureStream.closeEntry();
-    name = "dexsplitsample/Class4$LambdaInterface";
-    featureStream.putNextEntry(new ZipEntry(name));
-    featureStream.write(Files.readAllBytes(Paths.get(CLASS4_LAMBDA_INTERFACE)));
-    featureStream.closeEntry();
-    featureStream.close();
-    // Make sure that we can pass in a name for the output.
-    String specificOutputName = "renamed";
-    if (useOptions) {
-      Options options = new Options();
-      options.addInputArchive(inputZip.toString());
-      options.setOutput(output.toString());
-      if (explicitBase) {
-        options.addBaseJar(baseJar.toString());
-      } else if (renameBase){
-        // Ensure that we can rename base (if people called a feature base)
-        options.setBaseOutputName("base_renamed");
-      }
-      options.addFeatureJar(featureJar.toString(), specificOutputName);
-      DexSplitter.run(options);
-    } else {
-      List<String> args = Lists.newArrayList(
-          "--input",
-          inputZip.toString(),
-          "--output",
-          output.toString(),
-          "--feature-jar",
-          featureJar.toString().concat(":").concat(specificOutputName));
-      if (explicitBase) {
-        args.add("--base-jar");
-        args.add(baseJar.toString());
-      } else if (renameBase) {
-        args.add("--base-output-name");
-        args.add("base_renamed");
-      }
-
-      DexSplitter.main(args.toArray(StringUtils.EMPTY_ARRAY));
-    }
-    String baseOutputName = explicitBase || !renameBase ? "base" : "base_renamed";
-    Path base = output.resolve(baseOutputName).resolve("classes.dex");
-    Path feature = output.resolve(specificOutputName).resolve("classes.dex");;
-    validateUnobfuscatedOutput(base, feature);
-  }
-
-  @Test
-  public void splitFilesObfuscation()
-      throws CompilationFailedException, IOException, ExecutionException {
-    // Initial normal compile to create dex files.
-    Path inputDex = temp.newFolder().toPath().resolve("input.zip");
-    Path proguardMap = temp.getRoot().toPath().resolve("proguard.map");
-
-    R8.run(
-        R8Command.builder()
-            .setOutput(inputDex, OutputMode.DexIndexed)
-            .addProgramFiles(Paths.get(CLASS1_CLASS))
-            .addProgramFiles(Paths.get(CLASS2_CLASS))
-            .addProgramFiles(Paths.get(CLASS3_CLASS))
-            .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
-            .addProgramFiles(Paths.get(CLASS3_SYNTHETIC_CLASS))
-            .addProgramFiles(Paths.get(CLASS4_CLASS))
-            .addProgramFiles(Paths.get(CLASS4_LAMBDA_INTERFACE))
-            .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
-            .setProguardMapOutputPath(proguardMap)
-            .addProguardConfiguration(getProguardConf(), Origin.unknown())
-            .build());
-
-    Path outputDex = temp.newFolder().toPath().resolve("output");
-    Files.createDirectory(outputDex);
-    Path splitSpec = createSplitSpec();
-
-    DexSplitter.main(
-        new String[] {
-          "--input", inputDex.toString(),
-          "--output", outputDex.toString(),
-          "--feature-splits", splitSpec.toString(),
-          "--proguard-map", proguardMap.toString()
-        });
-
-    Path base = outputDex.resolve("base").resolve("classes.dex");
-    Path feature = outputDex.resolve("feature1").resolve("classes.dex");
-    String class3 = "dexsplitsample.Class3";
-    // We should still be able to run the Class3 which we kept, it has a call to the obfuscated
-    // class1 which is in base.
-    ArtCommandBuilder builder = new ArtCommandBuilder();
-    builder.appendClasspath(base.toString());
-    builder.appendClasspath(feature.toString());
-    builder.setMainClass(class3);
-    String out = ToolHelper.runArt(builder);
-    assertEquals(out, "Class3\n");
-
-    // Class1 should not be in the feature, it should still be in base.
-    builder = new ArtCommandBuilder();
-    builder.appendClasspath(feature.toString());
-    builder.setMainClass(class3);
-    try {
-      ToolHelper.runArt(builder);
-      assertFalse(true);
-    } catch (AssertionError assertionError) {
-      // We expect this to throw since base is not in the path and Class3 depends on Class1.
-    }
-
-    // Ensure that the Class1 is actually in the correct split. Note that Class2 would have been
-    // shaken away.
-    CodeInspector inspector = new CodeInspector(base, proguardMap);
-    ClassSubject subject = inspector.clazz("dexsplitsample.Class1");
-    assertTrue(subject.isPresent());
-    assertTrue(subject.isRenamed());
-  }
-
-  @Test
-  public void splitNonClassFiles()
-      throws CompilationFailedException, IOException, FeatureMappingException {
-    Path inputZip = temp.getRoot().toPath().resolve("input-zip-with-non-class-files.jar");
-    ZipOutputStream inputZipStream = new ZipOutputStream(Files.newOutputStream(inputZip));
-    String name = "dexsplitsample/TextFile.txt";
-    inputZipStream.putNextEntry(new ZipEntry(name));
-    byte[] fileBytes = Files.readAllBytes(Paths.get(TEXT_FILE));
-    inputZipStream.write(fileBytes);
-    inputZipStream.closeEntry();
-    name = "dexsplitsample/TextFile2.txt";
-    inputZipStream.putNextEntry(new ZipEntry(name));
-    inputZipStream.write(fileBytes);
-    inputZipStream.write(fileBytes);
-    inputZipStream.closeEntry();
-    inputZipStream.close();
-    Path output = temp.newFolder().toPath().resolve("output");
-    Files.createDirectory(output);
-    Path baseJar = temp.getRoot().toPath().resolve("base.jar");
-    Path featureJar = temp.getRoot().toPath().resolve("feature1.jar");
-    ZipOutputStream baseStream = new ZipOutputStream(Files.newOutputStream(baseJar));
-    name = "dexsplitsample/TextFile.txt";
-    baseStream.putNextEntry(new ZipEntry(name));
-    baseStream.write(fileBytes);
-    baseStream.closeEntry();
-    baseStream.close();
-    ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar));
-    name = "dexsplitsample/TextFile2.txt";
-    featureStream.putNextEntry(new ZipEntry(name));
-    featureStream.write(fileBytes);
-    featureStream.write(fileBytes);
-    featureStream.closeEntry();
-    featureStream.close();
-    Options options = new Options();
-    options.addInputArchive(inputZip.toString());
-    options.setOutput(output.toString());
-    options.addFeatureJar(baseJar.toString());
-    options.addFeatureJar(featureJar.toString());
-    options.setSplitNonClassResources(true);
-    DexSplitter.run(options);
-    Path baseDir = output.resolve("base");
-    Path featureDir = output.resolve("feature1");
-    byte[] contents = fileBytes;
-    byte[] contents2 = new byte[contents.length * 2];
-    System.arraycopy(contents, 0, contents2, 0, contents.length);
-    System.arraycopy(contents, 0, contents2, contents.length, contents.length);
-    Path baseTextFile = baseDir.resolve("dexsplitsample/TextFile.txt");
-    Path featureTextFile = featureDir.resolve("dexsplitsample/TextFile2.txt");
-    assert Files.exists(baseTextFile);
-    assert Files.exists(featureTextFile);
-    assert Arrays.equals(Files.readAllBytes(baseTextFile), contents);
-    assert Arrays.equals(Files.readAllBytes(featureTextFile), contents2);
-  }
-
-  @Test
-  public void splitDuplicateNonClassFiles()
-      throws IOException, CompilationFailedException, FeatureMappingException {
-    Path inputZip = temp.getRoot().toPath().resolve("input-zip-with-non-class-files.jar");
-    ZipOutputStream inputZipStream = new ZipOutputStream(Files.newOutputStream(inputZip));
-    String name = "dexsplitsample/TextFile.txt";
-    inputZipStream.putNextEntry(new ZipEntry(name));
-    byte[] fileBytes = Files.readAllBytes(Paths.get(TEXT_FILE));
-    inputZipStream.write(fileBytes);
-    inputZipStream.closeEntry();
-    inputZipStream.close();
-    Path output = temp.newFolder().toPath().resolve("output");
-    Files.createDirectory(output);
-    Path featureJar = temp.getRoot().toPath().resolve("feature1.jar");
-    Path feature2Jar = temp.getRoot().toPath().resolve("feature2.jar");
-    ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar));
-    name = "dexsplitsample/TextFile.txt";
-    featureStream.putNextEntry(new ZipEntry(name));
-    featureStream.write(fileBytes);
-    featureStream.closeEntry();
-    featureStream.close();
-    ZipOutputStream feature2Stream = new ZipOutputStream(Files.newOutputStream(feature2Jar));
-    name = "dexsplitsample/TextFile.txt";
-    feature2Stream.putNextEntry(new ZipEntry(name));
-    feature2Stream.write(fileBytes);
-    feature2Stream.closeEntry();
-    feature2Stream.close();
-    Options options = new Options();
-    options.addInputArchive(inputZip.toString());
-    options.setOutput(output.toString());
-    options.addFeatureJar(feature2Jar.toString());
-    options.addFeatureJar(featureJar.toString());
-    options.setSplitNonClassResources(true);
-    DexSplitter.run(options);
-    Path baseDir = output.resolve("base");
-    Path feature1Dir = output.resolve("feature1");
-    Path feature2Dir = output.resolve("feature2");
-    Path baseTextFile = baseDir.resolve("dexsplitsample/TextFile.txt");
-    Path feature1TextFile = feature1Dir.resolve("dexsplitsample/TextFile2.txt");
-    Path feature2TextFile = feature2Dir.resolve("dexsplitsample/TextFile2.txt");
-    assert !Files.exists(feature1TextFile);
-    assert !Files.exists(feature2TextFile);
-    assert Files.exists(baseTextFile);
-    assert Arrays.equals(Files.readAllBytes(baseTextFile), fileBytes);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
index 05d6d3d..bd7929d 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -17,12 +17,9 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.dexsplitter.DexSplitter.Options;
 import com.android.tools.r8.utils.ArchiveResourceProvider;
 import com.android.tools.r8.utils.Pair;
-import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.ZipUtils;
-import com.google.common.collect.ImmutableList;
 import com.google.common.io.ByteStreams;
 import dalvik.system.PathClassLoader;
 import java.io.IOException;
@@ -31,9 +28,7 @@
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.List;
 import java.util.Set;
-import java.util.function.Consumer;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 import java.util.zip.ZipOutputStream;
@@ -155,96 +150,6 @@
         toRun, baseOutput, r8TestCompileResult.getFeature(0), parameters.getRuntime());
   }
 
-  // Compile the passed in classes plus RunInterface and SplitRunner using R8, then split
-  // based on the base/feature sets. toRun must implement the BaseRunInterface
-  <E extends Throwable> ProcessResult testDexSplitter(
-      TestParameters parameters,
-      Set<Class<?>> baseClasses,
-      Set<Class<?>> featureClasses,
-      Class<?> toRun,
-      String expectedOutput,
-      ThrowingConsumer<R8TestCompileResult, E> compileResultConsumer,
-      Consumer<R8FullTestBuilder> r8TestConfigurator)
-      throws Exception, E {
-    List<Class<?>> baseClassesWithRunner =
-        ImmutableList.<Class<?>>builder()
-            .add(RunInterface.class, SplitRunner.class)
-            .addAll(baseClasses)
-            .build();
-
-    Path baseJar = jarTestClasses(baseClassesWithRunner);
-    Path featureJar = jarTestClasses(featureClasses);
-
-    Path featureOnly =
-        testForR8(parameters.getBackend())
-            .addProgramClasses(featureClasses)
-            .addClasspathClasses(baseClasses)
-            .addClasspathClasses(RunInterface.class)
-            .addKeepAllClassesRule()
-            .addInliningAnnotations()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .writeToZip();
-    if (parameters.isDexRuntime()) {
-      // With D8 this should just work. We compile all of the base classes, then run with the
-      // feature loaded at runtime. Since there is no inlining/class merging we don't
-      // have any issues.
-      testForD8()
-          .addProgramClasses(SplitRunner.class, RunInterface.class)
-          .addProgramClasses(baseClasses)
-          .setMinApi(parameters.getApiLevel())
-          .compile()
-          .run(
-              parameters.getRuntime(),
-              SplitRunner.class,
-              toRun.getName(),
-              featureOnly.toAbsolutePath().toString())
-          .assertSuccessWithOutput(expectedOutput);
-    }
-
-    R8FullTestBuilder builder = testForR8(parameters.getBackend());
-    if (parameters.isCfRuntime()) {
-      // Compiling to jar we need to support the same way of loading code at runtime as
-      // android supports.
-      builder
-          .addProgramClasses(PathClassLoader.class)
-          .addKeepClassAndMembersRules(PathClassLoader.class);
-    }
-
-    R8FullTestBuilder r8FullTestBuilder =
-        builder
-            .setMinApi(parameters.getApiLevel())
-            .addProgramClasses(SplitRunner.class, RunInterface.class)
-            .addProgramClasses(baseClasses)
-            .addProgramClasses(featureClasses)
-            .addKeepMainRule(SplitRunner.class)
-            .addKeepClassRules(toRun);
-    r8TestConfigurator.accept(r8FullTestBuilder);
-    R8TestCompileResult r8TestCompileResult = r8FullTestBuilder.compile();
-    compileResultConsumer.accept(r8TestCompileResult);
-    Path fullFiles = r8TestCompileResult.writeToZip();
-
-    // Ensure that we can run the program as a unit (i.e., without splitting)
-    r8TestCompileResult
-        .run(parameters.getRuntime(), SplitRunner.class, toRun.getName())
-        .assertSuccessWithOutput(expectedOutput);
-
-    Path splitterOutput = temp.newFolder().toPath();
-    Path splitterBaseDexFile = splitterOutput.resolve("base").resolve("classes.dex");
-    Path splitterFeatureDexFile = splitterOutput.resolve("feature").resolve("classes.dex");
-
-    Options options = new Options();
-    options.setOutput(splitterOutput.toString());
-    options.addBaseJar(baseJar.toString());
-    options.addFeatureJar(featureJar.toString(), "feature");
-
-    options.addInputArchive(fullFiles.toString());
-    DexSplitter.run(options);
-
-    return runFeatureOnArt(
-        toRun, splitterBaseDexFile, splitterFeatureDexFile, parameters.getRuntime());
-  }
-
   ProcessResult runFeatureOnArt(
       Class toRun, Path splitterBaseDexFile, Path splitterFeatureDexFile, TestRuntime runtime)
       throws IOException {
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeFinalTest.java b/src/test/java/com/android/tools/r8/graph/InvokeFinalTest.java
index 54f2fa4..6aaf325 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeFinalTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeFinalTest.java
@@ -75,8 +75,6 @@
     }
 
     public void bar() {
-      // TODO(b/110175213): We cannot change this to an invoke-special since this requires a
-      //  direct bridge in DEX.
       foo();
     }
   }
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index d3e91bb..ced7e4f 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -97,7 +97,7 @@
     ProgramMethod method = getMethod(inspector, DEFAULT_CLASS_NAME, "int", "x", ImmutableList.of());
     assertFalse(
         appInfo
-            .resolveMethodOnClassHolder(method.getReference())
+            .resolveMethodOnClassHolderLegacy(method.getReference())
             .getSingleTarget()
             .isVirtualMethod());
     assertNull(appInfo.lookupDirectTarget(method.getReference(), method));
@@ -181,14 +181,15 @@
 
     assertFalse(
         appInfo
-            .resolveMethodOnClass(classTestSuper, methodXOnTestSuper.getReference())
+            .resolveMethodOnClassLegacy(classTestSuper, methodXOnTestSuper.getReference())
             .getSingleTarget()
             .isVirtualMethod());
     assertNull(
         appInfo
-            .resolveMethodOnClass(classTest, methodXOnTestSuper.getReference())
+            .resolveMethodOnClassLegacy(classTest, methodXOnTestSuper.getReference())
             .getSingleTarget());
-    assertNull(appInfo.resolveMethodOnClass(classTest, methodXOnTestReference).getSingleTarget());
+    assertNull(
+        appInfo.resolveMethodOnClassLegacy(classTest, methodXOnTestReference).getSingleTarget());
 
     assertNull(appInfo.lookupDirectTarget(methodXOnTestSuper.getReference(), methodXOnTestSuper));
     assertNull(appInfo.lookupDirectTarget(methodXOnTestReference, methodYOnTest));
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java
index d05122a..d574d5a 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java
@@ -12,6 +12,7 @@
 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 java.io.IOException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -39,9 +40,14 @@
         .addProgramClasses(I.class)
         .addProgramClassFileData(getClassWithTransformedInvoked())
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatThrowsIf(parameters.isCfRuntime(), VerifyError.class)
         // TODO(b/144410139): Consider making this a compilation failure when generating DEX.
-        .assertSuccessWithOutputLinesIf(parameters.isDexRuntime(), "Hello World!");
+        .applyIf(
+            parameters.isCfRuntime(),
+            r -> r.assertFailureWithErrorThatThrows(VerifyError.class),
+            !(parameters.isDexRuntimeVersion(Version.V13_0_0)
+                && parameters.canUseDefaultAndStaticInterfaceMethods()),
+            r -> r.assertSuccessWithOutputLines("Hello World!"),
+            r -> r.assertFailureWithErrorThatThrows(NoSuchMethodError.class));
   }
 
   @Test
@@ -54,14 +60,17 @@
         .enableNoMethodStaticizingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatThrowsIf(parameters.isCfRuntime(), VerifyError.class)
         // TODO(b/144410139): Consider making this a compilation failure when generating DEX.
-        .assertSuccessWithOutputLinesIf(
+        .applyIf(
+            parameters.isCfRuntime(),
+            r -> r.assertFailureWithErrorThatThrows(VerifyError.class),
             parameters.isDexRuntime() && !parameters.canUseDefaultAndStaticInterfaceMethods(),
-            "Hello World!")
-        .assertFailureWithErrorThatThrowsIf(
-            parameters.isDexRuntime() && parameters.canUseDefaultAndStaticInterfaceMethods(),
-            NullPointerException.class);
+            r -> r.assertSuccessWithOutputLines("Hello World!"),
+            parameters.isDexRuntime()
+                && !parameters.isDexRuntimeVersion(Version.V13_0_0)
+                && parameters.canUseDefaultAndStaticInterfaceMethods(),
+            r -> r.assertFailureWithErrorThatThrows(NullPointerException.class),
+            r -> r.assertFailureWithErrorThatThrows(NoSuchMethodError.class));
   }
 
   private byte[] getClassWithTransformedInvoked() throws IOException {
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 956d59e..b8078ae 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -72,11 +72,12 @@
     // Check lookup will produce the same result.
     DexMethod id = method.getReference();
     assertEquals(
-        appInfo().resolveMethodOnClass(id.holder, method.getReference()).getSingleTarget(), method);
+        appInfo().resolveMethodOnClassLegacy(id.holder, method.getReference()).getSingleTarget(),
+        method);
 
     // Check lookup targets with include method.
     MethodResolutionResult resolutionResult =
-        appInfo().resolveMethodOnClass(clazz, method.getReference());
+        appInfo().resolveMethodOnClassLegacy(clazz, method.getReference());
     AppInfoWithLiveness appInfo = null; // TODO(b/154881041): Remove or compute liveness.
     LookupResult lookupResult =
         resolutionResult.lookupVirtualDispatchTargets(
@@ -97,7 +98,7 @@
     AppInfoWithLiveness appInfo = null; // TODO(b/154881041): Remove or compute liveness.
     LookupResultSuccess lookupResult =
         appInfo()
-            .resolveMethodOnInterface(clazz, method.getReference())
+            .resolveMethodOnInterfaceLegacy(clazz, method.getReference())
             .lookupVirtualDispatchTargets(clazz, appInfo(), appInfo, dexReference -> false)
             .asLookupResultSuccess();
     assertNotNull(lookupResult);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceInstanceofTest.java b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceInstanceofTest.java
index a729899..68c055d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceInstanceofTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceInstanceofTest.java
@@ -99,7 +99,7 @@
     }
     if (parameters.isDexRuntime()) {
       if (parameters.getDexRuntimeVersion().isEqualTo(Version.V7_0_0)
-          || parameters.getDexRuntimeVersion().isEqualTo(Version.V13_MASTER)) {
+          || parameters.getDexRuntimeVersion().isEqualTo(Version.V13_0_0)) {
         return ImmutableList.of("true");
       }
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/loops/LoopWith1Iterations.java b/src/test/java/com/android/tools/r8/ir/optimize/loops/LoopWith1Iterations.java
index 6bbfb60..dc3ca19 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/loops/LoopWith1Iterations.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/loops/LoopWith1Iterations.java
@@ -33,7 +33,6 @@
         .setMinApi(parameters.getApiLevel())
         .addProgramClasses(Main.class)
         .addKeepMainRule(Main.class)
-        .addOptionsModification(options -> options.testing.enableExperimentalLoopUnrolling = true)
         .enableInliningAnnotations()
         .noMinification()
         .compile()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/loops/LoopWith1IterationsEscape.java b/src/test/java/com/android/tools/r8/ir/optimize/loops/LoopWith1IterationsEscape.java
new file mode 100644
index 0000000..07efa85
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/loops/LoopWith1IterationsEscape.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2022, 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.loops;
+
+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;
+
+@RunWith(Parameterized.class)
+public class LoopWith1IterationsEscape extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public LoopWith1IterationsEscape(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testLoopRemoved() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(Main.class)
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .noMinification()
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("end 0", "iteration", "end 1");
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      loopExit();
+      loopNoExit();
+    }
+
+    @NeverInline
+    public static void loopNoExit() {
+      Object[] objects = new Object[1];
+      int i;
+      for (i = 0; i < objects.length; i++) {
+        if (System.currentTimeMillis() < 0) {
+          break;
+        }
+        System.out.println("iteration");
+      }
+      System.out.println("end " + i);
+    }
+
+    @NeverInline
+    public static void loopExit() {
+      Object[] objects = new Object[1];
+      int i;
+      for (i = 0; i < objects.length; i++) {
+        if (System.currentTimeMillis() > 0) {
+          break;
+        }
+        System.out.println("iteration");
+      }
+      System.out.println("end " + i);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantInstanceStoreTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantInstanceStoreTest.java
index 4c3f07d..29ab7c9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantInstanceStoreTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantInstanceStoreTest.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -36,7 +37,11 @@
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines("1");
+        .applyIf(
+            // See b/229706824.
+            parameters.getDexRuntimeVersion().equals(Version.V13_0_0),
+            r -> r.assertSuccessWithOutputLines("0"),
+            r -> r.assertSuccessWithOutputLines("1"));
   }
 
   static class Main {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantStaticStoreTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantStaticStoreTest.java
index 60a0b34..abf3664 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantStaticStoreTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantStaticStoreTest.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -36,7 +37,11 @@
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines("1");
+        .applyIf(
+            // See b/229706824.
+            parameters.getDexRuntimeVersion().equals(Version.V13_0_0),
+            r -> r.assertSuccessWithOutputLines("0"),
+            r -> r.assertSuccessWithOutputLines("1"));
   }
 
   static class Main {
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress65007724.java b/src/test/java/com/android/tools/r8/jasmin/Regress65007724.java
index cdc5b63..90660f4 100644
--- a/src/test/java/com/android/tools/r8/jasmin/Regress65007724.java
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress65007724.java
@@ -6,7 +6,6 @@
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -51,11 +50,6 @@
             .setMinApi(parameters.getApiLevel())
             .addProgramClassFileData(builder.buildClasses())
             .run(parameters.getRuntime(), clazz.name);
-    if (parameters.getDexRuntimeVersion().isEqualTo(Version.V13_MASTER)) {
-      // See b/220821265
-      d8TestRunResult.assertFailure();
-    } else {
-      d8TestRunResult.assertSuccessWithOutput("Hello World!");
-    }
+    d8TestRunResult.assertSuccessWithOutput("Hello World!");
   }
 }
diff --git a/src/test/java/com/android/tools/r8/lightir/LIRBasicCallbackTest.java b/src/test/java/com/android/tools/r8/lightir/LIRBasicCallbackTest.java
new file mode 100644
index 0000000..0602eb3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/lightir/LIRBasicCallbackTest.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2022, 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.lightir;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.IntBox;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LIRBasicCallbackTest extends TestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public LIRBasicCallbackTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  @Test
+  public void test() throws Exception {
+    LIRCode code = LIRCode.builder().addConstNull().addConstInt(42).build();
+
+    // State to keep track of position in the byte array as we don't expose this in the iterator.
+    IntBox offset = new IntBox(0);
+
+    LIRIterator it = code.iterator();
+
+    // The iterator and the elements are the same object providing a view on the byte stream.
+    assertTrue(it.hasNext());
+    LIRInstructionView next = it.next();
+    assertSame(it, next);
+
+    it.accept(
+        (int opcode, int operandOffset, int operandSize) -> {
+          int headerSize = 1;
+          assertEquals(LIROpcodes.ACONST_NULL, opcode);
+          assertEquals(offset.get() + headerSize, operandOffset);
+          assertEquals(0, operandSize);
+          offset.increment(headerSize + operandSize);
+        });
+
+    assertTrue(it.hasNext());
+    it.next();
+    it.accept(
+        (int opcode, int operandOffset, int operandSize) -> {
+          int headerSize = 2; // opcode + payload-size
+          assertEquals(LIROpcodes.ICONST, opcode);
+          assertEquals(offset.get() + headerSize, operandOffset);
+          assertEquals(4, operandSize);
+          offset.increment(headerSize + operandSize);
+        });
+    assertFalse(it.hasNext());
+
+    // The iterator can also be use in a normal java for-each loop.
+    // However, the item is not an actual item just a current view, so it can't be cached!
+    LIRInstructionView oldView = null;
+    for (LIRInstructionView view : code) {
+      if (oldView == null) {
+        oldView = view;
+        view.accept((opcode, ignore1, ignore2) -> assertEquals(LIROpcodes.ACONST_NULL, opcode));
+      } else {
+        assertSame(oldView, view);
+        view.accept((opcode, ignore1, ignore2) -> assertEquals(LIROpcodes.ICONST, opcode));
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index dd40ecf..75b7dda 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -25,8 +25,6 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.dexsplitter.DexSplitter;
-import com.android.tools.r8.dexsplitter.DexSplitter.Options;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.errors.Unreachable;
@@ -255,49 +253,6 @@
   }
 
   @Test
-  public void everyThirdClassInMainWithDexSplitter() throws Throwable {
-    List<String> featureMappings = new ArrayList<>();
-    List<String> inFeatureMapping = new ArrayList<>();
-
-    ImmutableList.Builder<String> mainDexBuilder = ImmutableList.builder();
-    for (int i = 0; i < MANY_CLASSES.size(); i++) {
-      String clazz = MANY_CLASSES.get(i);
-      // Write the first 2 classes into the split.
-      if (i < 10) {
-        featureMappings.add(clazz + ":feature1");
-        inFeatureMapping.add(clazz);
-      }
-      if (i % 3 == 0) {
-        mainDexBuilder.add(clazz);
-      }
-    }
-    Path featureSplitMapping = temp.getRoot().toPath().resolve("splitmapping");
-    Path mainDexFile = temp.getRoot().toPath().resolve("maindex");
-    FileUtils.writeTextFile(featureSplitMapping, featureMappings);
-    List<String> mainDexList = mainDexBuilder.build();
-    FileUtils.writeTextFile(mainDexFile, ListUtils.map(mainDexList, MainDexListTests::typeToEntry));
-    Path output = temp.getRoot().toPath().resolve("split_output");
-    Files.createDirectories(output);
-    TestDiagnosticsHandler diagnosticsHandler = new TestDiagnosticsHandler();
-    Options options = new Options(diagnosticsHandler);
-    options.addInputArchive(getManyClassesMultiDexAppPath().toString());
-    options.setFeatureSplitMapping(featureSplitMapping.toString());
-    options.setOutput(output.toString());
-    options.setMainDexList(mainDexFile.toString());
-    DexSplitter.run(options);
-    assertEquals(0, diagnosticsHandler.numberOfErrorsAndWarnings());
-    Path baseDir = output.resolve("base");
-    CodeInspector inspector =
-        new CodeInspector(
-            AndroidApp.builder().addProgramFiles(baseDir.resolve("classes.dex")).build());
-    for (String clazz : mainDexList) {
-      if (!inspector.clazz(clazz).isPresent() && !inFeatureMapping.contains(clazz)) {
-        failedToFindClassInExpectedFile(baseDir, clazz);
-      }
-    }
-  }
-
-  @Test
   public void singleClassInMainDex() throws Throwable {
     ImmutableList<String> mainDex = ImmutableList.of(MANY_CLASSES.get(0));
     verifyMainDexContains(mainDex, getManyClassesSingleDexAppPath(), true);
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/InterfaceInvokeWithNonTrivialButImpreciseStaticTypeTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/InterfaceInvokeWithNonTrivialButImpreciseStaticTypeTest.java
new file mode 100644
index 0000000..836c703
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/InterfaceInvokeWithNonTrivialButImpreciseStaticTypeTest.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2022, 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.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+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;
+
+@RunWith(Parameterized.class)
+public class InterfaceInvokeWithNonTrivialButImpreciseStaticTypeTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("B");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      I i = System.currentTimeMillis() > 0 ? new B() : new C();
+      i.m();
+    }
+  }
+
+  @NoVerticalClassMerging
+  interface I {
+
+    void m();
+  }
+
+  static class A {}
+
+  @NoHorizontalClassMerging
+  static class B extends A implements I {
+
+    @Override
+    public void m() {
+      System.out.println("B");
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class C extends A implements I {
+
+    @Override
+    public void m() {
+      System.out.println("C");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
index 7ea76aa..5b061d7 100644
--- a/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
@@ -50,18 +50,18 @@
         };
     DexEncodedMethod langObjectNotifyMethod =
         appInfo
-            .resolveMethodOnClassHolder(
+            .resolveMethodOnClassHolderLegacy(
                 factory.createMethod(fooType, factory.createProto(factory.voidType), "notify"))
             .getSingleTarget();
     for (DexType arrType : arrayTypes) {
       assertNull(
           appInfo
-              .resolveMethodOnClassHolder(
+              .resolveMethodOnClassHolderLegacy(
                   factory.createMethod(arrType, factory.createProto(arrType), "clone"))
               .getSingleTarget());
       DexEncodedMethod target =
           appInfo
-              .resolveMethodOnClassHolder(
+              .resolveMethodOnClassHolderLegacy(
                   factory.createMethod(arrType, factory.createProto(factory.voidType), "notify"))
               .getSingleTarget();
       assertEquals(langObjectNotifyMethod, target);
diff --git a/src/test/java/com/android/tools/r8/resolution/InvokePolymorphicResolutionTest.java b/src/test/java/com/android/tools/r8/resolution/InvokePolymorphicResolutionTest.java
index 1b0af62..db280ea 100644
--- a/src/test/java/com/android/tools/r8/resolution/InvokePolymorphicResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/InvokePolymorphicResolutionTest.java
@@ -48,7 +48,9 @@
     MethodReference invokeExact =
         methodFromMethod(MethodHandle.class.getMethod("invokeExact", Object[].class));
     MethodResolutionResult resolution1 =
-        appView.appInfo().resolveMethod(buildMethod(invokeExact, appView.dexItemFactory()), false);
+        appView
+            .appInfo()
+            .resolveMethodLegacy(buildMethod(invokeExact, appView.dexItemFactory()), false);
     assertFalse(resolution1.isFailedResolution());
 
     // An inexact signature should also find invokeExact.
@@ -61,7 +63,7 @@
     MethodResolutionResult resolution2 =
         appView
             .appInfo()
-            .resolveMethod(buildMethod(inexactInvokeExact, appView.dexItemFactory()), false);
+            .resolveMethodLegacy(buildMethod(inexactInvokeExact, appView.dexItemFactory()), false);
     assertFalse(resolution2.isFailedResolution());
 
     // The should both be the same MethodHandle.invokeExact method.
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 67007d2c..ebee166 100644
--- a/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java
@@ -59,7 +59,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(Base.class, "collect", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 76cf55a..4c668d0 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -194,7 +194,7 @@
         buildNullaryVoidMethod(invokeReceiver, methodName, appInfo.dexItemFactory());
     ProgramMethod context =
         appInfo.definitionForProgramType(reference.holder).getProgramDefaultInitializer();
-    Assert.assertNotNull(appInfo.resolveMethodOnClassHolder(reference).getSingleTarget());
+    Assert.assertNotNull(appInfo.resolveMethodOnClassHolderLegacy(reference).getSingleTarget());
     DexEncodedMethod singleVirtualTarget =
         appInfo.lookupSingleVirtualTarget(appView, reference, context, false);
     if (singleTargetHolderOrNull == null) {
@@ -209,8 +209,8 @@
   @Test
   public void lookupVirtualTargets() {
     DexMethod method = buildNullaryVoidMethod(invokeReceiver, methodName, appInfo.dexItemFactory());
-    Assert.assertNotNull(appInfo.resolveMethodOnClassHolder(method).getSingleTarget());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    Assert.assertNotNull(appInfo.resolveMethodOnClassHolderLegacy(method).getSingleTarget());
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 9de13d2..4fbc7e7 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
@@ -91,7 +91,7 @@
   @Test
   public void resolveTarget() {
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
+        appInfo.resolveMethodOnClassLegacy(methodOnB.holder, methodOnB);
     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 d8ad8dd..77f847c 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
@@ -108,7 +108,7 @@
   @Test
   public void testResolution() {
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
+        appInfo.resolveMethodOnClassLegacy(methodOnB.holder, methodOnB);
     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 ca541ea..8118ef2 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
@@ -128,7 +128,7 @@
     DexProgramClass bClass = appInfo.definitionForProgramType(methodOnBReference.holder);
     ProgramMethod methodOnB = bClass.lookupProgramMethod(methodOnBReference);
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOnInterface(methodOnBReference.holder, methodOnBReference);
+        appInfo.resolveMethodOnInterfaceLegacy(methodOnBReference.holder, methodOnBReference);
     DexEncodedMethod resolved = resolutionResult.getSingleTarget();
     assertEquals(methodOnBReference, resolved.getReference());
     assertFalse(resolutionResult.isVirtualTarget());
@@ -140,7 +140,7 @@
   @Test
   public void lookupVirtualTargets() {
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOnInterface(methodOnBReference.holder, methodOnBReference);
+        appInfo.resolveMethodOnInterfaceLegacy(methodOnBReference.holder, methodOnBReference);
     DexEncodedMethod resolved = resolutionResult.getSingleTarget();
     assertEquals(methodOnBReference, resolved.getReference());
     assertFalse(resolutionResult.isVirtualTarget());
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 c40f080..47c0857 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
@@ -175,7 +175,7 @@
   public void lookupSingleTarget() {
     DexProgramClass bClass = appView.definitionForProgramType(methodOnB.holder);
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
+        appInfo.resolveMethodOnClassLegacy(methodOnB.holder, methodOnB);
     DexEncodedMethod resolved = resolutionResult.getSingleTarget();
     assertEquals(methodOnA, resolved.getReference());
     assertFalse(resolutionResult.isVirtualTarget());
@@ -188,7 +188,7 @@
   @Test
   public void lookupVirtualTargets() {
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
+        appInfo.resolveMethodOnClassLegacy(methodOnB.holder, methodOnB);
     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 898cfae..aa827c8 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
@@ -122,7 +122,7 @@
     // Resolve the method from the point of the declared holder.
     assertEquals(method.holder, declaredClassDefinition.type);
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOn(declaredClassDefinition, method);
+        appInfo.resolveMethodOnLegacy(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 82c6b63..50f4428 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
@@ -123,7 +123,7 @@
     // Resolve the method from the point of the declared holder.
     assertEquals(method.holder, declaredClassDefinition.type);
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOn(declaredClassDefinition, method);
+        appInfo.resolveMethodOnLegacy(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 b14ab55..1a6485c 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
@@ -96,7 +96,7 @@
     // Resolve the method from the point of the declared holder.
     assertEquals(method.holder, declaredClassDefinition.type);
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOn(declaredClassDefinition, method);
+        appInfo.resolveMethodOnLegacy(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 5638929..5fddce3 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
@@ -121,7 +121,7 @@
     // Resolve the method from the point of the declared holder.
     assertEquals(method.holder, declaredClassDefinition.type);
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOn(declaredClassDefinition, method);
+        appInfo.resolveMethodOnLegacy(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 d65dabf..31a8904 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
@@ -98,7 +98,7 @@
     // Resolve the method from the point of the declared holder.
     assertEquals(method.holder, declaredClassDefinition.type);
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOn(declaredClassDefinition, method);
+        appInfo.resolveMethodOnLegacy(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 f1a35bb..63cd43e 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
@@ -74,7 +74,7 @@
     DexProgramClass bClass =
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(bar);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 d68610e..f7bfe55 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
@@ -73,7 +73,7 @@
     DexProgramClass bClass =
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(bar);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 ccc0430..7d9eb16 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
@@ -76,7 +76,7 @@
     DexProgramClass bClass =
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(bar);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 f2f7dd9..4a9c475 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
@@ -72,7 +72,7 @@
     DexProgramClass bClass =
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(bar);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 a91d3ed..754b19d 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
@@ -59,7 +59,7 @@
     DexProgramClass aClass =
         appInfo.definitionFor(buildType(A.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(bar);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 9ae877d..c5363de 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
@@ -65,7 +65,7 @@
     DexProgramClass cClass =
         appInfo.definitionFor(buildType(C.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(B.class.getMethod("foo"), appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(bar);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(bar);
     assertEquals(
         OptionalBool.TRUE, resolutionResult.isAccessibleForVirtualDispatchFrom(cClass, appInfo));
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificDifferentParentHierarchyTest.java b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificDifferentParentHierarchyTest.java
new file mode 100644
index 0000000..856d3fe
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificDifferentParentHierarchyTest.java
@@ -0,0 +1,153 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.duplicatedefinitions;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import java.nio.file.Path;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+/**
+ * This is testing resolving Main.f for:
+ *
+ * <pre>
+ * I: I_L { f }
+ * J: J_L extends I { }, J_P { f }
+ * class Main implements I, J
+ * </pre>
+ */
+public class MaximallySpecificDifferentParentHierarchyTest extends TestBase {
+
+  private static final String EXPECTED = "I::foo";
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private Path libraryClasses;
+
+  @Before
+  public void setup() throws Exception {
+    libraryClasses = temp.newFile("lib.jar").toPath();
+    ZipBuilder.builder(libraryClasses)
+        .addFilesRelative(
+            ToolHelper.getClassPathForTests(),
+            ToolHelper.getClassFileForTestClass(I.class),
+            ToolHelper.getClassFileForTestClass(J.class))
+        .build();
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AndroidApp.Builder builder = AndroidApp.builder();
+    builder
+        .addProgramFiles(ToolHelper.getClassFileForTestClass(Main.class))
+        .addClassProgramData(getJProgram());
+    builder.addLibraryFiles(parameters.getDefaultRuntimeLibrary(), libraryClasses);
+    AppView<AppInfoWithClassHierarchy> appView =
+        computeAppViewWithClassHierarchy(
+            builder.build(), null, options -> options.loadAllClassDefinitions = true);
+    AppInfoWithClassHierarchy appInfo = appView.appInfo();
+    DexMethod method = buildNullaryVoidMethod(Main.class, "foo", appInfo.dexItemFactory());
+    // TODO(b/214382176): Extend resolution to support multiple definition results.
+    assertThrows(
+        Unreachable.class,
+        () -> {
+          appInfo.unsafeResolveMethodDueToDexFormat(method);
+        });
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addRunClasspathFiles(libraryClasses)
+        .addProgramClasses(Main.class)
+        .addProgramClassFileData(getJProgram())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    runTest(testForD8(parameters.getBackend()))
+        // TODO(b/214382176): Extend resolution to support multiple definition results.
+        .assertFailureWithErrorThatThrowsIf(
+            !parameters.canUseDefaultAndStaticInterfaceMethods(),
+            IncompatibleClassChangeError.class)
+        .assertSuccessWithOutputLinesIf(
+            parameters.canUseDefaultAndStaticInterfaceMethods(), EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    // TODO(b/214382176): Extend resolution to support multiple definition results.
+    runTest(testForR8(parameters.getBackend()).addKeepMainRule(Main.class))
+        .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+  }
+
+  private TestRunResult<?> runTest(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder)
+      throws Exception {
+    return testBuilder
+        .addProgramClasses(Main.class)
+        .addProgramClassFileData(getJProgram())
+        .addDefaultRuntimeLibrary(parameters)
+        .addLibraryFiles(libraryClasses)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(options -> options.loadAllClassDefinitions = true)
+        .compile()
+        .addBootClasspathFiles(buildOnDexRuntime(parameters, libraryClasses))
+        .run(parameters.getRuntime(), Main.class);
+  }
+
+  private byte[] getJProgram() throws Exception {
+    return transformer(JProgram.class).setClassDescriptor(descriptor(J.class)).transform();
+  }
+
+  public interface I {
+    default void foo() {
+      System.out.println("I::foo");
+    }
+  }
+
+  public interface JProgram {
+    default void foo() {
+      System.out.println("J_Program::foo");
+    }
+  }
+
+  public interface J extends I {}
+
+  public static class Main implements I, J {
+
+    public static void main(String[] args) {
+      new Main().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultiplePathsICCETest.java b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultiplePathsICCETest.java
new file mode 100644
index 0000000..2e57f9f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultiplePathsICCETest.java
@@ -0,0 +1,146 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.duplicatedefinitions;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import java.nio.file.Path;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+/**
+ * This is testing resolving Main.f for:
+ *
+ * <pre>
+ * I: I_L { f }, I_P { f }
+ * J: J_L { f }, J_P { f }
+ * class Main implements I,J
+ * </pre>
+ */
+public class MaximallySpecificMultiplePathsICCETest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private Path libraryClasses;
+
+  @Before
+  public void setup() throws Exception {
+    libraryClasses = temp.newFile("lib.jar").toPath();
+    ZipBuilder.builder(libraryClasses)
+        .addFilesRelative(
+            ToolHelper.getClassPathForTests(),
+            ToolHelper.getClassFileForTestClass(J.class),
+            ToolHelper.getClassFileForTestClass(I.class))
+        .build();
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AndroidApp.Builder builder = AndroidApp.builder();
+    builder
+        .addProgramFiles(
+            ToolHelper.getClassFileForTestClass(I.class),
+            ToolHelper.getClassFileForTestClass(J.class))
+        .addClassProgramData(getMainWithInterfacesIAndJ());
+    builder.addLibraryFiles(parameters.getDefaultRuntimeLibrary(), libraryClasses);
+    AppView<AppInfoWithClassHierarchy> appView =
+        computeAppViewWithClassHierarchy(
+            builder.build(), null, options -> options.loadAllClassDefinitions = true);
+    AppInfoWithClassHierarchy appInfo = appView.appInfo();
+    DexMethod method = buildNullaryVoidMethod(Main.class, "foo", appInfo.dexItemFactory());
+    // TODO(b/214382176): Extend resolution to support multiple definition results.
+    assertThrows(
+        Unreachable.class,
+        () -> {
+          appInfo.unsafeResolveMethodDueToDexFormat(method);
+        });
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addRunClasspathFiles(libraryClasses)
+        .addProgramClasses(I.class, J.class)
+        .addProgramClassFileData(getMainWithInterfacesIAndJ())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    runTest(testForD8(parameters.getBackend()), IncompatibleClassChangeError.class);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    runTest(
+        testForR8(parameters.getBackend()).addKeepMainRule(Main.class),
+        IncompatibleClassChangeError.class);
+  }
+
+  private void runTest(
+      TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder, Class<? extends Throwable> errorClass)
+      throws Exception {
+    testBuilder
+        .addProgramClasses(I.class, J.class)
+        .addProgramClassFileData(getMainWithInterfacesIAndJ())
+        .addDefaultRuntimeLibrary(parameters)
+        .addLibraryFiles(libraryClasses)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(options -> options.loadAllClassDefinitions = true)
+        .compile()
+        .addBootClasspathFiles(buildOnDexRuntime(parameters, libraryClasses))
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(errorClass);
+  }
+
+  private byte[] getMainWithInterfacesIAndJ() throws Exception {
+    return transformer(Main.class).setImplements(I.class, J.class).transform();
+  }
+
+  public interface I {
+    default void foo() {
+      System.out.println("I::foo");
+    }
+  }
+
+  public interface J {
+    default void foo() {
+      System.out.println("J::foo");
+    }
+  }
+
+  public static class Main implements I /*, J */ {
+
+    public static void main(String[] args) {
+      new Main().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultiplePathsSuccessTest.java b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultiplePathsSuccessTest.java
new file mode 100644
index 0000000..c743ac4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultiplePathsSuccessTest.java
@@ -0,0 +1,167 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.duplicatedefinitions;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import java.nio.file.Path;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+/**
+ * This is testing resolving Main.f for:
+ *
+ * <pre>
+ * I: I_L { f }, I_P { }
+ * J: J_L { }, J_P { f }
+ * class Main implements I,J
+ * </pre>
+ */
+public class MaximallySpecificMultiplePathsSuccessTest extends TestBase {
+
+  private static final String EXPECTED = "I::foo";
+  // TODO(b/214382176): Extend resolution to support multiple definition results.
+  private static final String D8_R8_RESULT = "J::foo";
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private Path libraryClasses;
+
+  @Before
+  public void setup() throws Exception {
+    libraryClasses = temp.newFile("lib.jar").toPath();
+    ZipBuilder.builder(libraryClasses)
+        .addFilesRelative(
+            ToolHelper.getClassPathForTests(),
+            ToolHelper.getClassFileForTestClass(J.class),
+            ToolHelper.getClassFileForTestClass(I.class))
+        .build();
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AndroidApp.Builder builder = AndroidApp.builder();
+    builder
+        .addProgramFiles(ToolHelper.getClassFileForTestClass(Main.class))
+        .addClassProgramData(getIProgram())
+        .addClassProgramData(getJProgram());
+    builder.addLibraryFiles(parameters.getDefaultRuntimeLibrary(), libraryClasses);
+    AppView<AppInfoWithClassHierarchy> appView =
+        computeAppViewWithClassHierarchy(
+            builder.build(), null, options -> options.loadAllClassDefinitions = true);
+    AppInfoWithClassHierarchy appInfo = appView.appInfo();
+    DexMethod method = buildNullaryVoidMethod(Main.class, "foo", appInfo.dexItemFactory());
+    // TODO(b/214382176): Extend resolution to support multiple definition results.
+    assertThrows(
+        Unreachable.class,
+        () -> {
+          appInfo.unsafeResolveMethodDueToDexFormat(method);
+        });
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addRunClasspathFiles(libraryClasses)
+        .addProgramClasses(Main.class)
+        .addProgramClassFileData(getIProgram(), getJProgram())
+        .addDefaultRuntimeLibrary(parameters)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    boolean isDalvik = parameters.getDexRuntimeVersion().isDalvik();
+    runTest(testForD8(parameters.getBackend()))
+        .assertSuccessWithOutputLinesIf(isDalvik, D8_R8_RESULT)
+        .assertSuccessWithOutputLinesIf(
+            !isDalvik && !parameters.canUseDefaultAndStaticInterfaceMethods(), D8_R8_RESULT)
+        // TODO(b/214382176): Extend resolution to support multiple definition results.
+        .assertSuccessWithOutputLinesIf(
+            parameters.canUseDefaultAndStaticInterfaceMethods(), EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    // TODO(b/214382176): Extend resolution to support multiple definition results.
+    runTest(testForR8(parameters.getBackend()).addKeepMainRule(Main.class))
+        .assertFailureWithErrorThatThrowsIf(
+            parameters.canUseDefaultAndStaticInterfaceMethods(), NoSuchMethodError.class)
+        .assertSuccessWithOutputLinesIf(
+            !parameters.canUseDefaultAndStaticInterfaceMethods(), D8_R8_RESULT);
+  }
+
+  private TestRunResult<?> runTest(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder)
+      throws Exception {
+    return testBuilder
+        .addProgramClasses(Main.class)
+        .addProgramClassFileData(getIProgram(), getJProgram())
+        .addDefaultRuntimeLibrary(parameters)
+        .addLibraryFiles(libraryClasses)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(options -> options.loadAllClassDefinitions = true)
+        .compile()
+        .addBootClasspathFiles(buildOnDexRuntime(parameters, libraryClasses))
+        .run(parameters.getRuntime(), Main.class);
+  }
+
+  private byte[] getJProgram() throws Exception {
+    return transformer(JProgram.class).setClassDescriptor(descriptor(J.class)).transform();
+  }
+
+  private byte[] getIProgram() throws Exception {
+    return transformer(IProgram.class).setClassDescriptor(descriptor(I.class)).transform();
+  }
+
+  public interface I {
+    default void foo() {
+      System.out.println("I::foo");
+    }
+  }
+
+  public interface J {}
+
+  public interface IProgram {}
+
+  public interface JProgram {
+    default void foo() {
+      System.out.println("J::foo");
+    }
+  }
+
+  public static class Main implements I, J {
+
+    public static void main(String[] args) {
+      new Main().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultiplePathsThroughClassTest.java b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultiplePathsThroughClassTest.java
new file mode 100644
index 0000000..a14d33a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultiplePathsThroughClassTest.java
@@ -0,0 +1,158 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.duplicatedefinitions;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import java.nio.file.Path;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+/**
+ * This is testing resolving Main.f for:
+ *
+ * <pre>
+ * I: I_L { f }, I_P { f }
+ * A: A_P implements I { }
+ * J: J_P { f }
+ * class Main extends A implements J
+ * </pre>
+ */
+public class MaximallySpecificMultiplePathsThroughClassTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private Path libraryClasses;
+
+  @Before
+  public void setup() throws Exception {
+    libraryClasses = temp.newFile("lib.jar").toPath();
+    ZipBuilder.builder(libraryClasses)
+        .addFilesRelative(
+            ToolHelper.getClassPathForTests(), ToolHelper.getClassFileForTestClass(I.class))
+        .build();
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AndroidApp.Builder builder = AndroidApp.builder();
+    builder
+        .addProgramFiles(
+            ToolHelper.getClassFileForTestClass(Main.class),
+            ToolHelper.getClassFileForTestClass(J.class))
+        .addClassProgramData(getAWithImplementsI())
+        .addClassProgramData(getIProgram());
+    builder.addLibraryFiles(parameters.getDefaultRuntimeLibrary(), libraryClasses);
+    AppView<AppInfoWithClassHierarchy> appView =
+        computeAppViewWithClassHierarchy(
+            builder.build(), null, options -> options.loadAllClassDefinitions = true);
+    AppInfoWithClassHierarchy appInfo = appView.appInfo();
+    DexMethod method = buildNullaryVoidMethod(Main.class, "foo", appInfo.dexItemFactory());
+    // TODO(b/214382176): Extend resolution to support multiple definition results.
+    assertThrows(
+        Unreachable.class,
+        () -> {
+          appInfo.unsafeResolveMethodDueToDexFormat(method);
+        });
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addRunClasspathFiles(libraryClasses)
+        .addProgramClasses(J.class, Main.class)
+        .addProgramClassFileData(getAWithImplementsI(), getIProgram())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    runTest(testForD8(parameters.getBackend()), IncompatibleClassChangeError.class);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    runTest(
+        testForR8(parameters.getBackend()).addKeepMainRule(Main.class),
+        IncompatibleClassChangeError.class);
+  }
+
+  private void runTest(
+      TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder, Class<? extends Throwable> errorClass)
+      throws Exception {
+    testBuilder
+        .addProgramClasses(J.class, Main.class)
+        .addProgramClassFileData(getAWithImplementsI(), getIProgram())
+        .addDefaultRuntimeLibrary(parameters)
+        .addLibraryFiles(libraryClasses)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(options -> options.loadAllClassDefinitions = true)
+        .compile()
+        .addBootClasspathFiles(buildOnDexRuntime(parameters, libraryClasses))
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(errorClass);
+  }
+
+  private byte[] getAWithImplementsI() throws Exception {
+    return transformer(A.class).setImplements(I.class).transform();
+  }
+
+  private byte[] getIProgram() throws Exception {
+    return transformer(IProgram.class).setClassDescriptor(descriptor(I.class)).transform();
+  }
+
+  public interface I {
+    default void foo() {
+      System.out.println("I::foo");
+    }
+  }
+
+  public interface IProgram {
+    default void foo() {
+      System.out.println("I_Program::foo");
+    }
+  }
+
+  public interface J {
+    default void foo() {
+      System.out.println("J::foo");
+    }
+  }
+
+  public static class A /* implements I */ {}
+
+  public static class Main extends A implements J {
+
+    public static void main(String[] args) {
+      new Main().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleDominatingAfterJoinTest.java b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleDominatingAfterJoinTest.java
new file mode 100644
index 0000000..3e3b85b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleDominatingAfterJoinTest.java
@@ -0,0 +1,156 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.duplicatedefinitions;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import java.nio.file.Path;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+/**
+ * This is testing resolving Main.f for:
+ *
+ * <pre>
+ * I: I_L { f }
+ * J: J_L extends I { f }
+ * K: K_L extends J { }, K_P extends J { }
+ * class Main implements I,K
+ * </pre>
+ */
+public class MaximallySpecificSingleDominatingAfterJoinTest extends TestBase {
+
+  private static final String EXPECTED = "J::foo";
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private Path libraryClasses;
+
+  @Before
+  public void setup() throws Exception {
+    libraryClasses = temp.newFile("lib.jar").toPath();
+    ZipBuilder.builder(libraryClasses)
+        .addFilesRelative(
+            ToolHelper.getClassPathForTests(),
+            ToolHelper.getClassFileForTestClass(K.class),
+            ToolHelper.getClassFileForTestClass(J.class),
+            ToolHelper.getClassFileForTestClass(I.class))
+        .build();
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AndroidApp.Builder builder = AndroidApp.builder();
+    builder.addProgramFiles(
+        ToolHelper.getClassFileForTestClass(K.class),
+        ToolHelper.getClassFileForTestClass(Main.class));
+    builder.addLibraryFiles(parameters.getDefaultRuntimeLibrary(), libraryClasses);
+    AppView<AppInfoWithClassHierarchy> appView =
+        computeAppViewWithClassHierarchy(
+            builder.build(), null, options -> options.loadAllClassDefinitions = true);
+    AppInfoWithClassHierarchy appInfo = appView.appInfo();
+    DexMethod method = buildNullaryVoidMethod(Main.class, "foo", appInfo.dexItemFactory());
+    // TODO(b/214382176): Extend resolution to support multiple definition results.
+    assertThrows(
+        Unreachable.class,
+        () -> {
+          appInfo.unsafeResolveMethodDueToDexFormat(method);
+        });
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addRunClasspathFiles(libraryClasses)
+        .addProgramClasses(K.class, Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    runTest(
+        testForD8(parameters.getBackend()),
+        parameters.getDexRuntimeVersion().isDalvik()
+            ? VerifyError.class
+            // TODO(b/214382176): Extend resolution to support multiple definition results.
+            : AbstractMethodError.class);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    runTest(
+        testForR8(parameters.getBackend()).addKeepMainRule(Main.class), AbstractMethodError.class);
+  }
+
+  private void runTest(
+      TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder,
+      Class<? extends Throwable> errorIfNotSupportingDefaultMethods)
+      throws Exception {
+    testBuilder
+        .addProgramClasses(K.class, Main.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .addLibraryFiles(libraryClasses)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(options -> options.loadAllClassDefinitions = true)
+        .compile()
+        .addBootClasspathFiles(buildOnDexRuntime(parameters, libraryClasses))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLinesIf(
+            parameters.canUseDefaultAndStaticInterfaceMethods(), EXPECTED)
+        // TODO(b/214382176): Extend resolution to support multiple definition results.
+        .assertFailureWithErrorThatThrowsIf(
+            !parameters.canUseDefaultAndStaticInterfaceMethods(),
+            errorIfNotSupportingDefaultMethods);
+  }
+
+  public interface I {
+    default void foo() {
+      System.out.println("I::foo");
+    }
+  }
+
+  public interface J extends I {
+    @Override
+    default void foo() {
+      System.out.println("J::foo");
+    }
+  }
+
+  /* Present on both library and program */
+  public interface K extends J {}
+
+  public static class Main implements I, K {
+
+    public static void main(String[] args) {
+      new Main().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleDominatingSubTest.java b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleDominatingSubTest.java
new file mode 100644
index 0000000..630c284
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleDominatingSubTest.java
@@ -0,0 +1,165 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.duplicatedefinitions;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+/**
+ * This is testing resolving Main.f for:
+ *
+ * <pre>
+ * I: I_L { f }
+ * J: J_L extends I { f }, J_P extends I { f }
+ * K: K_P extends J { f }
+ * class Main implements I,K
+ * </pre>
+ */
+public class MaximallySpecificSingleDominatingSubTest extends TestBase {
+
+  private static final String EXPECTED = "K::foo";
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private Path libraryClasses;
+
+  @Before
+  public void setup() throws Exception {
+    libraryClasses = temp.newFile("lib.jar").toPath();
+    ZipBuilder.builder(libraryClasses)
+        .addFilesRelative(
+            ToolHelper.getClassPathForTests(),
+            ToolHelper.getClassFileForTestClass(J.class),
+            ToolHelper.getClassFileForTestClass(I.class))
+        .build();
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AndroidApp.Builder builder = AndroidApp.builder();
+    builder
+        .addProgramFiles(
+            ToolHelper.getClassFileForTestClass(K.class),
+            ToolHelper.getClassFileForTestClass(Main.class))
+        .addClassProgramData(ImmutableList.of(getJOnProgram()));
+    builder.addLibraryFiles(parameters.getDefaultRuntimeLibrary(), libraryClasses);
+    AppView<AppInfoWithClassHierarchy> appView =
+        computeAppViewWithClassHierarchy(
+            builder.build(), null, options -> options.loadAllClassDefinitions = true);
+    AppInfoWithClassHierarchy appInfo = appView.appInfo();
+    DexMethod method = buildNullaryVoidMethod(Main.class, "foo", appInfo.dexItemFactory());
+    // TODO(b/214382176): Extend resolution to support multiple definition results
+    assertThrows(
+        Unreachable.class,
+        () -> {
+          appInfo.unsafeResolveMethodDueToDexFormat(method);
+        });
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addRunClasspathFiles(libraryClasses)
+        .addProgramClasses(K.class, Main.class)
+        .addProgramClassFileData(getJOnProgram())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    runTest(testForD8(parameters.getBackend()));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    runTest(testForR8(parameters.getBackend()).addKeepMainRule(Main.class));
+  }
+
+  private void runTest(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    testBuilder
+        .addProgramClasses(K.class, Main.class)
+        .addProgramClassFileData(getJOnProgram())
+        .addDefaultRuntimeLibrary(parameters)
+        .addLibraryFiles(libraryClasses)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(options -> options.loadAllClassDefinitions = true)
+        .compile()
+        .addBootClasspathFiles(buildOnDexRuntime(parameters, libraryClasses))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  private byte[] getJOnProgram() throws Exception {
+    return transformer(JProgram.class).setClassDescriptor(descriptor(J.class)).transform();
+  }
+
+  public interface I {
+    default void foo() {
+      System.out.println("I::foo");
+    }
+  }
+
+  /* Present on both library and program */
+  public interface JProgram extends I {
+    @Override
+    default void foo() {
+      System.out.println("J_Program::foo");
+      ;
+    }
+  }
+
+  public interface J extends I {
+    @Override
+    default void foo() {
+      System.out.println("J_Library::foo");
+      ;
+    }
+  }
+
+  public interface K extends J {
+
+    @Override
+    default void foo() {
+      System.out.println("K::foo");
+    }
+  }
+
+  public static class Main implements I, K {
+
+    public static void main(String[] args) {
+      new Main().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleLibraryPartialTest.java b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleLibraryPartialTest.java
new file mode 100644
index 0000000..c840cc2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleLibraryPartialTest.java
@@ -0,0 +1,146 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.duplicatedefinitions;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import java.nio.file.Path;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+/**
+ * This is testing resolving Main.f for:
+ *
+ * <pre>
+ * I: I_L { f }, I_P { }
+ * class Main implements I
+ * </pre>
+ */
+public class MaximallySpecificSingleLibraryPartialTest extends TestBase {
+
+  private static final String EXPECTED = "I::foo";
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private Path libraryClasses;
+
+  @Before
+  public void setup() throws Exception {
+    libraryClasses = temp.newFile("lib.jar").toPath();
+    ZipBuilder.builder(libraryClasses)
+        .addFilesRelative(
+            ToolHelper.getClassPathForTests(), ToolHelper.getClassFileForTestClass(I.class))
+        .build();
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AndroidApp.Builder builder = AndroidApp.builder();
+    builder
+        .addProgramFiles(ToolHelper.getClassFileForTestClass(Main.class))
+        .addClassProgramData(getIProgram());
+    builder.addLibraryFiles(parameters.getDefaultRuntimeLibrary(), libraryClasses);
+    AppView<AppInfoWithClassHierarchy> appView =
+        computeAppViewWithClassHierarchy(
+            builder.build(), null, options -> options.loadAllClassDefinitions = true);
+    AppInfoWithClassHierarchy appInfo = appView.appInfo();
+    DexMethod method = buildNullaryVoidMethod(Main.class, "foo", appInfo.dexItemFactory());
+    // TODO(b/214382176): Extend resolution to support multiple definition results.
+    assertThrows(
+        Unreachable.class,
+        () -> {
+          appInfo.unsafeResolveMethodDueToDexFormat(method);
+        });
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addRunClasspathFiles(libraryClasses)
+        .addProgramClasses(Main.class)
+        .addProgramClassFileData(getIProgram())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    // TODO(b/214382176): Extend resolution to support multiple definition results.
+    runTest(testForD8(parameters.getBackend()))
+        .assertFailureWithErrorThatThrowsIf(
+            !parameters.canUseDefaultAndStaticInterfaceMethods(),
+            parameters.getDexRuntimeVersion().isDalvik()
+                ? VerifyError.class
+                : AbstractMethodError.class)
+        .assertSuccessWithOutputLinesIf(
+            parameters.canUseDefaultAndStaticInterfaceMethods(), EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    // TODO(b/214382176): Extend resolution to support multiple definition results.
+    runTest(testForR8(parameters.getBackend()).addKeepMainRule(Main.class))
+        .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+  }
+
+  private TestRunResult<?> runTest(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder)
+      throws Exception {
+    return testBuilder
+        .addProgramClasses(Main.class)
+        .addProgramClassFileData(getIProgram())
+        .addDefaultRuntimeLibrary(parameters)
+        .addLibraryFiles(libraryClasses)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(options -> options.loadAllClassDefinitions = true)
+        .compile()
+        .addBootClasspathFiles(buildOnDexRuntime(parameters, libraryClasses))
+        .run(parameters.getRuntime(), Main.class);
+  }
+
+  private byte[] getIProgram() throws Exception {
+    return transformer(IProgram.class).setClassDescriptor(descriptor(I.class)).transform();
+  }
+
+  public interface I {
+    default void foo() {
+      System.out.println("I::foo");
+    }
+  }
+
+  public interface IProgram {}
+
+  public static class Main implements I {
+
+    public static void main(String[] args) {
+      new Main().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleProgramPartialTest.java b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleProgramPartialTest.java
new file mode 100644
index 0000000..de9876c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleProgramPartialTest.java
@@ -0,0 +1,146 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.duplicatedefinitions;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import java.nio.file.Path;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+/**
+ * This is testing resolving Main.f for:
+ *
+ * <pre>
+ * I: I_L { }, I_P { f }
+ * class Main implements I
+ * </pre>
+ */
+public class MaximallySpecificSingleProgramPartialTest extends TestBase {
+
+  private static final String UNEXPECTED = "I::foo";
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private Path libraryClasses;
+
+  @Before
+  public void setup() throws Exception {
+    libraryClasses = temp.newFile("lib.jar").toPath();
+    ZipBuilder.builder(libraryClasses)
+        .addFilesRelative(
+            ToolHelper.getClassPathForTests(), ToolHelper.getClassFileForTestClass(I.class))
+        .build();
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AndroidApp.Builder builder = AndroidApp.builder();
+    builder.addClassProgramData(getMainImplementingI()).addClassProgramData(getIProgram());
+    builder.addLibraryFiles(parameters.getDefaultRuntimeLibrary(), libraryClasses);
+    AppView<AppInfoWithClassHierarchy> appView =
+        computeAppViewWithClassHierarchy(
+            builder.build(), null, options -> options.loadAllClassDefinitions = true);
+    AppInfoWithClassHierarchy appInfo = appView.appInfo();
+    DexMethod method = buildNullaryVoidMethod(Main.class, "foo", appInfo.dexItemFactory());
+    // TODO(b/214382176): Extend resolution to support multiple definition results.
+    assertThrows(
+        Unreachable.class,
+        () -> {
+          appInfo.unsafeResolveMethodDueToDexFormat(method);
+        });
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addRunClasspathFiles(libraryClasses)
+        .addProgramClassFileData(getMainImplementingI(), getIProgram())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    // TODO(b/214382176): Extend resolution to support multiple definition results.
+    runTest(testForD8(parameters.getBackend()))
+        .assertFailureWithErrorThatThrowsIf(
+            parameters.canUseDefaultAndStaticInterfaceMethods(), NoSuchMethodError.class)
+        .assertSuccessWithOutputLinesIf(
+            !parameters.canUseDefaultAndStaticInterfaceMethods(), UNEXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    // TODO(b/214382176): Extend resolution to support multiple definition results.
+    runTest(testForR8(parameters.getBackend()).addKeepMainRule(Main.class))
+        .assertFailureWithErrorThatThrowsIf(
+            parameters.canUseDefaultAndStaticInterfaceMethods(), NoSuchMethodError.class)
+        .assertSuccessWithOutputLinesIf(
+            !parameters.canUseDefaultAndStaticInterfaceMethods(), UNEXPECTED);
+  }
+
+  private TestRunResult<?> runTest(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder)
+      throws Exception {
+    return testBuilder
+        .addProgramClassFileData(getMainImplementingI(), getIProgram())
+        .addDefaultRuntimeLibrary(parameters)
+        .addLibraryFiles(libraryClasses)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(options -> options.loadAllClassDefinitions = true)
+        .compile()
+        .addBootClasspathFiles(buildOnDexRuntime(parameters, libraryClasses))
+        .run(parameters.getRuntime(), Main.class);
+  }
+
+  private byte[] getIProgram() throws Exception {
+    return transformer(IProgram.class).setClassDescriptor(descriptor(I.class)).transform();
+  }
+
+  private byte[] getMainImplementingI() throws Exception {
+    return transformer(Main.class).setImplements(I.class).transform();
+  }
+
+  public interface I {}
+
+  public interface IProgram {
+    default void foo() {
+      System.out.println("I::foo");
+    }
+  }
+
+  public static class Main implements /* I */ IProgram {
+
+    public static void main(String[] args) {
+      new Main().foo();
+    }
+  }
+}
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 81a0f45..51f29aa 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
@@ -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());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 d708d1e..05bb852 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
@@ -51,7 +51,7 @@
             .buildWithLiveness()
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 8f94ef3..7314ee5 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
@@ -51,7 +51,7 @@
             .buildWithLiveness()
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 6b31505..9f1df3e 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
@@ -53,7 +53,7 @@
                 Main.class)
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 0b78a07..b5cac2e 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
@@ -53,7 +53,7 @@
                 Main.class)
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 7f09e71..31e9e9e 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
@@ -53,7 +53,7 @@
             .buildWithLiveness()
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 0f37198..53a17c4 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
@@ -50,7 +50,7 @@
             .buildWithLiveness()
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 8d35771..018cddb 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
@@ -50,7 +50,7 @@
             .buildWithLiveness()
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 290dbec..d5aefd7 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
@@ -58,7 +58,7 @@
               .buildWithLiveness()
               .appInfo();
       DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-      MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+      MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(method);
       if (minApi.isLessThan(apiLevelWithDefaultInterfaceMethodsSupport())) {
         // When desugaring a forwarding method throwing ICCE is inserted.
         // Check that the resolved method throws such an exception.
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 b3c88c7..e838d5d 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
@@ -64,7 +64,7 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOnInterface(method.holder, method);
+        appInfo.resolveMethodOnInterfaceLegacy(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 1b3d3d9..dfa7451 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
@@ -63,7 +63,7 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOnInterface(method.holder, method);
+        appInfo.resolveMethodOnInterfaceLegacy(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 8908dce..ed99f3e 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
@@ -63,7 +63,7 @@
             .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterfaceHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterfaceHolderLegacy(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
@@ -112,7 +112,7 @@
             .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterfaceHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterfaceHolderLegacy(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 8122941..9002d8d 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
@@ -63,7 +63,7 @@
             .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterfaceHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterfaceHolderLegacy(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/InvokeInterfaceWithStaticTargetTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
index ca5e24e..29ae548 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
@@ -56,7 +56,7 @@
         AssertionError.class,
         () ->
             appInfo
-                .resolveMethodOnInterfaceHolder(method)
+                .resolveMethodOnInterfaceHolderLegacy(method)
                 .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 0819b97..6d93202 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
@@ -63,7 +63,7 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(J.class, "bar", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOnInterface(method.holder, method);
+        appInfo.resolveMethodOnInterfaceLegacy(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 5912e69..8b1b588 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
@@ -61,7 +61,7 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOnInterface(method.holder, method);
+        appInfo.resolveMethodOnInterfaceLegacy(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 3503bdf..c48764d 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
@@ -61,7 +61,7 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOnInterface(method.holder, method);
+        appInfo.resolveMethodOnInterfaceLegacy(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 4d85bfb..50e000b 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
@@ -63,7 +63,7 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOnInterface(method.holder, method);
+        appInfo.resolveMethodOnInterfaceLegacy(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 682d447..a3b1d8f 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
@@ -60,7 +60,7 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOnInterface(method.holder, method);
+        appInfo.resolveMethodOnInterfaceLegacy(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 bbf73d7..1d8a03a 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
@@ -60,7 +60,7 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOnInterface(method.holder, method);
+        appInfo.resolveMethodOnInterfaceLegacy(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 bff70ca..13a108d 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
@@ -74,7 +74,7 @@
             .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(Abstract.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 255fbed..45eab24 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
@@ -59,7 +59,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 f33ee0b..a875b43 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
@@ -63,7 +63,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
@@ -86,7 +86,8 @@
             .addProgramClassFileData(getDWithPackagePrivateFoo())
             .run(parameters.getRuntime(), Main.class);
     if (parameters.isCfRuntime()
-        || parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
+        || parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)
+        || parameters.getRuntime().asDex().getVm().isNewerThanOrEqual(DexVm.ART_13_0_0_TARGET)) {
       runResult.assertSuccessWithOutputLines(EXPECTED);
     } else {
       runResult.assertSuccessWithOutputLines(EXPECTED_ART);
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 be8acd3..3eb13d5 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
@@ -73,7 +73,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 8846bf2..6ccf173 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
@@ -67,7 +67,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 da660f2..7a588f3 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
@@ -60,7 +60,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 58c64b1..42733a1 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
@@ -131,7 +131,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());
-    MethodResolutionResult resolution = appInfo.resolveMethodOnClassHolder(fooA);
+    MethodResolutionResult resolution = appInfo.resolveMethodOnClassHolderLegacy(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 e856ed2..2906ddd 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
@@ -59,7 +59,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 665ef74..42c441b 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
@@ -62,7 +62,7 @@
             .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 df0722f..67afdad 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
@@ -62,7 +62,7 @@
             .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 d4541ac..c6c6aac 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
@@ -63,7 +63,7 @@
             .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 6aac0fb..ada09f0 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
@@ -59,7 +59,8 @@
                       Main.class);
               AppInfoWithLiveness appInfo = appView.appInfo();
               DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-              MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+              MethodResolutionResult resolutionResult =
+                  appInfo.resolveMethodOnClassHolderLegacy(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 a1f6b17..184b03f 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
@@ -62,7 +62,7 @@
             .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 19f951a..a96b487 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
@@ -87,7 +87,7 @@
             });
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(initial, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(method);
     DexProgramClass context =
         DexProgramClass.asProgramClassOrNull(
             appView
@@ -238,7 +238,7 @@
                 .build());
     AppInfoWithClassHierarchy appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 33bfc01..69ef032 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
@@ -62,7 +62,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(Top.class, "clear", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 28d1cc2..b72f8b6 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
@@ -68,7 +68,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(ViewModel.class, "clear", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 24aa358..2d10b18 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
@@ -61,7 +61,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 3ffdf09..87d27ee 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
@@ -54,7 +54,7 @@
     AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(builder.build(), Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 337ba95..0a3d14a 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
@@ -50,7 +50,7 @@
             PackagePrivateChainTest.Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolderLegacy(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 8769eca..d1eb536 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
@@ -63,7 +63,7 @@
             .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterfaceHolder(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterfaceHolderLegacy(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/RetraceLambdaTest.java b/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
index d68447d..e7b884c 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
@@ -34,12 +34,6 @@
 
   private static final String JAVAC_LAMBDA_METHOD = "lambda$main$0";
 
-  // TODO(b/172014416): These should not be needed once fixed.
-  private static final String LAMBDA_BRIDGE_METHOD = "$r8$lambda$dX5OYTAgq4ijGUv_zaGoVsFINMs";
-  private static final String INTERNAL_LAMBDA_CLASS =
-      Main.class.getTypeName()
-          + "$$InternalSyntheticLambda$0$11a5d582ed94e937718cf3ed497d4d164b60dfa85d606466457007fade57dce8$0";
-
   @Parameters(name = "{0}")
   public static TestParametersCollection parameters() {
     return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java
index eb1d9f3..f5943b5 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java
@@ -40,11 +40,9 @@
   public List<String> retracedStackTrace() {
     return Arrays.asList(
         "  at com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
-            // TODO(b/226885646): Should be KotlinJavaSourceFileTestLibrary.kt
-            + ".throwsException(KotlinJavaSourceFileTestLibrary.java:22)",
+            + ".throwsException(KotlinJavaSourceFileTestLibrary.kt:22)",
         "  at com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
-            // TODO(b/226885646): Should be KotlinJavaSourceFileTestLibrary.kt
-            + ".callsThrowsException(KotlinJavaSourceFileTestLibrary.java:19)",
+            + ".callsThrowsException(KotlinJavaSourceFileTestLibrary.kt:19)",
         "  at com.google.appreduce.remapper.KotlinJavaSourceFileTestObject"
             + ".main(KotlinJavaSourceFileTestObject.java:32)");
   }
@@ -53,11 +51,9 @@
   public List<String> retraceVerboseStackTrace() {
     return Arrays.asList(
         "  at com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
-            // TODO(b/226885646): Should be KotlinJavaSourceFileTestLibrary.kt
-            + ".void throwsException()(KotlinJavaSourceFileTestLibrary.java:22)",
+            + ".void throwsException()(KotlinJavaSourceFileTestLibrary.kt:22)",
         "  at com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
-            // TODO(b/226885646): Should be KotlinJavaSourceFileTestLibrary.kt
-            + ".void callsThrowsException()(KotlinJavaSourceFileTestLibrary.java:19)",
+            + ".void callsThrowsException()(KotlinJavaSourceFileTestLibrary.kt:19)",
         "  at com.google.appreduce.remapper.KotlinJavaSourceFileTestObject"
             + ".void main(java.lang.String[])(KotlinJavaSourceFileTestObject.java:32)");
   }
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationAssertionHandlerKotlinTestBase.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationAssertionHandlerKotlinTestBase.java
index 84bd1ef..b70b311 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationAssertionHandlerKotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationAssertionHandlerKotlinTestBase.java
@@ -4,12 +4,13 @@
 
 package com.android.tools.r8.rewrite.assertions;
 
-import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assume.assumeTrue;
 
 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.TestBuilder;
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.MethodReference;
@@ -70,10 +71,21 @@
 
   protected abstract List<Path> getKotlinFiles() throws IOException;
 
+  protected boolean transformKotlinClasses() {
+    return false;
+  }
+
+  protected byte[] transformedKotlinClasses(Path kotlinClasses) throws IOException {
+    assert false;
+    return null;
+  }
+
   protected abstract String getTestClassName();
 
   protected void configureR8(R8FullTestBuilder builder) {}
 
+  protected void configureResultR8(R8TestCompileResult builder) {}
+
   private Path kotlinStdlibLibraryForRuntime() throws Exception {
     Path kotlinStdlibCf = kotlinc.getKotlinStdlibJar();
     if (parameters.getRuntime().isCf()) {
@@ -107,6 +119,16 @@
     }
   }
 
+  private void addKotlinClasses(TestBuilder<?, ?> builder) {
+    builder.applyIf(
+        transformKotlinClasses(),
+        b ->
+            b.addProgramClassFileData(
+                transformedKotlinClasses(
+                    compiledForAssertions.getForConfiguration(kotlinc, targetVersion))),
+        b -> b.addProgramFiles(compiledForAssertions.getForConfiguration(kotlinc, targetVersion)));
+  }
+
   @Test
   public void testD8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
@@ -114,7 +136,7 @@
         .apply(this::configureKotlinStdlib)
         .setMinApi(parameters.getApiLevel())
         .addProgramClasses(AssertionHandlers.class)
-        .addProgramFiles(compiledForAssertions.getForConfiguration(kotlinc, targetVersion))
+        .apply(this::addKotlinClasses)
         .addAssertionsConfiguration(
             builder ->
                 builder
@@ -131,7 +153,7 @@
         .apply(this::configureKotlinStdlib)
         .setMinApi(parameters.getApiLevel())
         .addProgramClasses(AssertionHandlers.class)
-        .addProgramFiles(compiledForAssertions.getForConfiguration(kotlinc, targetVersion))
+        .apply(this::addKotlinClasses)
         .addAssertionsConfiguration(
             builder ->
                 builder
@@ -140,13 +162,8 @@
                     .build())
         .addKeepMainRule(getTestClassName())
         .apply(this::configureR8)
-        .allowDiagnosticWarningMessages(!kotlinStdlibAsLibrary)
         .compile()
-        .applyIf(
-            !kotlinStdlibAsLibrary,
-            result ->
-                result.assertAllWarningMessagesMatch(
-                    equalTo("Resource 'META-INF/MANIFEST.MF' already exists.")))
+        .apply(this::configureResultR8)
         .run(parameters.getRuntime(), getTestClassName())
         .assertSuccessWithOutput(getExpectedOutput());
   }
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationAssertionHandlerWithConditionsTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationAssertionHandlerWithConditionsTest.java
new file mode 100644
index 0000000..af2c8db
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationAssertionHandlerWithConditionsTest.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2022, 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.rewrite.assertions;
+
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.rewrite.assertions.assertionhandler.AssertionHandlers;
+import com.android.tools.r8.rewrite.assertions.assertionhandler.AssertionsWithConditions;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class AssertionConfigurationAssertionHandlerWithConditionsTest
+    extends AssertionConfigurationAssertionHandlerTestBase {
+
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines(
+          "assertionHandler: assertionWithSimpleCondition",
+          "assertionHandler: assertionWithCondition");
+
+  @Override
+  String getExpectedOutput() {
+    return EXPECTED_OUTPUT;
+  }
+
+  @Override
+  MethodReference getAssertionHandler() throws Exception {
+    return Reference.methodFromMethod(
+        AssertionHandlers.class.getMethod("assertionHandler", Throwable.class));
+  }
+
+  @Override
+  List<Class<?>> getTestClasses() {
+    return ImmutableList.of(AssertionsWithConditions.class);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/assertionhandler/AssertionsWithConditions.java b/src/test/java/com/android/tools/r8/rewrite/assertions/assertionhandler/AssertionsWithConditions.java
new file mode 100644
index 0000000..17d4d54
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/assertionhandler/AssertionsWithConditions.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2022, 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.rewrite.assertions.assertionhandler;
+
+import com.android.tools.r8.Keep;
+
+public class AssertionsWithConditions {
+
+  @Keep
+  public static void assertionWithSimpleCondition(int x) {
+    assert x < 0;
+  }
+
+  private static boolean isZero(int x) {
+    return x == 0;
+  }
+
+  private static boolean isNegative(int x) {
+    return x < 0;
+  }
+
+  @Keep
+  public static void assertionWithCondition(int x, int y, int z) {
+    assert x == 0 && isZero(y) && isNegative(z);
+  }
+
+  public static void main(String[] args) {
+    assertionWithSimpleCondition(args.length);
+    assertionWithCondition(args.length, args.length, args.length);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/kotlinassertionhandlerdoublecheck/AssertionConfigurationAssertionHandlerKotlinDoubleCheckTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlinassertionhandlerdoublecheck/AssertionConfigurationAssertionHandlerKotlinDoubleCheckTest.java
new file mode 100644
index 0000000..d0177bb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlinassertionhandlerdoublecheck/AssertionConfigurationAssertionHandlerKotlinDoubleCheckTest.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2022, 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.rewrite.assertions.kotlinassertionhandlerdoublecheck;
+
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
+import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.rewrite.assertions.AssertionConfigurationAssertionHandlerKotlinTestBase;
+import com.android.tools.r8.rewrite.assertions.assertionhandler.AssertionHandlers;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class AssertionConfigurationAssertionHandlerKotlinDoubleCheckTest
+    extends AssertionConfigurationAssertionHandlerKotlinTestBase {
+
+  public AssertionConfigurationAssertionHandlerKotlinDoubleCheckTest(
+      TestParameters parameters,
+      KotlinTestParameters kotlinParameters,
+      boolean kotlinStdlibAsClasspath,
+      boolean useJvmAssertions)
+      throws IOException {
+    super(parameters, kotlinParameters, kotlinStdlibAsClasspath, useJvmAssertions);
+  }
+
+  @Override
+  protected String getExpectedOutput() {
+    return StringUtils.lines("assertionHandler: doubleCheckAssertion");
+  }
+
+  @Override
+  protected MethodReference getAssertionHandler() throws Exception {
+    return Reference.methodFromMethod(
+        AssertionHandlers.class.getMethod("assertionHandler", Throwable.class));
+  }
+
+  @Override
+  protected List<Path> getKotlinFiles() throws IOException {
+    return getKotlinFilesInTestPackage(getClass().getPackage());
+  }
+
+  @Override
+  protected boolean transformKotlinClasses() {
+    return true;
+  }
+
+  @Override
+  protected byte[] transformedKotlinClasses(Path kotlinClasses) throws IOException {
+    Path compiledKotlinClasses = temp.newFolder().toPath();
+    ZipUtils.unzip(
+        compiledForAssertions.getForConfiguration(kotlinc, targetVersion), compiledKotlinClasses);
+    String testClassPath =
+        DescriptorUtils.getPackageBinaryNameFromJavaType(getTestClassName())
+            + FileUtils.CLASS_EXTENSION;
+    String assertionsMockBinaryName =
+        DescriptorUtils.getPackageBinaryNameFromJavaType(
+            getClass().getPackage().getName() + ".AssertionsMock");
+    // Rewrite the static get on AssertionsMock.Enabled to static get on kotlin._Assertions.ENABLED
+    return transformer(
+            compiledKotlinClasses.resolve(testClassPath),
+            Reference.classFromTypeName(getTestClassName()))
+        .transformFieldInsnInMethod(
+            "doubleCheckAssertionsEnabled",
+            (opcode, owner, name, descriptor, continuation) -> {
+              continuation.visitFieldInsn(
+                  opcode,
+                  owner.equals(assertionsMockBinaryName) ? "kotlin/_Assertions" : owner,
+                  name,
+                  descriptor);
+            })
+        .transform();
+  }
+
+  @Override
+  protected String getTestClassName() {
+    return getClass().getPackage().getName() + ".AssertionDoubleCheckKt";
+  }
+
+  @Override
+  protected void configureR8(R8FullTestBuilder builder) {
+    boolean referencesNotNull =
+        !kotlinParameters.is(KotlinCompilerVersion.KOTLINC_1_3_72) && !kotlinStdlibAsLibrary;
+    builder.applyIf(referencesNotNull, b -> b.addDontWarn("org.jetbrains.annotations.NotNull"));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/kotlinassertionhandlerdoublecheck/AssertionDoubleCheck.kt b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlinassertionhandlerdoublecheck/AssertionDoubleCheck.kt
new file mode 100644
index 0000000..3351bcd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlinassertionhandlerdoublecheck/AssertionDoubleCheck.kt
@@ -0,0 +1,28 @@
+// Copyright (c) 2022, 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.rewrite.assertions.kotlinassertionhandlerdoublecheck
+
+fun doubleCheckAssertionsEnabled() {
+  // AssertionsMock.ENABLED will be rewritten to kotlin._Assertions.ENABLED to
+  // test
+  //   * two checks on kotlin._Assertions.ENABLED and
+  //   * check on both kotlin._Assertions.ENABLED and $assertionsDisabled
+  // before entering the assertion code.
+  //
+  // This is testing code like this found in kotlin-stdlib:
+  //
+  //   if (_Assertions.ENABLED)
+  //     assert(...) { ... }
+  //   }
+  //
+  // E.g. in kotlin/io/files/FileTreeWalk.kt.
+  if (AssertionsMock.ENABLED) {
+    assert(false) { "doubleCheckAssertion" }
+  }
+}
+
+fun main() {
+  doubleCheckAssertionsEnabled();
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/kotlinassertionhandlerdoublecheck/AssertionsMock.kt b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlinassertionhandlerdoublecheck/AssertionsMock.kt
new file mode 100644
index 0000000..bd05154
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlinassertionhandlerdoublecheck/AssertionsMock.kt
@@ -0,0 +1,10 @@
+// Copyright (c) 2022, 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.rewrite.assertions.kotlinassertionhandlerdoublecheck
+
+object AssertionsMock {
+    @JvmField
+    val ENABLED: Boolean = false
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/kotlinassertionhandlersimple/AssertionConfigurationAssertionHandlerKotlinSimpleTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlinassertionhandlersimple/AssertionConfigurationAssertionHandlerKotlinSimpleTest.java
index e460253..aad753e 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/kotlinassertionhandlersimple/AssertionConfigurationAssertionHandlerKotlinSimpleTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlinassertionhandlersimple/AssertionConfigurationAssertionHandlerKotlinSimpleTest.java
@@ -4,9 +4,12 @@
 
 package com.android.tools.r8.rewrite.assertions.kotlinassertionhandlersimple;
 
+import static org.hamcrest.CoreMatchers.equalTo;
+
 import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 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.references.MethodReference;
 import com.android.tools.r8.references.Reference;
@@ -62,6 +65,17 @@
         !kotlinParameters.is(KotlinCompilerVersion.KOTLINC_1_3_72)
             && !kotlinStdlibAsLibrary
             && !useJvmAssertions;
-    builder.applyIf(referencesNotNull, b -> b.addDontWarn("org.jetbrains.annotations.NotNull"));
+    builder
+        .applyIf(referencesNotNull, b -> b.addDontWarn("org.jetbrains.annotations.NotNull"))
+        .allowDiagnosticWarningMessages(!kotlinStdlibAsLibrary);
+  }
+
+  @Override
+  protected void configureResultR8(R8TestCompileResult builder) {
+    builder.applyIf(
+        !kotlinStdlibAsLibrary,
+        result ->
+            result.assertAllWarningMessagesMatch(
+                equalTo("Resource 'META-INF/MANIFEST.MF' already exists.")));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/kotlinassertionhandlerwithexceptions/AssertionConfigurationAssertionHandlerKotlinRethrowingTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlinassertionhandlerwithexceptions/AssertionConfigurationAssertionHandlerKotlinRethrowingTest.java
index 93b3b57..c2848c3 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/kotlinassertionhandlerwithexceptions/AssertionConfigurationAssertionHandlerKotlinRethrowingTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlinassertionhandlerwithexceptions/AssertionConfigurationAssertionHandlerKotlinRethrowingTest.java
@@ -4,8 +4,11 @@
 
 package com.android.tools.r8.rewrite.assertions.kotlinassertionhandlerwithexceptions;
 
+import static org.hamcrest.CoreMatchers.equalTo;
+
 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.references.MethodReference;
 import com.android.tools.r8.references.Reference;
@@ -66,6 +69,16 @@
     builder
         .addKeepRules("-keep class **.*Kt { assertionsWith*(); simpleAssertion(); }")
         .addDontWarn("org.jetbrains.annotations.NotNull")
-        .applyIf(!kotlinStdlibAsLibrary, b -> b.addDontWarn("org.jetbrains.annotations.Nullable"));
+        .applyIf(!kotlinStdlibAsLibrary, b -> b.addDontWarn("org.jetbrains.annotations.Nullable"))
+        .allowDiagnosticWarningMessages(!kotlinStdlibAsLibrary);
+  }
+
+  @Override
+  protected void configureResultR8(R8TestCompileResult builder) {
+    builder.applyIf(
+        !kotlinStdlibAsLibrary,
+        result ->
+            result.assertAllWarningMessagesMatch(
+                equalTo("Resource 'META-INF/MANIFEST.MF' already exists.")));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
index 604caae..bb63e6f 100644
--- a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
@@ -86,7 +86,7 @@
                     "Hello!", "Unexpected outcome of checkcast", "Goodbye!", "");
 
               case V7_0_0:
-              case V13_MASTER:
+              case V13_0_0:
                 return StringUtils.joinLines(
                     "Hello!",
                     "Unexpected outcome of checkcast",
diff --git a/src/test/java/com/android/tools/r8/shaking/b169045091/B169045091.java b/src/test/java/com/android/tools/r8/shaking/b169045091/B169045091.java
index 3a32244..d3b8c48 100644
--- a/src/test/java/com/android/tools/r8/shaking/b169045091/B169045091.java
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/B169045091.java
@@ -100,7 +100,7 @@
     DexMethod helloReference = buildNullaryVoidMethod(HelloGreeter.class, "hello", dexItemFactory);
     assertTrue(
         appInfo
-            .resolveMethodOnClassHolder(helloReference)
+            .resolveMethodOnClassHolderLegacy(helloReference)
             .isAccessibleFrom(context, appView)
             .isTrue());
 
@@ -108,7 +108,7 @@
     DexMethod worldReference = buildNullaryVoidMethod(WorldGreeter.class, "world", dexItemFactory);
     assertTrue(
         appInfo
-            .resolveMethodOnClassHolder(worldReference)
+            .resolveMethodOnClassHolderLegacy(worldReference)
             .isAccessibleFrom(context, appView)
             .isFalse());
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/MultipleRulesRegression228791247Test.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/MultipleRulesRegression228791247Test.java
new file mode 100644
index 0000000..c61fa16
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/MultipleRulesRegression228791247Test.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2022, 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.methods.interfaces;
+
+import static com.android.tools.r8.references.Reference.classFromClass;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MultipleRulesRegression228791247Test extends TestBase {
+
+  private static final String EXPECTED = StringUtils.lines("Hello!");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MultipleRulesRegression228791247Test(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(I.class, J.class, A.class, TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    // Regression adds two rules causes the forwarding method to be generated twice.
+    String rule1 = "-keep class " + classFromClass(J.class).getTypeName() + "{ void *oo(); }";
+    String rule2 = "-keep class " + classFromClass(J.class).getTypeName() + "{ void fo*(); }";
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class, J.class, A.class, TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules(rule1, rule2)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  public interface I {
+    default void foo() {
+      if (System.nanoTime() > 0) {
+        System.out.println("Hello!");
+      }
+    }
+  }
+
+  public interface J extends I {
+    // No foo, but it will be kept at J::foo.
+  }
+
+  public static class A implements J {}
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      J j = System.nanoTime() > 0 ? new A() : null;
+      j.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java b/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
deleted file mode 100644
index f41be5c..0000000
--- a/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.utils;
-
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertFalse;
-
-import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
-import com.google.common.collect.ImmutableList;
-import java.util.List;
-import org.junit.Test;
-
-public class FeatureClassMappingTest {
-
-  @Test
-  public void testSimpleParse() throws Exception {
-
-    List<String> lines =
-        ImmutableList.of(
-            "# Comment, don't care about contents: even more ::::",
-            "com.google.base:base",
-            "", // Empty lines allowed
-            "com.google.feature1:feature1",
-            "com.google.feature1:feature1", // Multiple definitions of the same predicate allowed.
-            "com.google$:feature1",
-            "_com.google:feature21",
-            "com.google.*:feature32");
-    FeatureClassMapping mapping = new FeatureClassMapping(lines);
-  }
-
-  private void ensureThrowsMappingException(List<String> lines) {
-    try {
-      new FeatureClassMapping(lines);
-      assertFalse(true);
-    } catch (FeatureMappingException e) {
-      // Expected
-    }
-  }
-
-  private void ensureThrowsMappingException(String string) {
-    ensureThrowsMappingException(ImmutableList.of(string));
-  }
-
-  @Test
-  public void testLookup() throws Exception {
-    List<String> lines =
-        ImmutableList.of(
-            "com.google.Base:base",
-            "",
-            "com.google.Feature1:feature1",
-            "com.google.Feature1:feature1", // Multiple definitions of the same predicate allowed.
-            "com.google.different.*:feature1",
-            "_com.Google:feature21",
-            "com.google.bas42.*:feature42");
-    FeatureClassMapping mapping = new FeatureClassMapping(lines);
-    assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1");
-    assertEquals(mapping.featureForClass("com.google.different.Feature1"), "feature1");
-    assertEquals(mapping.featureForClass("com.google.different.Foobar"), "feature1");
-    assertEquals(mapping.featureForClass("com.google.Base"), "base");
-    assertEquals(mapping.featureForClass("com.google.bas42.foo.bar.bar.Foo"), "feature42");
-    assertEquals(mapping.featureForClass("com.google.bas42.f$o$o$.bar43.bar.Foo"), "feature42");
-    assertEquals(mapping.featureForClass("_com.Google"), "feature21");
-  }
-
-  @Test
-  public void testCatchAllWildcards() throws Exception {
-    testBaseWildcard(true);
-    testBaseWildcard(false);
-    testNonBaseCatchAll();
-  }
-
-  private void testNonBaseCatchAll() throws FeatureMappingException {
-    List<String> lines =
-        ImmutableList.of(
-            "com.google.Feature1:feature1",
-            "*:nonbase",
-            "com.strange.*:feature2");
-    FeatureClassMapping mapping = new FeatureClassMapping(lines);
-    assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1");
-    assertEquals(mapping.featureForClass("com.google.different.Feature1"), "nonbase");
-    assertEquals(mapping.featureForClass("com.strange.different.Feature1"), "feature2");
-    assertEquals(mapping.featureForClass("Feature1"), "nonbase");
-    assertEquals(mapping.featureForClass("a.b.z.A"), "nonbase");
-  }
-
-  private void testBaseWildcard(boolean explicitBase) throws FeatureMappingException {
-    List<String> lines =
-        ImmutableList.of(
-            "com.google.Feature1:feature1",
-            explicitBase ? "*:base" : "",
-            "com.strange.*:feature2");
-    FeatureClassMapping mapping = new FeatureClassMapping(lines);
-    assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1");
-    assertEquals(mapping.featureForClass("com.google.different.Feature1"), "base");
-    assertEquals(mapping.featureForClass("com.strange.different.Feature1"), "feature2");
-    assertEquals(mapping.featureForClass("com.stranger.Clazz"), "base");
-    assertEquals(mapping.featureForClass("Feature1"), "base");
-    assertEquals(mapping.featureForClass("a.b.z.A"), "base");
-  }
-
-  @Test
-  public void testWrongLines() throws Exception {
-    // No colon.
-    ensureThrowsMappingException("foo");
-    ensureThrowsMappingException("com.google.base");
-    // Two colons.
-    ensureThrowsMappingException(ImmutableList.of("a:b:c"));
-
-    // Empty identifier.
-    ensureThrowsMappingException("com..google:feature1");
-
-    // Ambiguous redefinition
-    ensureThrowsMappingException(
-        ImmutableList.of("com.google.foo:feature1", "com.google.foo:feature2"));
-    ensureThrowsMappingException(
-        ImmutableList.of("com.google.foo.*:feature1", "com.google.foo.*:feature2"));
-  }
-
-  @Test
-  public void testUsesOnlyExactMappings() throws Exception {
-    List<String> lines =
-        ImmutableList.of(
-            "com.pkg1.Clazz:feature1",
-            "com.pkg2.Clazz:feature2");
-    FeatureClassMapping mapping = new FeatureClassMapping(lines);
-
-    assertEquals(mapping.featureForClass("com.pkg1.Clazz"), "feature1");
-    assertEquals(mapping.featureForClass("com.pkg2.Clazz"), "feature2");
-    assertEquals(mapping.featureForClass("com.pkg1.Other"), mapping.baseName);
-  }
-}
diff --git a/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1 b/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
index 27356f7..0f2a679 100644
--- a/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
+++ b/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
@@ -1 +1 @@
-175091160988fe534b90b70308ac0f7c489e48ec
\ No newline at end of file
+cec0d3cf2655ac9c77410ddf87a7b4e5900858a5
\ No newline at end of file
diff --git a/tools/apk_masseur.py b/tools/apk_masseur.py
index b363bb7..a4a010b 100755
--- a/tools/apk_masseur.py
+++ b/tools/apk_masseur.py
@@ -45,9 +45,6 @@
   apk = args[0]
   return (options, apk)
 
-def findKeystore():
-  return os.path.join(os.getenv('HOME'), '.android', 'app.keystore')
-
 def repack(apk, processed_out, resources, temp, quiet, logging):
   processed_apk = os.path.join(temp, 'processed.apk')
   shutil.copyfile(apk, processed_apk)
@@ -80,25 +77,13 @@
 
 def sign(unsigned_apk, keystore, temp, quiet, logging):
   signed_apk = os.path.join(temp, 'unaligned.apk')
-  apk_utils.sign_with_apksigner(
+  return apk_utils.sign_with_apksigner(
       unsigned_apk, signed_apk, keystore, quiet=quiet, logging=logging)
-  return signed_apk
 
 def align(signed_apk, temp, quiet, logging):
   utils.Print('Aligning', quiet=quiet)
   aligned_apk = os.path.join(temp, 'aligned.apk')
-  zipalign_path = (
-      'zipalign' if 'build_tools' in os.environ.get('PATH')
-      else os.path.join(utils.getAndroidBuildTools(), 'zipalign'))
-  cmd = [
-    zipalign_path,
-    '-f',
-    '4',
-    signed_apk,
-    aligned_apk
-  ]
-  utils.RunCmd(cmd, quiet=quiet, logging=logging)
-  return signed_apk
+  return apk_utils.align(signed_apk, aligned_apk)
 
 def masseur(
     apk, dex=None, resources=None, out=None, adb_options=None, keystore=None,
@@ -106,7 +91,7 @@
   if not out:
     out = os.path.basename(apk)
   if not keystore:
-    keystore = findKeystore()
+    keystore = apk_utils.default_keystore()
   with utils.TempDir() as temp:
     processed_apk = None
     if dex:
diff --git a/tools/apk_utils.py b/tools/apk_utils.py
index c3c616b..86d3b0f 100755
--- a/tools/apk_utils.py
+++ b/tools/apk_utils.py
@@ -5,9 +5,13 @@
 
 import optparse
 import os
+import shutil
 import subprocess
 import sys
+import time
+
 import utils
+import zip_utils
 
 USAGE = 'usage: %prog [options] <apk>'
 
@@ -34,6 +38,30 @@
   apk = args[0]
   return (options, apk)
 
+def add_baseline_profile_to_apk(apk, baseline_profile, tmp_dir):
+  if baseline_profile is None:
+    return apk
+  ts = time.time_ns()
+  dest_apk = os.path.join(tmp_dir, 'app-%s.apk' % ts)
+  dest_apk_aligned = os.path.join(tmp_dir, 'app-aligned-%s.apk' % ts)
+  dest_apk_signed = os.path.join(tmp_dir, 'app-signed-%s.apk' % ts)
+  shutil.copy2(apk, dest_apk)
+  zip_utils.add_file_to_zip(
+      baseline_profile, 'assets/dexopt/baseline.prof', dest_apk)
+  align(dest_apk, dest_apk_aligned)
+  sign_with_apksigner(dest_apk_aligned, dest_apk_signed)
+  return dest_apk_signed
+
+def align(apk, aligned_apk):
+  zipalign_path = (
+      'zipalign' if 'build_tools' in os.environ.get('PATH')
+      else os.path.join(utils.getAndroidBuildTools(), 'zipalign'))
+  cmd = [zipalign_path, '-f', '4', apk, aligned_apk]
+  utils.RunCmd(cmd, quiet=True, logging=False)
+  return aligned_apk
+
+def default_keystore():
+  return os.path.join(os.getenv('HOME'), '.android', 'app.keystore')
 
 def sign(unsigned_apk, signed_apk, keystore, quiet=False, logging=True):
   utils.Print('Signing (ignore the warnings)', quiet=quiet)
@@ -52,20 +80,20 @@
   utils.RunCmd(cmd, quiet=quiet)
 
 def sign_with_apksigner(
-    unsigned_apk, signed_apk, keystore, password='android', quiet=False,
+    unsigned_apk, signed_apk, keystore=None, password='android', quiet=False,
     logging=True):
   cmd = [
     os.path.join(utils.getAndroidBuildTools(), 'apksigner'),
     'sign',
     '-v',
-    '--ks', keystore,
+    '--ks', keystore or default_keystore(),
     '--ks-pass', 'pass:' + password,
     '--min-sdk-version', '19',
     '--out', signed_apk,
     unsigned_apk
   ]
   utils.RunCmd(cmd, quiet=quiet, logging=logging)
-
+  return signed_apk
 
 def main():
   (options, apk) = parse_options()
diff --git a/tools/archive.py b/tools/archive.py
index 893fc19..d7bf1a9 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -33,22 +33,13 @@
       type="string", action="store")
   return result.parse_args()
 
-def GetToolVersion(jar_path):
-  # TODO(mkroghj) This would not work for r8-lib, maybe use utils.getR8Version.
-  output = subprocess.check_output([
-    jdk.GetJavaExecutable(), '-jar', jar_path, '--version'
-  ]).decode('utf-8')
-  return output.splitlines()[0].strip()
-
 def GetVersion():
-  r8_version = GetToolVersion(utils.R8_JAR)
-  d8_version = GetToolVersion(utils.D8_JAR)
-  # The version printed is "D8 vVERSION_NUMBER" and "R8 vVERSION_NUMBER"
-  # Sanity check that versions match.
-  if d8_version.split()[1] != r8_version.split()[1]:
-    raise Exception(
-        'Version mismatch: \n%s\n%s' % (d8_version, r8_version))
-  return d8_version.split()[1]
+  output = subprocess.check_output([
+      jdk.GetJavaExecutable(), '-cp', utils.R8_JAR, 'com.android.tools.r8.R8',
+      '--version'
+  ]).decode('utf-8')
+  r8_version = output.splitlines()[0].strip()
+  return r8_version.split()[1]
 
 def GetGitBranches():
   return subprocess.check_output(['git', 'show', '-s', '--pretty=%d', 'HEAD'])
@@ -157,7 +148,6 @@
     # The '-Pno_internal' flag is important because we generate the lib based on uses in tests.
     gradle.RunGradle([
         utils.R8,
-        utils.D8,
         utils.R8LIB,
         utils.R8LIB_NO_DEPS,
         utils.R8RETRACE,
@@ -193,7 +183,6 @@
     create_maven_release.write_default_r8_pom_file(default_pom_file, version)
 
     for file in [
-      utils.D8_JAR,
       utils.R8_JAR,
       utils.R8LIB_JAR,
       utils.R8LIB_JAR + '.map',
diff --git a/tools/dexsplitter.py b/tools/dexsplitter.py
deleted file mode 100755
index a97d089..0000000
--- a/tools/dexsplitter.py
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-# for details. All rights reserved. Use of this source code is governed by a
-# BSD-style license that can be found in the LICENSE file.
-
-import sys
-import toolhelper
-
-if __name__ == '__main__':
-  sys.exit(toolhelper.run('dexsplitter', sys.argv[1:]))
diff --git a/tools/gradle.py b/tools/gradle.py
index 19fa773..92503a7 100755
--- a/tools/gradle.py
+++ b/tools/gradle.py
@@ -59,10 +59,10 @@
     GRADLE, GRADLE_TGZ, GRADLE_SHA1, 'Gradle binary')
 
 def EnsureJdk():
-  jdkHome = jdk.GetJdkHome()
-  jdkTgz = jdkHome + '.tar.gz'
+  jdkRoot = jdk.GetJdkRoot()
+  jdkTgz = jdkRoot + '.tar.gz'
   jdkSha1 = jdkTgz + '.sha1'
-  utils.EnsureDepFromGoogleCloudStorage(jdkHome, jdkTgz, jdkSha1, 'JDK')
+  utils.EnsureDepFromGoogleCloudStorage(jdkRoot, jdkTgz, jdkSha1, 'JDK')
 
 def EnsureDeps():
   EnsureGradle()
diff --git a/tools/internal_test.py b/tools/internal_test.py
index 8e4caa5..45dd694 100755
--- a/tools/internal_test.py
+++ b/tools/internal_test.py
@@ -189,7 +189,7 @@
   return utils.get_HEAD_sha1()
 
 def get_test_result_dir():
-  return os.path.join(utils.R8_TEST_RESULTS_BUCKET, TEST_RESULT_DIR)
+  return os.path.join(utils.R8_INTERNAL_TEST_RESULTS_BUCKET, TEST_RESULT_DIR)
 
 def get_sha_destination(sha):
   return os.path.join(get_test_result_dir(), sha)
@@ -287,6 +287,9 @@
     print('Tests failed, you can print the logs by running(googlers only):')
     print('  tools/internal_test.py --print_logs %s' % git_hash)
     return 1
+  else:
+    print(' Test validation of archiving logs, see b/177799191')
+    print('  tools/internal_test.py --print_logs %s' % git_hash)
 
 def run_continuously():
   # If this script changes, we will restart ourselves
@@ -374,7 +377,7 @@
         stderr_fd.close()
       if stdout_fd:
         stdout_fd.close()
-      if exitcode != 0:
+      if exitcode != 0 or True:
         handle_output(archive, stderr, stdout, popen.returncode,
                       timed_out, ' '.join(cmd))
     return exitcode
diff --git a/tools/jdk.py b/tools/jdk.py
index c9cb628..5cf1465 100755
--- a/tools/jdk.py
+++ b/tools/jdk.py
@@ -13,7 +13,10 @@
 def GetJdkHome():
   return GetJdk11Home()
 
-def GetJdk11Home():
+def GetJdkRoot():
+  return GetJdk11Root()
+
+def GetJdk11Root():
   root = os.path.join(JDK_DIR, 'jdk-11')
   if defines.IsLinux():
     return os.path.join(root, 'linux')
@@ -24,6 +27,14 @@
   else:
     return os.environ['JAVA_HOME']
 
+def GetJdk11Home():
+  root = GetJdk11Root()
+  # osx has the home inside Contents/Home in the bundle
+  if defines.IsOsX():
+    return os.path.join(root,'Contents', 'Home')
+  else:
+    return root
+
 def GetJdk9Home():
   root = os.path.join(JDK_DIR, 'openjdk-9.0.4')
   if defines.IsLinux():
diff --git a/tools/linux/README.art-versions b/tools/linux/README.art-versions
index fee679c..02e81cd 100644
--- a/tools/linux/README.art-versions
+++ b/tools/linux/README.art-versions
@@ -42,9 +42,9 @@
   <continue with repo sync as above>
 
 
-art-13-master (Android T)
--------------------------
-Build from master commit e208b04cc2efaf707390d7acbe8f978142701d72.
+art-13 (Android T)
+------------------
+Build from tm-dev commit 442e1091f39417c692d91609af05e58af60d8e2b.
 
 repo sync -cq -j24
 source build/envsetup.sh
@@ -53,16 +53,16 @@
 m -j48 build-art
 m -j48 test-art-host
 
-Collected into tools/linux/host/art-13-master. The "host" path element is checked
+Collected into tools/linux/host/art-13. The "host" path element is checked
 by the script for running Art.
 
   cd <r8 checkout>
   scripts/update-host-art.sh \
-     --android-checkout <...>/android/master \
-     --art-dir host/art-13-master \
+     --android-checkout <...>/android/tm-dev \
+     --art-dir host/art-13 \
      --android-product redfin
 
-(cd tools/linux/host; upload_to_google_storage.py -a --bucket r8-deps art-13-master)
+(cd tools/linux/host; upload_to_google_storage.py -a --bucket r8-deps art-13)
 
 art-12.0.0 (Android S)
 ---------------------
diff --git a/tools/linux/host/art-13-dev.tar.gz.sha1 b/tools/linux/host/art-13-dev.tar.gz.sha1
new file mode 100644
index 0000000..5367655
--- /dev/null
+++ b/tools/linux/host/art-13-dev.tar.gz.sha1
@@ -0,0 +1 @@
+fed34a1eecaf012550cdd9df24434b8b2068a194
\ No newline at end of file
diff --git a/tools/linux/host/art-13-master.tar.gz.sha1 b/tools/linux/host/art-13-master.tar.gz.sha1
deleted file mode 100644
index 8b16edd..0000000
--- a/tools/linux/host/art-13-master.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-a2cf2b34b8712adb5c25a18bc6135946cfb1a047
\ No newline at end of file
diff --git a/tools/startup/adb_utils.py b/tools/startup/adb_utils.py
index 981bcbd..7f1d3bc 100644
--- a/tools/startup/adb_utils.py
+++ b/tools/startup/adb_utils.py
@@ -4,11 +4,33 @@
 # BSD-style license that can be found in the LICENSE file.
 
 from enum import Enum
+import os
 import subprocess
+import sys
+import threading
 import time
 
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+import utils
+
 DEVNULL=subprocess.DEVNULL
 
+class ProcessReader(threading.Thread):
+
+  def __init__(self, process):
+    threading.Thread.__init__(self)
+    self.lines = []
+    self.process = process
+
+  def run(self):
+    for line in self.process.stdout:
+      line = line.decode('utf-8').strip()
+      self.lines.append(line)
+
+  def stop(self):
+    self.process.kill()
+
 class ScreenState(Enum):
   OFF_LOCKED = 1,
   OFF_UNLOCKED = 2
@@ -27,6 +49,11 @@
   def is_on_and_unlocked(self):
     return self == ScreenState.ON_UNLOCKED
 
+def broadcast(action, component, device_id=None):
+  print('Sending broadcast %s' % action)
+  cmd = create_adb_cmd('shell am broadcast -a %s %s' % (action, component), device_id)
+  return subprocess.check_output(cmd).decode('utf-8').strip().splitlines()
+
 def create_adb_cmd(arguments, device_id=None):
   assert isinstance(arguments, list) or isinstance(arguments, str)
   cmd = ['adb']
@@ -39,7 +66,7 @@
 def capture_app_profile_data(app_id, device_id=None):
   cmd = create_adb_cmd(
       'shell killall -s SIGUSR1 %s' % app_id, device_id)
-  subprocess.check_output(cmd)
+  subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
   time.sleep(5)
 
 def check_app_has_profile_data(app_id, device_id=None):
@@ -54,6 +81,10 @@
   if size == 4:
     raise ValueError('Expected size of profile at %s to be > 4K' % profile_path)
 
+def clear_logcat(device_id=None):
+  cmd = create_adb_cmd('logcat -c', device_id)
+  subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+
 def clear_profile_data(app_id, device_id=None):
   cmd = create_adb_cmd(
       'shell cmd package compile --reset %s' % app_id, device_id)
@@ -65,11 +96,13 @@
   subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
 
 def force_compilation(app_id, device_id=None):
+  print('Applying AOT (full)')
   cmd = create_adb_cmd(
       'shell cmd package compile -m speed -f %s' % app_id, device_id)
   subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
 
 def force_profile_compilation(app_id, device_id=None):
+  print('Applying AOT (profile)')
   cmd = create_adb_cmd(
       'shell cmd package compile -m speed-profile -f %s' % app_id, device_id)
   subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
@@ -86,6 +119,15 @@
         'Expected stdout to end with ".apk", was: %s' % stdout)
   return apk_path
 
+def get_profile_data(app_id, device_id=None):
+  with utils.TempDir() as temp:
+    source = get_profile_path(app_id)
+    target = os.path.join(temp, 'primary.prof')
+    cmd = create_adb_cmd('pull %s %s' % (source, target), device_id)
+    subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+    with open(target, 'rb') as f:
+      return f.read()
+
 def get_profile_path(app_id):
   return '/data/misc/profiles/cur/0/%s/primary.prof' % app_id
 
@@ -170,10 +212,23 @@
   return screen_off_timeout
 
 def install(apk, device_id=None):
+  print('Installing %s' % apk)
   cmd = create_adb_cmd('install %s' % apk, device_id)
   stdout = subprocess.check_output(cmd).decode('utf-8')
   assert 'Success' in stdout
 
+def install_profile(app_id, device_id=None):
+  # This assumes that the profileinstaller library has been added to the app,
+  # https://developer.android.com/jetpack/androidx/releases/profileinstaller.
+  action = 'androidx.profileinstaller.action.INSTALL_PROFILE'
+  component = '%s/androidx.profileinstaller.ProfileInstallReceiver' % app_id
+  stdout = broadcast(action, component, device_id)
+  assert len(stdout) == 2
+  assert stdout[0] == ('Broadcasting: Intent { act=%s flg=0x400000 cmp=%s }' % (action, component))
+  assert stdout[1] == 'Broadcast completed: result=1', stdout[1]
+  stop_app(app_id, device_id)
+  force_profile_compilation(app_id, device_id)
+
 def issue_key_event(key_event, device_id=None, sleep_in_seconds=1):
   cmd = create_adb_cmd('shell input keyevent %s' % key_event, device_id)
   stdout = subprocess.check_output(cmd).decode('utf-8').strip()
@@ -200,6 +255,10 @@
   assert not wait_for_activity_to_launch or 'total_time' in result
   return result
 
+def navigate_to_home_screen(device_id=None):
+  cmd = create_adb_cmd('shell input keyevent KEYCODE_HOME', device_id)
+  subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+
 def prepare_for_interaction_with_device(device_id=None, device_pin=None):
   # Increase screen off timeout to avoid device screen turns off.
   twenty_four_hours_in_millis = 24 * 60 * 60 * 1000
@@ -226,7 +285,26 @@
   stdout = subprocess.check_output(cmd).decode('utf-8').strip()
   assert len(stdout) == 0
 
+def start_logcat(device_id=None, format=None, filter=None):
+  args = ['logcat']
+  if format:
+    args.extend(['--format', format])
+  if filter:
+    args.append(filter)
+  cmd = create_adb_cmd(args, device_id)
+  logcat_process = subprocess.Popen(
+      cmd, bufsize=1024*1024, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+  reader = ProcessReader(logcat_process)
+  reader.start()
+  return reader
+
+def stop_logcat(logcat_reader):
+  logcat_reader.stop()
+  logcat_reader.join()
+  return logcat_reader.lines
+
 def stop_app(app_id, device_id=None):
+  print('Shutting down %s' % app_id)
   cmd = create_adb_cmd('shell am force-stop %s' % app_id, device_id)
   subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
 
@@ -237,6 +315,7 @@
       device_id)
 
 def uninstall(app_id, device_id=None):
+  print('Uninstalling %s' % app_id)
   cmd = create_adb_cmd('uninstall %s' % app_id, device_id)
   process_result = subprocess.run(cmd, capture_output=True)
   stdout = process_result.stdout.decode('utf-8')
@@ -246,7 +325,8 @@
   else:
     expected_error = (
         'java.lang.IllegalArgumentException: Unknown package: %s' % app_id)
-    assert expected_error in stderr
+    assert 'Failure [DELETE_FAILED_INTERNAL_ERROR]' in stdout \
+        or expected_error in stderr
 
 def unlock(device_id=None, device_pin=None):
   screen_state = get_screen_state(device_id)
diff --git a/tools/startup/generate_startup_descriptors.py b/tools/startup/generate_startup_descriptors.py
index ea0fdf3..f1faee43 100755
--- a/tools/startup/generate_startup_descriptors.py
+++ b/tools/startup/generate_startup_descriptors.py
@@ -6,16 +6,23 @@
 import adb_utils
 
 import argparse
+import os
 import sys
 import time
 
 def extend_startup_descriptors(startup_descriptors, iteration, options):
-  generate_startup_profile_on_device(options)
-  classes_and_methods = adb_utils.get_classes_and_methods_from_app_profile(
-      options.app_id, options.device_id)
-  current_startup_descriptors = \
-      transform_classes_and_methods_to_r8_startup_descriptors(
-          classes_and_methods, options)
+  (logcat, profile, profile_classes_and_methods) = \
+      generate_startup_profile(options)
+  if options.logcat:
+    write_tmp_logcat(logcat, iteration, options)
+    current_startup_descriptors = get_r8_startup_descriptors_from_logcat(logcat)
+  else:
+    write_tmp_profile(profile, iteration, options)
+    write_tmp_profile_classes_and_methods(profile_classes_and_methods, iteration, options)
+    current_startup_descriptors = \
+        transform_classes_and_methods_to_r8_startup_descriptors(
+            profile_classes_and_methods, options)
+  write_tmp_startup_descriptors(current_startup_descriptors, iteration, options)
   number_of_new_startup_descriptors = add_r8_startup_descriptors(
       startup_descriptors, current_startup_descriptors)
   if options.out is not None:
@@ -24,15 +31,32 @@
             % (number_of_new_startup_descriptors, iteration + 1))
   return number_of_new_startup_descriptors
 
-def generate_startup_profile_on_device(options):
-  if not options.use_existing_profile:
-    # Clear existing profile data.
-    adb_utils.clear_profile_data(options.app_id, options.device_id)
-
+def generate_startup_profile(options):
+  logcat = None
+  profile = None
+  profile_classes_and_methods = None
+  if options.use_existing_profile:
+    # Verify presence of profile.
+    adb_utils.check_app_has_profile_data(options.app_id, options.device_id)
+    profile = adb_utils.get_profile_data(options.app_id, options.device_id)
+    profile_classes_and_methods = \
+        adb_utils.get_classes_and_methods_from_app_profile(
+            options.app_id, options.device_id)
+  else:
     # Unlock device.
     tear_down_options = adb_utils.prepare_for_interaction_with_device(
         options.device_id, options.device_pin)
 
+    logcat_process = None
+    if options.logcat:
+      # Clear logcat and start capturing logcat.
+      adb_utils.clear_logcat(options.device_id)
+      logcat_process = adb_utils.start_logcat(
+          options.device_id, format='raw', filter='r8:I *:S')
+    else:
+      # Clear existing profile data.
+      adb_utils.clear_profile_data(options.app_id, options.device_id)
+
     # Launch activity to generate startup profile on device.
     adb_utils.launch_activity(
         options.app_id, options.main_activity, options.device_id)
@@ -40,17 +64,36 @@
     # Wait for activity startup.
     time.sleep(options.startup_duration)
 
-    # Capture startup profile.
-    adb_utils.capture_app_profile_data(options.app_id, options.device_id)
+    if options.logcat:
+      # Get startup descriptors from logcat.
+      logcat = adb_utils.stop_logcat(logcat_process)
+    else:
+      # Capture startup profile.
+      adb_utils.capture_app_profile_data(options.app_id, options.device_id)
+      profile = adb_utils.get_profile_data(options.app_id, options.device_id)
+      profile_classes_and_methods = \
+          adb_utils.get_classes_and_methods_from_app_profile(
+              options.app_id, options.device_id)
 
     # Shutdown app.
     adb_utils.stop_app(options.app_id, options.device_id)
-
     adb_utils.tear_down_after_interaction_with_device(
         tear_down_options, options.device_id)
 
-  # Verify presence of profile.
-  adb_utils.check_app_has_profile_data(options.app_id, options.device_id)
+  return (logcat, profile, profile_classes_and_methods)
+
+def get_r8_startup_descriptors_from_logcat(logcat):
+  startup_descriptors = []
+  for line in logcat:
+    if line == '--------- beginning of main':
+      continue
+    if line == '--------- beginning of system':
+      continue
+    if not line.startswith('L') or not line.endswith(';'):
+      print('Unrecognized line in logcat: %s' % line)
+      continue
+    startup_descriptors.append(line)
+  return startup_descriptors
 
 def transform_classes_and_methods_to_r8_startup_descriptors(
     classes_and_methods, options):
@@ -72,6 +115,53 @@
   return new_number_of_startup_descriptors \
       - previous_number_of_startup_descriptors
 
+def write_tmp_binary_artifact(artifact, iteration, options, name):
+  if not options.tmp_dir:
+    return
+  out_dir = os.path.join(options.tmp_dir, str(iteration))
+  os.makedirs(out_dir, exist_ok=True)
+  path = os.path.join(out_dir, name)
+  with open(path, 'wb') as f:
+    f.write(artifact)
+
+def write_tmp_textual_artifact(artifact, iteration, options, name, item_to_string=None):
+  if not options.tmp_dir:
+    return
+  out_dir = os.path.join(options.tmp_dir, str(iteration))
+  os.makedirs(out_dir, exist_ok=True)
+  path = os.path.join(out_dir, name)
+  with open(path, 'w') as f:
+    for item in artifact:
+      f.write(item if item_to_string is None else item_to_string(item))
+      f.write('\n')
+
+def write_tmp_logcat(logcat, iteration, options):
+  write_tmp_textual_artifact(logcat, iteration, options, 'logcat.txt')
+
+def write_tmp_profile(profile, iteration, options):
+  write_tmp_binary_artifact(profile, iteration, options, 'primary.prof')
+
+def write_tmp_profile_classes_and_methods(
+    profile_classes_and_methods, iteration, options):
+  def item_to_string(item):
+    descriptor = item.get('descriptor')
+    flags = item.get('flags')
+    return '%s%s%s%s' % (
+        'H' if flags.get('hot') else '',
+        'S' if flags.get('startup') else '',
+        'P' if flags.get('post_startup') else '',
+        descriptor)
+  write_tmp_textual_artifact(
+      profile_classes_and_methods,
+      iteration,
+      options,
+      'profile.txt',
+      item_to_string)
+
+def write_tmp_startup_descriptors(startup_descriptors, iteration, options):
+  write_tmp_textual_artifact(
+      startup_descriptors, iteration, options, 'startup-descriptors.txt')
+
 def parse_options(argv):
   result = argparse.ArgumentParser(
       description='Generate a perfetto trace file.')
@@ -84,6 +174,9 @@
                       help='Device id (e.g., emulator-5554).')
   result.add_argument('--device-pin',
                       help='Device pin code (e.g., 1234)')
+  result.add_argument('--logcat',
+                      action='store_true',
+                      default=False)
   result.add_argument('--include-post-startup',
                       help='Include post startup classes and methods in the R8 '
                            'startup descriptors',
@@ -102,6 +195,9 @@
                       help='Duration in seconds before shutting down app',
                       default=15,
                       type=int)
+  result.add_argument('--tmp-dir',
+                      help='Directory where to store intermediate artifacts'
+                            ' (by default these are not emitted)')
   result.add_argument('--until-stable',
                       help='Repeat profile generation until no new startup '
                            'descriptors are found',
diff --git a/tools/startup/measure_startup.py b/tools/startup/measure_startup.py
index 080f827..f0b2ce9 100755
--- a/tools/startup/measure_startup.py
+++ b/tools/startup/measure_startup.py
@@ -20,6 +20,7 @@
 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 
 import adb_utils
+import apk_utils
 import perfetto_utils
 import utils
 
@@ -45,13 +46,13 @@
       tear_down_options['previous_screen_off_timeout'],
       options.device_id)
 
-def run_all(options, tmp_dir):
+def run_all(apk, options, tmp_dir):
   # Launch app while collecting information.
   data_avg = {}
   for iteration in range(options.iterations):
     print('Starting iteration %i' % iteration)
     out_dir = os.path.join(options.out_dir, str(iteration))
-    prepare_for_run(out_dir, options)
+    prepare_for_run(apk, out_dir, options)
     data = run(out_dir, options, tmp_dir)
     add_data(data_avg, data)
     print("Result:")
@@ -64,20 +65,25 @@
   print(data_avg)
   write_data(options.out_dir, data_avg)
 
-def prepare_for_run(out_dir, options):
+def prepare_for_run(apk, out_dir, options):
   adb_utils.root(options.device_id)
   adb_utils.uninstall(options.app_id, options.device_id)
-  adb_utils.install(options.apk, options.device_id)
-  adb_utils.clear_profile_data(options.app_id, options.device_id)
+  adb_utils.install(apk, options.device_id)
   if options.aot:
-    adb_utils.force_compilation(options.app_id, options.device_id)
-  elif options.aot_profile:
+    if options.baseline_profile:
+      adb_utils.clear_profile_data(options.app_id, options.device_id)
+      adb_utils.install_profile(options.app_id, options.device_id)
+    else:
+      adb_utils.force_compilation(options.app_id, options.device_id)
+  if options.hot_startup:
     adb_utils.launch_activity(
-        options.app_id, options.main_activity, options.device_id)
-    time.sleep(options.aot_profile_sleep)
-    adb_utils.stop_app(options.app_id, options.device_id)
-    adb_utils.force_profile_compilation(options.app_id, options.device_id)
-
+        options.app_id,
+        options.main_activity,
+        options.device_id,
+        wait_for_activity_to_launch=False)
+    time.sleep(options.startup_duration)
+    adb_utils.navigate_to_home_screen(options.device_id)
+    time.sleep(1)
   adb_utils.drop_caches(options.device_id)
   os.makedirs(out_dir, exist_ok=True)
 
@@ -87,9 +93,9 @@
   # Start perfetto trace collector.
   perfetto_process = None
   perfetto_trace_path = None
-  if not options.no_perfetto:
+  if options.perfetto:
     perfetto_process, perfetto_trace_path = perfetto_utils.record_android_trace(
-        out_dir, tmp_dir)
+        out_dir, tmp_dir, options.device_id)
 
   # Launch main activity.
   launch_activity_result = adb_utils.launch_activity(
@@ -99,7 +105,7 @@
       wait_for_activity_to_launch=True)
 
   # Wait for perfetto trace collector to stop.
-  if not options.no_perfetto:
+  if options.perfetto:
     perfetto_utils.stop_record_android_trace(perfetto_process, out_dir)
 
   # Get minor and major page faults from app process.
@@ -137,36 +143,38 @@
 
 def compute_startup_data(launch_activity_result, perfetto_trace_path, options):
   startup_data = {
-    'time_to_activity_started_ms': launch_activity_result.get('total_time')
+    'adb_startup': launch_activity_result.get('total_time')
   }
   perfetto_startup_data = {}
-  if not options.no_perfetto:
+  if options.perfetto:
     trace_processor = TraceProcessor(file_path=perfetto_trace_path)
 
-    # Compute time to first frame according to the builtin android_startup metric.
+    # Compute time to first frame according to the builtin android_startup
+    # metric.
     startup_metric = trace_processor.metric(['android_startup'])
     time_to_first_frame_ms = \
         startup_metric.android_startup.startup[0].to_first_frame.dur_ms
+    perfetto_startup_data['perfetto_startup'] = round(time_to_first_frame_ms)
 
-    # Compute time to first and last doFrame event.
-    bind_application_slice = perfetto_utils.find_unique_slice_by_name(
-        'bindApplication', options, trace_processor)
-    activity_start_slice = perfetto_utils.find_unique_slice_by_name(
-        'activityStart', options, trace_processor)
-    do_frame_slices = perfetto_utils.find_slices_by_name(
-        'Choreographer#doFrame', options, trace_processor)
-    first_do_frame_slice = next(do_frame_slices)
-    *_, last_do_frame_slice = do_frame_slices
+    if not options.hot_startup:
+      # Compute time to first and last doFrame event.
+      bind_application_slice = perfetto_utils.find_unique_slice_by_name(
+          'bindApplication', options, trace_processor)
+      activity_start_slice = perfetto_utils.find_unique_slice_by_name(
+          'activityStart', options, trace_processor)
+      do_frame_slices = perfetto_utils.find_slices_by_name(
+          'Choreographer#doFrame', options, trace_processor)
+      first_do_frame_slice = next(do_frame_slices)
+      *_, last_do_frame_slice = do_frame_slices
 
-    perfetto_startup_data = {
-      'time_to_first_frame_ms': round(time_to_first_frame_ms),
-      'time_to_first_choreographer_do_frame_ms':
-          round(perfetto_utils.get_slice_end_since_start(
-              first_do_frame_slice, bind_application_slice)),
-      'time_to_last_choreographer_do_frame_ms':
-          round(perfetto_utils.get_slice_end_since_start(
-              last_do_frame_slice, bind_application_slice))
-    }
+      perfetto_startup_data.update({
+        'time_to_first_choreographer_do_frame_ms':
+            round(perfetto_utils.get_slice_end_since_start(
+                first_do_frame_slice, bind_application_slice)),
+        'time_to_last_choreographer_do_frame_ms':
+            round(perfetto_utils.get_slice_end_since_start(
+                last_do_frame_slice, bind_application_slice))
+      })
 
   # Return combined startup data.
   return startup_data | perfetto_startup_data
@@ -191,10 +199,6 @@
                       help='Enable force compilation using profiles',
                       default=False,
                       action='store_true')
-  result.add_argument('--aot-profile-sleep',
-                      help='Duration in seconds before forcing compilation',
-                      default=15,
-                      type=int)
   result.add_argument('--apk',
                       help='Path to the APK',
                       required=True)
@@ -202,6 +206,10 @@
                       help='Device id (e.g., emulator-5554).')
   result.add_argument('--device-pin',
                       help='Device pin code (e.g., 1234)')
+  result.add_argument('--hot-startup',
+                      help='Measure hot startup instead of cold startup',
+                      default=False,
+                      action='store_true')
   result.add_argument('--iterations',
                       help='Number of traces to generate',
                       default=1,
@@ -216,16 +224,26 @@
   result.add_argument('--out-dir',
                       help='Directory to store trace files in',
                       required=True)
+  result.add_argument('--baseline-profile',
+                      help='Baseline profile to install')
+  result.add_argument('--startup-duration',
+                      help='Duration in seconds before shutting down app',
+                      default=15,
+                      type=int)
   options, args = result.parse_known_args(argv)
-  assert (not options.aot) or (not options.aot_profile)
+  setattr(options, 'perfetto', not options.no_perfetto)
+  # Profile is only used with --aot.
+  assert options.aot or not options.baseline_profile
   return options, args
 
 def main(argv):
   (options, args) = parse_options(argv)
   with utils.TempDir() as tmp_dir:
+    apk = apk_utils.add_baseline_profile_to_apk(
+        options.apk, options.baseline_profile, tmp_dir)
     tear_down_options = adb_utils.prepare_for_interaction_with_device(
         options.device_id, options.device_pin)
-    run_all(options, tmp_dir)
+    run_all(apk, options, tmp_dir)
     adb_utils.tear_down_after_interaction_with_device(
         tear_down_options, options.device_id)
 
diff --git a/tools/startup/perfetto_utils.py b/tools/startup/perfetto_utils.py
index d85f53e..d2f53b4 100644
--- a/tools/startup/perfetto_utils.py
+++ b/tools/startup/perfetto_utils.py
@@ -31,7 +31,7 @@
     assert os.path.exists(record_android_trace_path)
   return record_android_trace_path
 
-def record_android_trace(out_dir, tmp_dir):
+def record_android_trace(out_dir, tmp_dir, device_id=None):
   record_android_trace_path = ensure_record_android_trace(tmp_dir)
   config_path = os.path.join(os.path.dirname(__file__), 'config.pbtx')
   perfetto_trace_path = os.path.join(out_dir, 'trace.perfetto-trace')
@@ -43,6 +43,8 @@
       '--out',
       perfetto_trace_path,
       '--no-open']
+  if device_id is not None:
+    cmd.extend(['--serial', device_id])
   perfetto_process = subprocess.Popen(
       cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
   lines = []
diff --git a/tools/test.py b/tools/test.py
index 665ab5f..0e3bf0f 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -530,7 +530,7 @@
   print("Reading failed tests in", report)
   failing = set()
   inFailedSection = False
-  for line in file(report):
+  for line in open(report):
     l = line.strip()
     if l == "<h2>Failed tests</h2>":
       inFailedSection = True
diff --git a/tools/utils.py b/tools/utils.py
index 2a4b18e..9e837c3 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -51,7 +51,6 @@
 R8LIB_TESTS_DEPS_TARGET = R8_TESTS_DEPS_TARGET
 
 ALL_DEPS_JAR = os.path.join(LIBS, 'deps_all.jar')
-D8_JAR = os.path.join(LIBS, 'd8.jar')
 R8_JAR = os.path.join(LIBS, 'r8.jar')
 R8_WITH_RELOCATED_DEPS_JAR = os.path.join(LIBS, 'r8_with_relocated_deps.jar')
 R8LIB_JAR = os.path.join(LIBS, 'r8lib.jar')
@@ -102,6 +101,7 @@
 USER_HOME = os.path.expanduser('~')
 
 R8_TEST_RESULTS_BUCKET = 'r8-test-results'
+R8_INTERNAL_TEST_RESULTS_BUCKET = 'r8-internal-test-results'
 
 def archive_file(name, gs_dir, src_file):
   gs_file = '%s/%s' % (gs_dir, name)
@@ -344,7 +344,9 @@
   if is_html:
     cmd += ['-z', 'html']
   if public_read:
-    cmd += ['-a', 'public-read']
+    # TODO(b/177799191) Temporarily disable public-read to test uniform access control
+    if 'r8-test-results' not in destination:
+      cmd += ['-a', 'public-read']
   cmd += ['-R', directory, destination]
   PrintCmd(cmd)
   subprocess.check_call(cmd)
diff --git a/tools/zip_utils.py b/tools/zip_utils.py
new file mode 100644
index 0000000..b0571f2
--- /dev/null
+++ b/tools/zip_utils.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+import zipfile
+
+def add_file_to_zip(file, destination, zip_file):
+  with zipfile.ZipFile(zip_file, 'a') as zip:
+    zip.write(file, destination)