Merge commit 'e19234b32da0a3a33c6677979323e27e047edf91' into dev-release
diff --git a/src/library_desugar/java/j$/nio/file/FileSystem.java b/src/library_desugar/java/j$/nio/file/FileSystem.java
new file mode 100644
index 0000000..a7a49a2
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/FileSystem.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 j$.nio.file;
+
+import j$.nio.file.spi.FileSystemProvider;
+
+public class FileSystem {
+  public FileSystemProvider provider() {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/j$/nio/file/FileSystems.java b/src/library_desugar/java/j$/nio/file/FileSystems.java
new file mode 100644
index 0000000..d7214ca
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/FileSystems.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 j$.nio.file;
+
+public class FileSystems {
+  public static FileSystem getDefault() {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/j$/nio/file/Files.java b/src/library_desugar/java/j$/nio/file/Files.java
index 2825d24..f34593b 100644
--- a/src/library_desugar/java/j$/nio/file/Files.java
+++ b/src/library_desugar/java/j$/nio/file/Files.java
@@ -5,10 +5,9 @@
 package j$.nio.file;
 
 import java.io.IOException;
-import java.nio.file.Path;
 
 public class Files {
-  public static String probeContentType(Path path) throws IOException {
+  public static String probeContentType(j$.nio.file.Path path) throws IOException {
     return null;
   }
 }
diff --git a/src/library_desugar/java/j$/nio/file/LinkOption.java b/src/library_desugar/java/j$/nio/file/LinkOption.java
new file mode 100644
index 0000000..b8cf168
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/LinkOption.java
@@ -0,0 +1,15 @@
+// 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;
+
+public class LinkOption extends OpenOption {
+  public static java.nio.file.LinkOption wrap_convert(j$.nio.file.LinkOption option) {
+    return null;
+  }
+
+  public static j$.nio.file.LinkOption wrap_convert(java.nio.file.LinkOption option) {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/j$/nio/file/OpenOption.java b/src/library_desugar/java/j$/nio/file/OpenOption.java
new file mode 100644
index 0000000..8b063d1
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/OpenOption.java
@@ -0,0 +1,7 @@
+// 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;
+
+public class OpenOption {}
diff --git a/src/library_desugar/java/j$/nio/file/Path.java b/src/library_desugar/java/j$/nio/file/Path.java
new file mode 100644
index 0000000..781ab5f
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/Path.java
@@ -0,0 +1,12 @@
+// 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;
+
+public class Path {
+
+  public static j$.nio.file.Path wrap_convert(java.nio.file.Path path) {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/j$/nio/file/StandardOpenOption.java b/src/library_desugar/java/j$/nio/file/StandardOpenOption.java
new file mode 100644
index 0000000..a65da37
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/StandardOpenOption.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 j$.nio.file;
+
+public class StandardOpenOption extends OpenOption {
+
+  public static java.nio.file.StandardOpenOption wrap_convert(
+      j$.nio.file.StandardOpenOption option) {
+    return null;
+  }
+
+  public static j$.nio.file.StandardOpenOption wrap_convert(
+      java.nio.file.StandardOpenOption option) {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/j$/nio/file/WatchEvent.java b/src/library_desugar/java/j$/nio/file/WatchEvent.java
new file mode 100644
index 0000000..fd977e9
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/WatchEvent.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 j$.nio.file;
+
+public class WatchEvent<T> {
+
+  public static java.nio.file.WatchEvent<?> wrap_convert(j$.nio.file.WatchEvent<?> option) {
+    return null;
+  }
+
+  public static j$.nio.file.WatchEvent<?> wrap_convert(java.nio.file.WatchEvent<?> option) {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributeView.java b/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributeView.java
new file mode 100644
index 0000000..774cf2c
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributeView.java
@@ -0,0 +1,17 @@
+// 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.attribute;
+
+public class BasicFileAttributeView extends FileAttributeView {
+  public static java.nio.file.attribute.BasicFileAttributeView wrap_convert(
+      j$.nio.file.attribute.BasicFileAttributeView fileAttributeView) {
+    return null;
+  }
+
+  public static j$.nio.file.attribute.BasicFileAttributeView wrap_convert(
+      java.nio.file.attribute.BasicFileAttributeView fileAttributeView) {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributes.java b/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributes.java
new file mode 100644
index 0000000..dd91a95
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributes.java
@@ -0,0 +1,17 @@
+// 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.attribute;
+
+public class BasicFileAttributes {
+  public static java.nio.file.attribute.BasicFileAttributes wrap_convert(
+      j$.nio.file.attribute.BasicFileAttributes fileAttributes) {
+    return null;
+  }
+
+  public static j$.nio.file.attribute.BasicFileAttributes wrap_convert(
+      java.nio.file.attribute.BasicFileAttributes fileAttributes) {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/FileAttributeView.java b/src/library_desugar/java/j$/nio/file/attribute/FileAttributeView.java
new file mode 100644
index 0000000..34046eb3
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/attribute/FileAttributeView.java
@@ -0,0 +1,17 @@
+// 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.attribute;
+
+public class FileAttributeView {
+  public static java.nio.file.attribute.FileAttributeView wrap_convert(
+      j$.nio.file.attribute.FileAttributeView fileAttributeView) {
+    return null;
+  }
+
+  public static j$.nio.file.attribute.FileAttributeView wrap_convert(
+      java.nio.file.attribute.FileAttributeView fileAttributeView) {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/FileOwnerAttributeView.java b/src/library_desugar/java/j$/nio/file/attribute/FileOwnerAttributeView.java
new file mode 100644
index 0000000..34a637d
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/attribute/FileOwnerAttributeView.java
@@ -0,0 +1,17 @@
+// 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.attribute;
+
+public class FileOwnerAttributeView extends FileAttributeView {
+  public static java.nio.file.attribute.FileOwnerAttributeView wrap_convert(
+      j$.nio.file.attribute.FileOwnerAttributeView fileAttributeView) {
+    return null;
+  }
+
+  public static j$.nio.file.attribute.FileOwnerAttributeView wrap_convert(
+      java.nio.file.attribute.FileOwnerAttributeView fileAttributeView) {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributeView.java b/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributeView.java
new file mode 100644
index 0000000..bb24cb7
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributeView.java
@@ -0,0 +1,17 @@
+// 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.attribute;
+
+public class PosixFileAttributeView extends FileAttributeView {
+  public static java.nio.file.attribute.PosixFileAttributeView wrap_convert(
+      j$.nio.file.attribute.PosixFileAttributeView fileAttributeView) {
+    return null;
+  }
+
+  public static j$.nio.file.attribute.PosixFileAttributeView wrap_convert(
+      java.nio.file.attribute.PosixFileAttributeView fileAttributeView) {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributes.java b/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributes.java
new file mode 100644
index 0000000..5e61814
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributes.java
@@ -0,0 +1,17 @@
+// 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.attribute;
+
+public class PosixFileAttributes extends BasicFileAttributes {
+  public static java.nio.file.attribute.PosixFileAttributes wrap_convert(
+      j$.nio.file.attribute.PosixFileAttributes fileAttributes) {
+    return null;
+  }
+
+  public static j$.nio.file.attribute.PosixFileAttributes wrap_convert(
+      java.nio.file.attribute.PosixFileAttributes fileAttributes) {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/PosixFilePermission.java b/src/library_desugar/java/j$/nio/file/attribute/PosixFilePermission.java
new file mode 100644
index 0000000..94dbcbf
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/attribute/PosixFilePermission.java
@@ -0,0 +1,17 @@
+// 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.attribute;
+
+public class PosixFilePermission {
+  public static java.nio.file.attribute.PosixFilePermission wrap_convert(
+      j$.nio.file.attribute.PosixFilePermission permission) {
+    return null;
+  }
+
+  public static j$.nio.file.attribute.PosixFilePermission wrap_convert(
+      java.nio.file.attribute.PosixFilePermission permission) {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/j$/nio/file/spi/FileSystemProvider.java b/src/library_desugar/java/j$/nio/file/spi/FileSystemProvider.java
new file mode 100644
index 0000000..38152b3
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/spi/FileSystemProvider.java
@@ -0,0 +1,12 @@
+// 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.spi;
+
+public class FileSystemProvider {
+  public static java.nio.file.spi.FileSystemProvider wrap_convert(FileSystemProvider provider) {
+    // Rewritten in ASM to the wrapper method.
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/j$/util/stream/Collector.java b/src/library_desugar/java/j$/util/stream/Collector.java
new file mode 100644
index 0000000..126d6ad
--- /dev/null
+++ b/src/library_desugar/java/j$/util/stream/Collector.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 j$.util.stream;
+
+public class Collector {
+  public enum Characteristics {
+    ;
+
+    public static java.util.stream.Collector.Characteristics wrap_convert(
+        j$.util.stream.Collector.Characteristics option) {
+      return null;
+    }
+
+    public static j$.util.stream.Collector.Characteristics wrap_convert(
+        java.util.stream.Collector.Characteristics option) {
+      return null;
+    }
+  }
+}
diff --git a/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java b/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java
index 658c351..d02344d 100644
--- a/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java
+++ b/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java
@@ -4,13 +4,12 @@
 
 package java.adapter;
 
-import android.os.Build.VERSION;
 import android.os.StrictMode;
 import android.os.StrictMode.ThreadPolicy;
 import desugar.sun.nio.fs.DesugarDefaultFileSystemProvider;
+import j$.nio.file.FileSystems;
 import java.net.URI;
 import java.nio.file.FileSystem;
-import java.nio.file.FileSystems;
 import java.nio.file.spi.FileSystemProvider;
 
 /**
@@ -23,20 +22,26 @@
       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();
+    try {
+      // On API 26 and above, FileSystems is present.
+      Class.forName("java.nio.file.FileSystems");
+      j$.nio.file.FileSystem fileSystem = FileSystems.getDefault();
+      j$.nio.file.spi.FileSystemProvider provider = fileSystem.provider();
+      return j$.nio.file.spi.FileSystemProvider.wrap_convert(provider);
+    } catch (ClassNotFoundException ignored) {
+      // We reach this path is API < 26.
     }
+    // The DesugarDefaultFileSystemProvider requires the ThreadPolicy to be set to work correctly.
+    // We cannot set the ThreadPolicy in headless and it should not matter.
+    // In headless, android.os is absent so the following line will throw.
+    // In headfull, android.os is present and we set the thread policy.
+    try {
+      Class.forName("android.os.Build");
+      setThreadPolicy();
+    } catch (ClassNotFoundException ignored) {
+      // Headless mode.
+    }
+    return DesugarDefaultFileSystemProvider.instance();
   }
 
   private static void setThreadPolicy() {
diff --git a/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java b/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java
index 1caf2f1..da93736 100644
--- a/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java
+++ b/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java
@@ -4,7 +4,6 @@
 
 package java.adapter;
 
-import android.os.Build.VERSION;
 import desugar.sun.nio.fs.DesugarDefaultFileTypeDetector;
 import java.io.IOException;
 import java.nio.file.Path;
@@ -18,18 +17,19 @@
   private HybridFileTypeDetector() {}
 
   public static FileTypeDetector create() {
-    if (VERSION.SDK_INT >= 26) {
+    try {
+      // On API 26 and above, java.nio.file.Files is present.
+      Class.forName("java.nio.file.Files");
       return new PlatformFileTypeDetector();
-    } else {
+    } catch (ClassNotFoundException ignored) {
       return DesugarDefaultFileTypeDetector.create();
     }
   }
 
-  static class PlatformFileTypeDetector extends java.nio.file.spi.FileTypeDetector {
+  static class PlatformFileTypeDetector extends FileTypeDetector {
     @Override
     public String probeContentType(Path path) throws IOException {
-      // Relies at runtime on java.nio.file.Files.
-      return j$.nio.file.Files.probeContentType(path);
+      return j$.nio.file.Files.probeContentType(j$.nio.file.Path.wrap_convert(path));
     }
   }
 }
diff --git a/src/library_desugar/java/java/nio/file/FileApiFlips.java b/src/library_desugar/java/java/nio/file/FileApiFlips.java
new file mode 100644
index 0000000..71b172a
--- /dev/null
+++ b/src/library_desugar/java/java/nio/file/FileApiFlips.java
@@ -0,0 +1,207 @@
+// 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.nio.file;
+
+import static java.util.ConversionRuntimeException.exception;
+
+import java.nio.file.attribute.FileAttributeConversions;
+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 FileApiFlips {
+
+  public static Class<?> flipFileAttributes(Class<?> attributesClass) {
+    if (attributesClass == null) {
+      return null;
+    }
+    if (attributesClass == j$.nio.file.attribute.BasicFileAttributes.class) {
+      return java.nio.file.attribute.BasicFileAttributes.class;
+    }
+    if (attributesClass == j$.nio.file.attribute.PosixFileAttributes.class) {
+      return java.nio.file.attribute.PosixFileAttributes.class;
+    }
+    if (attributesClass == java.nio.file.attribute.BasicFileAttributes.class) {
+      return j$.nio.file.attribute.BasicFileAttributes.class;
+    }
+    if (attributesClass == java.nio.file.attribute.PosixFileAttributes.class) {
+      return j$.nio.file.attribute.PosixFileAttributes.class;
+    }
+    throw exception("java.nio.file.attribute.BasicFileAttributes", attributesClass);
+  }
+
+  public static Class<?> flipFileAttributeView(Class<?> attributeView) {
+    if (attributeView == null) {
+      return null;
+    }
+    if (attributeView == j$.nio.file.attribute.BasicFileAttributeView.class) {
+      return java.nio.file.attribute.BasicFileAttributeView.class;
+    }
+    if (attributeView == j$.nio.file.attribute.PosixFileAttributeView.class) {
+      return java.nio.file.attribute.PosixFileAttributeView.class;
+    }
+    if (attributeView == j$.nio.file.attribute.FileOwnerAttributeView.class) {
+      return java.nio.file.attribute.FileOwnerAttributeView.class;
+    }
+    if (attributeView == java.nio.file.attribute.BasicFileAttributeView.class) {
+      return j$.nio.file.attribute.BasicFileAttributeView.class;
+    }
+    if (attributeView == java.nio.file.attribute.PosixFileAttributeView.class) {
+      return j$.nio.file.attribute.PosixFileAttributeView.class;
+    }
+    if (attributeView == java.nio.file.attribute.FileOwnerAttributeView.class) {
+      return j$.nio.file.attribute.FileOwnerAttributeView.class;
+    }
+    throw exception("java.nio.file.attribute.FileAttributeView", attributeView);
+  }
+
+  public static RuntimeException exceptionOpenOption(Object suffix) {
+    throw exception("java.nio.file.OpenOption", suffix);
+  }
+
+  public static Set<?> flipOpenOptionSet(Set<?> openOptionSet) {
+    if (openOptionSet == null || openOptionSet.isEmpty()) {
+      return openOptionSet;
+    }
+    HashSet<Object> convertedSet = new HashSet<>();
+    Object guineaPig = openOptionSet.iterator().next();
+    if (guineaPig instanceof java.nio.file.OpenOption) {
+      for (Object item : openOptionSet) {
+        java.nio.file.OpenOption option;
+        try {
+          option = (java.nio.file.OpenOption) item;
+        } catch (ClassCastException cce) {
+          throw exceptionOpenOption(cce);
+        }
+        convertedSet.add(OpenOptionConversions.convert(option));
+      }
+      return convertedSet;
+    }
+    if (guineaPig instanceof j$.nio.file.OpenOption) {
+      for (Object item : openOptionSet) {
+        j$.nio.file.OpenOption option;
+        try {
+          option = (j$.nio.file.OpenOption) item;
+        } catch (ClassCastException cce) {
+          throw exceptionOpenOption(cce);
+        }
+        convertedSet.add(OpenOptionConversions.convert(option));
+      }
+      return convertedSet;
+    }
+    throw exceptionOpenOption(guineaPig.getClass());
+  }
+
+  public static RuntimeException exceptionFileTime(Object suffix) {
+    throw exception("java.nio.file.attribute.FileTime", suffix);
+  }
+
+  public static Map<String, Object> flipMapWithMaybeFileTimeValues(Map<String, Object> in) {
+    if (in == null || in.isEmpty()) {
+      return in;
+    }
+    HashMap<String, Object> newMap = new HashMap<>();
+    for (String key : in.keySet()) {
+      Object val = in.get(key);
+      if (val instanceof j$.nio.file.attribute.FileTime) {
+        j$.nio.file.attribute.FileTime fileTime;
+        try {
+          fileTime = (j$.nio.file.attribute.FileTime) val;
+        } catch (ClassCastException cce) {
+          throw exceptionFileTime(cce);
+        }
+        newMap.put(key, FileAttributeConversions.convert(fileTime));
+      } else if (val instanceof java.nio.file.attribute.FileTime) {
+        java.nio.file.attribute.FileTime fileTime;
+        try {
+          fileTime = (java.nio.file.attribute.FileTime) val;
+        } catch (ClassCastException cce) {
+          throw exceptionFileTime(cce);
+        }
+        newMap.put(key, FileAttributeConversions.convert(fileTime));
+      } else {
+        newMap.put(key, val);
+      }
+    }
+    return newMap;
+  }
+
+  public static RuntimeException exceptionPosixPermission(Object suffix) {
+    throw exception("java.nio.file.attribute.PosixFilePermission", suffix);
+  }
+
+  public static Set<?> flipPosixPermissionSet(Set<?> posixPermissions) {
+    if (posixPermissions == null || posixPermissions.isEmpty()) {
+      return posixPermissions;
+    }
+    HashSet<Object> convertedSet = new HashSet<>();
+    Object guineaPig = posixPermissions.iterator().next();
+    if (guineaPig instanceof java.nio.file.attribute.PosixFilePermission) {
+      for (Object item : posixPermissions) {
+        java.nio.file.attribute.PosixFilePermission permission;
+        try {
+          permission = (java.nio.file.attribute.PosixFilePermission) item;
+        } catch (ClassCastException cce) {
+          throw exceptionPosixPermission(cce);
+        }
+        convertedSet.add(j$.nio.file.attribute.PosixFilePermission.wrap_convert(permission));
+      }
+      return convertedSet;
+    }
+    if (guineaPig instanceof j$.nio.file.attribute.PosixFilePermission) {
+      for (Object item : posixPermissions) {
+        j$.nio.file.attribute.PosixFilePermission permission;
+        try {
+          permission = (j$.nio.file.attribute.PosixFilePermission) item;
+        } catch (ClassCastException cce) {
+          throw exceptionPosixPermission(cce);
+        }
+        convertedSet.add(j$.nio.file.attribute.PosixFilePermission.wrap_convert(permission));
+      }
+      return convertedSet;
+    }
+    throw exceptionPosixPermission(guineaPig.getClass());
+  }
+
+  public static RuntimeException exceptionWatchEvent(Object suffix) {
+    throw exception("java.nio.file.WatchEvent", suffix);
+  }
+
+  public static List<?> flipWatchEventList(List<?> watchEventList) {
+    if (watchEventList == null || watchEventList.isEmpty()) {
+      return watchEventList;
+    }
+    List<Object> convertedList = new ArrayList<>();
+    Object guineaPig = watchEventList.get(0);
+    if (guineaPig instanceof java.nio.file.WatchEvent) {
+      for (Object item : watchEventList) {
+        java.nio.file.WatchEvent<?> watchEvent;
+        try {
+          watchEvent = (java.nio.file.WatchEvent<?>) item;
+        } catch (ClassCastException cce) {
+          throw exceptionWatchEvent(cce);
+        }
+        convertedList.add(j$.nio.file.WatchEvent.wrap_convert(watchEvent));
+      }
+      return convertedList;
+    }
+    if (guineaPig instanceof j$.nio.file.WatchEvent) {
+      for (Object item : watchEventList) {
+        j$.nio.file.WatchEvent<?> watchEvent;
+        try {
+          watchEvent = (j$.nio.file.WatchEvent<?>) item;
+        } catch (ClassCastException cce) {
+          throw exceptionWatchEvent(cce);
+        }
+        convertedList.add(j$.nio.file.WatchEvent.wrap_convert(watchEvent));
+      }
+      return convertedList;
+    }
+    throw exceptionWatchEvent(guineaPig.getClass());
+  }
+}
diff --git a/src/library_desugar/java/java/nio/file/OpenOptionConversions.java b/src/library_desugar/java/java/nio/file/OpenOptionConversions.java
new file mode 100644
index 0000000..bfe4968
--- /dev/null
+++ b/src/library_desugar/java/java/nio/file/OpenOptionConversions.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.nio.file;
+
+import static java.util.ConversionRuntimeException.exception;
+
+public class OpenOptionConversions {
+  public static java.nio.file.OpenOption convert(j$.nio.file.OpenOption option) {
+    if (option == null) {
+      return null;
+    }
+    if (option instanceof j$.nio.file.StandardOpenOption) {
+      return j$.nio.file.StandardOpenOption.wrap_convert((j$.nio.file.StandardOpenOption) option);
+    }
+    if (option instanceof j$.nio.file.LinkOption) {
+      return j$.nio.file.LinkOption.wrap_convert((j$.nio.file.LinkOption) option);
+    }
+    throw exception("java.nio.file.OpenOption", option);
+  }
+
+  public static j$.nio.file.OpenOption convert(java.nio.file.OpenOption option) {
+    if (option == null) {
+      return null;
+    }
+    if (option instanceof java.nio.file.StandardOpenOption) {
+      return j$.nio.file.StandardOpenOption.wrap_convert((java.nio.file.StandardOpenOption) option);
+    }
+    if (option instanceof java.nio.file.LinkOption) {
+      return j$.nio.file.LinkOption.wrap_convert((java.nio.file.LinkOption) option);
+    }
+    throw exception("java.nio.file.OpenOption", option);
+  }
+}
diff --git a/src/library_desugar/java/java/nio/file/attribute/FileAttributeConversions.java b/src/library_desugar/java/java/nio/file/attribute/FileAttributeConversions.java
index f4c4beb..332c89b 100644
--- a/src/library_desugar/java/java/nio/file/attribute/FileAttributeConversions.java
+++ b/src/library_desugar/java/java/nio/file/attribute/FileAttributeConversions.java
@@ -4,7 +4,10 @@
 
 package java.nio.file.attribute;
 
+import static java.util.ConversionRuntimeException.exception;
+
 public class FileAttributeConversions {
+
   public static java.nio.file.attribute.FileTime convert(j$.nio.file.attribute.FileTime fileTime) {
     if (fileTime == null) {
       return null;
@@ -18,4 +21,68 @@
     }
     return j$.nio.file.attribute.FileTime.fromMillis(fileTime.toMillis());
   }
+
+  public static java.nio.file.attribute.FileAttributeView convert(
+      j$.nio.file.attribute.FileAttributeView fileAttributeView) {
+    if (fileAttributeView == null) {
+      return null;
+    }
+    if (fileAttributeView instanceof j$.nio.file.attribute.PosixFileAttributeView) {
+      return j$.nio.file.attribute.PosixFileAttributeView.wrap_convert(
+          (j$.nio.file.attribute.PosixFileAttributeView) fileAttributeView);
+    }
+    if (fileAttributeView instanceof j$.nio.file.attribute.FileOwnerAttributeView) {
+      return j$.nio.file.attribute.FileOwnerAttributeView.wrap_convert(
+          (j$.nio.file.attribute.FileOwnerAttributeView) fileAttributeView);
+    }
+    if (fileAttributeView instanceof j$.nio.file.attribute.BasicFileAttributeView) {
+      return j$.nio.file.attribute.BasicFileAttributeView.wrap_convert(
+          (j$.nio.file.attribute.BasicFileAttributeView) fileAttributeView);
+    }
+    throw exception("java.nio.file.attribute.FileAttributeView", fileAttributeView);
+  }
+
+  public static j$.nio.file.attribute.FileAttributeView convert(
+      java.nio.file.attribute.FileAttributeView fileAttributeView) {
+    if (fileAttributeView == null) {
+      return null;
+    }
+    if (fileAttributeView instanceof java.nio.file.attribute.PosixFileAttributeView) {
+      return j$.nio.file.attribute.PosixFileAttributeView.wrap_convert(
+          (java.nio.file.attribute.PosixFileAttributeView) fileAttributeView);
+    }
+    if (fileAttributeView instanceof java.nio.file.attribute.FileOwnerAttributeView) {
+      return j$.nio.file.attribute.FileOwnerAttributeView.wrap_convert(
+          (java.nio.file.attribute.FileOwnerAttributeView) fileAttributeView);
+    }
+    if (fileAttributeView instanceof java.nio.file.attribute.BasicFileAttributeView) {
+      return j$.nio.file.attribute.BasicFileAttributeView.wrap_convert(
+          (java.nio.file.attribute.BasicFileAttributeView) fileAttributeView);
+    }
+    throw exception("java.nio.file.attribute.FileAttributeView", fileAttributeView);
+  }
+
+  public static java.nio.file.attribute.BasicFileAttributes convert(
+      j$.nio.file.attribute.BasicFileAttributes fileAttributes) {
+    if (fileAttributes == null) {
+      return null;
+    }
+    if (fileAttributes instanceof j$.nio.file.attribute.PosixFileAttributes) {
+      return j$.nio.file.attribute.PosixFileAttributes.wrap_convert(
+          (j$.nio.file.attribute.PosixFileAttributes) fileAttributes);
+    }
+    return j$.nio.file.attribute.BasicFileAttributes.wrap_convert(fileAttributes);
+  }
+
+  public static j$.nio.file.attribute.BasicFileAttributes convert(
+      java.nio.file.attribute.BasicFileAttributes fileAttributes) {
+    if (fileAttributes == null) {
+      return null;
+    }
+    if (fileAttributes instanceof java.nio.file.attribute.PosixFileAttributes) {
+      return j$.nio.file.attribute.PosixFileAttributes.wrap_convert(
+          (java.nio.file.attribute.PosixFileAttributes) fileAttributes);
+    }
+    return j$.nio.file.attribute.BasicFileAttributes.wrap_convert(fileAttributes);
+  }
 }
diff --git a/src/library_desugar/java/java/util/ConversionRuntimeException.java b/src/library_desugar/java/java/util/ConversionRuntimeException.java
new file mode 100644
index 0000000..896be1e
--- /dev/null
+++ b/src/library_desugar/java/java/util/ConversionRuntimeException.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 java.util;
+
+public class ConversionRuntimeException extends RuntimeException {
+
+  public ConversionRuntimeException(String message) {
+    super(message);
+  }
+
+  public static RuntimeException exception(String type, Object suffix) {
+    throw new ConversionRuntimeException("Unsupported " + type + " :" + suffix);
+  }
+}
diff --git a/src/library_desugar/java/java/util/stream/StreamApiFlips.java b/src/library_desugar/java/java/util/stream/StreamApiFlips.java
new file mode 100644
index 0000000..632c7bd
--- /dev/null
+++ b/src/library_desugar/java/java/util/stream/StreamApiFlips.java
@@ -0,0 +1,50 @@
+// 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.util.stream;
+
+import static java.util.ConversionRuntimeException.exception;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class StreamApiFlips {
+
+  public static RuntimeException exceptionCharacteristics(Object suffix) {
+    throw exception("java.util.stream.Collector.Characteristics", suffix);
+  }
+
+  public static Set<?> flipCharacteristicSet(Set<?> characteristicSet) {
+    if (characteristicSet == null || characteristicSet.isEmpty()) {
+      return characteristicSet;
+    }
+    HashSet<Object> convertedSet = new HashSet<>();
+    Object guineaPig = characteristicSet.iterator().next();
+    if (guineaPig instanceof java.util.stream.Collector.Characteristics) {
+      for (Object item : characteristicSet) {
+        java.util.stream.Collector.Characteristics characteristics;
+        try {
+          characteristics = (java.util.stream.Collector.Characteristics) item;
+        } catch (ClassCastException cce) {
+          throw exceptionCharacteristics(cce);
+        }
+        convertedSet.add(j$.util.stream.Collector.Characteristics.wrap_convert(characteristics));
+      }
+      return convertedSet;
+    }
+    if (guineaPig instanceof j$.util.stream.Collector.Characteristics) {
+      for (Object item : characteristicSet) {
+        j$.util.stream.Collector.Characteristics characteristics;
+        try {
+          characteristics = (j$.util.stream.Collector.Characteristics) item;
+        } catch (ClassCastException cce) {
+          throw exceptionCharacteristics(cce);
+        }
+        convertedSet.add(j$.util.stream.Collector.Characteristics.wrap_convert(characteristics));
+      }
+      return convertedSet;
+    }
+    throw exceptionCharacteristics(guineaPig.getClass());
+  }
+}
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs.json b/src/library_desugar/jdk11/desugar_jdk_libs.json
index ef9b604..c764f31 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs.json
@@ -95,56 +95,24 @@
         "java.util.stream.Stream java.io.BufferedReader#lines()": "java.io.DesugarBufferedReader",
         "java.util.Spliterator java.util.LinkedHashSet#spliterator()": "java.util.DesugarLinkedHashSet"
       },
+      "api_generic_types_conversion": {
+        "java.util.Set java.util.stream.Collector#characteristics()" : [-1, "java.util.Set java.util.stream.StreamApiFlips#flipCharacteristicSet(java.util.Set)"]
+      },
       "wrapper_conversion": [
-        "java.util.function.IntUnaryOperator",
-        "java.util.function.BiFunction",
-        "java.util.function.IntConsumer",
-        "java.util.function.IntBinaryOperator",
-        "java.util.function.UnaryOperator",
-        "java.util.function.DoubleConsumer",
-        "java.util.function.IntPredicate",
-        "java.util.Spliterator$OfLong",
-        "java.util.stream.Collector",
-        "java.util.function.LongPredicate",
-        "java.util.function.ToLongFunction",
-        "java.util.function.LongToDoubleFunction",
-        "java.util.PrimitiveIterator$OfInt",
-        "java.util.function.LongToIntFunction",
-        "java.util.function.Predicate",
-        "java.util.Spliterator$OfPrimitive",
-        "java.util.function.DoubleToIntFunction",
-        "java.util.function.ObjDoubleConsumer",
-        "java.util.function.BinaryOperator",
-        "java.util.stream.DoubleStream",
-        "java.util.Spliterator$OfInt",
-        "java.util.stream.Stream",
-        "java.util.function.ObjLongConsumer",
-        "java.util.function.ToDoubleFunction",
-        "java.util.stream.IntStream",
-        "java.util.function.LongBinaryOperator",
-        "java.util.Spliterator$OfDouble",
-        "java.util.function.DoubleFunction",
-        "java.util.function.ObjIntConsumer",
-        "java.util.function.Function",
-        "java.util.function.Supplier",
-        "java.util.function.DoubleUnaryOperator",
-        "java.util.function.BiPredicate",
         "java.util.PrimitiveIterator$OfDouble",
-        "java.util.function.DoubleBinaryOperator",
+        "java.util.PrimitiveIterator$OfInt",
         "java.util.PrimitiveIterator$OfLong",
-        "java.util.function.BiConsumer",
-        "java.util.function.IntFunction",
-        "java.util.stream.LongStream",
-        "java.util.function.IntToDoubleFunction",
-        "java.util.function.LongFunction",
-        "java.util.function.ToIntFunction",
-        "java.util.function.LongConsumer",
-        "java.util.function.Consumer",
-        "java.util.function.IntToLongFunction",
-        "java.util.function.DoubleToLongFunction",
-        "java.util.function.LongUnaryOperator",
+        "java.util.Spliterator$OfDouble",
+        "java.util.Spliterator$OfInt",
+        "java.util.Spliterator$OfLong",
+        "java.util.Spliterator$OfPrimitive",
         "java.util.stream.BaseStream",
-        "java.util.function.DoublePredicate"
+        "java.util.stream.Collector",
+        "java.util.stream.Collector$Characteristics",
+        "java.util.stream.DoubleStream",
+        "java.util.stream.IntStream",
+        "java.util.stream.LongStream",
+        "java.util.stream.Stream"
       ],
       "wrapper_conversion_excluding": {
         "java.util.Spliterator": [
@@ -242,7 +210,8 @@
         "java.util.KeyValueHolder": "j$.util.KeyValueHolder",
         "java.util.SortedSet$1": "j$.util.SortedSet$1",
         "java.util.Tripwire": "j$.util.Tripwire",
-        "java.util.concurrent.Helpers": "j$.util.concurrent.Helpers"
+        "java.util.concurrent.Helpers": "j$.util.concurrent.Helpers",
+        "java.util.ConversionRuntimeException": "j$.util.ConversionRuntimeException"
       },
       "rewrite_derived_prefix": {
         "java.util.DoubleSummaryStatistics": {
@@ -254,6 +223,9 @@
         "java.util.LongSummaryStatistics": {
           "j$.util.LongSummaryStatistics": "java.util.LongSummaryStatistics"
         },
+        "java.util.stream.Collector": {
+          "j$.util.stream.Collector": "java.util.stream.Collector"
+        },
         "java.util.Optional": {
           "j$.util.Optional": "java.util.Optional"
         }
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_path.json b/src/library_desugar/jdk11/desugar_jdk_libs_path.json
index 4f0ec65..c09fa82 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_path.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_path.json
@@ -105,11 +105,16 @@
       "retarget_method_with_emulated_dispatch": {
         "java.nio.file.Path java.io.File#toPath()": "java.io.DesugarFile"
       },
-      "api_conversion_collection": {
-        "java.nio.channels.AsynchronousFileChannel java.nio.file.spi.FileSystemProvider#newAsynchronousFileChannel(java.nio.file.Path, java.util.Set, java.util.concurrent.ExecutorService, java.nio.file.attribute.FileAttribute[])" : [1, "OpenOption"],
-        "java.nio.channels.SeekableByteChannel java.nio.file.spi.FileSystemProvider#newByteChannel(java.nio.file.Path, java.util.Set, java.nio.file.attribute.FileAttribute[])" : [1, "OpenOption"],
-        "java.nio.channels.FileChannel java.nio.file.spi.FileSystemProvider#newFileChannel(java.nio.file.Path, java.util.Set, java.nio.file.attribute.FileAttribute[])" : [1, "OpenOption"],
-        "java.util.List java.nio.file.WatchKey#pollEvents()": [-1, "java.nio.file.WatchEvent"]
+      "api_generic_types_conversion": {
+        "java.nio.channels.AsynchronousFileChannel java.nio.file.spi.FileSystemProvider#newAsynchronousFileChannel(java.nio.file.Path, java.util.Set, java.util.concurrent.ExecutorService, java.nio.file.attribute.FileAttribute[])" : [1, "java.util.Set java.nio.file.FileApiFlips#flipOpenOptionSet(java.util.Set)"],
+        "java.nio.channels.SeekableByteChannel java.nio.file.spi.FileSystemProvider#newByteChannel(java.nio.file.Path, java.util.Set, java.nio.file.attribute.FileAttribute[])" : [1, "java.util.Set java.nio.file.FileApiFlips#flipOpenOptionSet(java.util.Set)"],
+        "java.nio.channels.FileChannel java.nio.file.spi.FileSystemProvider#newFileChannel(java.nio.file.Path, java.util.Set, java.nio.file.attribute.FileAttribute[])" : [1, "java.util.Set java.nio.file.FileApiFlips#flipOpenOptionSet(java.util.Set)"],
+        "java.util.List java.nio.file.WatchKey#pollEvents()": [-1, "java.util.List java.nio.file.FileApiFlips#flipWatchEventList(java.util.List)"],
+        "java.nio.file.attribute.FileAttributeView java.nio.file.spi.FileSystemProvider#getFileAttributeView(java.nio.file.Path, java.lang.Class, java.nio.file.LinkOption[])": [1, "java.lang.Class java.nio.file.FileApiFlips#flipFileAttributeView(java.lang.Class)"],
+        "java.nio.file.attribute.BasicFileAttributes java.nio.file.spi.FileSystemProvider#readAttributes(java.nio.file.Path, java.lang.Class, java.nio.file.LinkOption[])": [1, "java.lang.Class java.nio.file.FileApiFlips#flipFileAttributes(java.lang.Class)"],
+        "java.util.Set java.nio.file.attribute.PosixFileAttributes#permissions()": [-1, "java.util.Set java.nio.file.FileApiFlips#flipPosixPermissionSet(java.util.Set)"],
+        "void java.nio.file.attribute.PosixFileAttributeView#setPermissions(java.util.Set)": [0, "java.util.Set java.nio.file.FileApiFlips#flipPosixPermissionSet(java.util.Set)"],
+        "java.util.Map java.nio.file.spi.FileSystemProvider#readAttributes(java.nio.file.Path, java.lang.String, java.nio.file.LinkOption[])" : [-1, "java.util.Map java.nio.file.FileApiFlips#flipMapWithMaybeFileTimeValues(java.util.Map)"]
       },
       "wrapper_conversion": [
         "java.nio.channels.AsynchronousChannel",
@@ -117,6 +122,7 @@
         "java.nio.file.Path",
         "java.nio.file.FileSystem",
         "java.nio.file.WatchService",
+        "java.nio.file.WatchEvent",
         "java.nio.file.WatchEvent$Kind",
         "java.nio.file.WatchKey",
         "java.nio.file.Watchable",
@@ -124,19 +130,24 @@
         "java.nio.file.WatchEvent$Modifier",
         "java.nio.file.attribute.UserPrincipalLookupService",
         "java.nio.file.spi.FileSystemProvider",
+        "java.nio.file.spi.FileTypeDetector",
         "java.nio.file.AccessMode",
+        "java.nio.file.StandardOpenOption",
         "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"
+        "java.nio.file.attribute.PosixFilePermission",
+        "java.nio.file.attribute.BasicFileAttributes",
+        "java.nio.file.attribute.PosixFileAttributes",
+        "java.nio.file.attribute.FileOwnerAttributeView",
+        "java.nio.file.attribute.PosixFileAttributeView",
+        "java.nio.file.attribute.BasicFileAttributeView"
       ],
       "wrapper_conversion_excluding": {
         "java.nio.channels.AsynchronousFileChannel": [
@@ -146,7 +157,10 @@
         ]
       },
       "custom_conversion": {
-        "java.nio.file.attribute.FileTime": "java.nio.file.attribute.FileAttributeConversions"
+        "java.nio.file.OpenOption": "java.nio.file.OpenOptionConversions",
+        "java.nio.file.attribute.FileTime": "java.nio.file.attribute.FileAttributeConversions",
+        "java.nio.file.attribute.BasicFileAttributes": "java.nio.file.attribute.FileAttributeConversions",
+        "java.nio.file.attribute.FileAttributeView": "java.nio.file.attribute.FileAttributeConversions"
       }
     },
     {
@@ -219,57 +233,25 @@
         "java.util.stream.Stream java.io.BufferedReader#lines()": "java.io.DesugarBufferedReader",
         "java.util.Spliterator java.util.LinkedHashSet#spliterator()": "java.util.DesugarLinkedHashSet"
       },
+      "api_generic_conversion": {
+        "java.util.Set java.util.stream.Collector#characteristics()" : [-1, "java.util.Set java.util.stream.StreamApiFlips#flipCharacteristicSet(java.util.Set)"]
+      },
       "wrapper_conversion": [
         "java.nio.channels.SeekableByteChannel",
-        "java.util.function.IntUnaryOperator",
-        "java.util.function.BiFunction",
-        "java.util.function.IntConsumer",
-        "java.util.function.IntBinaryOperator",
-        "java.util.function.UnaryOperator",
-        "java.util.function.DoubleConsumer",
-        "java.util.function.IntPredicate",
-        "java.util.Spliterator$OfLong",
-        "java.util.stream.Collector",
-        "java.util.function.LongPredicate",
-        "java.util.function.ToLongFunction",
-        "java.util.function.LongToDoubleFunction",
-        "java.util.PrimitiveIterator$OfInt",
-        "java.util.function.LongToIntFunction",
-        "java.util.function.Predicate",
-        "java.util.Spliterator$OfPrimitive",
-        "java.util.function.DoubleToIntFunction",
-        "java.util.function.ObjDoubleConsumer",
-        "java.util.function.BinaryOperator",
-        "java.util.stream.DoubleStream",
-        "java.util.Spliterator$OfInt",
-        "java.util.stream.Stream",
-        "java.util.function.ObjLongConsumer",
-        "java.util.function.ToDoubleFunction",
-        "java.util.stream.IntStream",
-        "java.util.function.LongBinaryOperator",
-        "java.util.Spliterator$OfDouble",
-        "java.util.function.DoubleFunction",
-        "java.util.function.ObjIntConsumer",
-        "java.util.function.Function",
-        "java.util.function.Supplier",
-        "java.util.function.DoubleUnaryOperator",
-        "java.util.function.BiPredicate",
         "java.util.PrimitiveIterator$OfDouble",
-        "java.util.function.DoubleBinaryOperator",
+        "java.util.PrimitiveIterator$OfInt",
         "java.util.PrimitiveIterator$OfLong",
-        "java.util.function.BiConsumer",
-        "java.util.function.IntFunction",
-        "java.util.stream.LongStream",
-        "java.util.function.IntToDoubleFunction",
-        "java.util.function.LongFunction",
-        "java.util.function.ToIntFunction",
-        "java.util.function.LongConsumer",
-        "java.util.function.Consumer",
-        "java.util.function.IntToLongFunction",
-        "java.util.function.DoubleToLongFunction",
-        "java.util.function.LongUnaryOperator",
+        "java.util.Spliterator$OfDouble",
+        "java.util.Spliterator$OfInt",
+        "java.util.Spliterator$OfLong",
+        "java.util.Spliterator$OfPrimitive",
         "java.util.stream.BaseStream",
-        "java.util.function.DoublePredicate"
+        "java.util.stream.Collector",
+        "java.util.stream.Collector$Characteristics",
+        "java.util.stream.DoubleStream",
+        "java.util.stream.IntStream",
+        "java.util.stream.LongStream",
+        "java.util.stream.Stream"
       ],
       "wrapper_conversion_excluding": {
         "java.util.Spliterator": [
@@ -395,14 +377,33 @@
         "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",
-        "java.adapter" : "j$.adapter"
+        "java.adapter" : "j$.adapter",
+        "java.util.ConversionRuntimeException": "j$.util.ConversionRuntimeException"
       },
       "rewrite_derived_prefix": {
-        "java.nio.file.attribute.FileTime": {
-          "j$.nio.file.attribute.FileTime": "java.nio.file.attribute.FileTime"
+        "java.nio.file.attribute.": {
+          "j$.nio.file.attribute.": "java.nio.file.attribute."
+        },
+        "java.nio.file.OpenOption": {
+          "j$.nio.file.OpenOption": "java.nio.file.OpenOption"
+        },
+        "java.nio.file.StandardOpenOption": {
+          "j$.nio.file.StandardOpenOption": "java.nio.file.StandardOpenOption"
+        },
+        "java.nio.file.LinkOption": {
+          "j$.nio.file.LinkOption": "java.nio.file.LinkOption"
         },
         "java.nio.file.Files": {
           "j$.nio.file.Files": "java.nio.file.Files"
+        },
+        "java.nio.file.FileSystem": {
+          "j$.nio.file.FileSystem": "java.nio.file.FileSystem"
+        },
+        "java.nio.file.spi.FileSystemProvider": {
+          "j$.nio.file.spi.FileSystemProvider": "java.nio.file.spi.FileSystemProvider"
+        },
+        "java.nio.file.Path": {
+          "j$.nio.file.Path": "java.nio.file.Path"
         }
       },
       "retarget_method": {
@@ -441,6 +442,9 @@
         "java.util.LongSummaryStatistics": {
           "j$.util.LongSummaryStatistics": "java.util.LongSummaryStatistics"
         },
+        "java.util.stream.Collector": {
+          "j$.util.stream.Collector": "java.util.stream.Collector"
+        },
         "java.util.Optional": {
           "j$.util.Optional": "java.util.Optional"
         }
diff --git a/src/main/java/com/android/tools/r8/ApiLevelException.java b/src/main/java/com/android/tools/r8/ApiLevelException.java
deleted file mode 100644
index d58f0ec..0000000
--- a/src/main/java/com/android/tools/r8/ApiLevelException.java
+++ /dev/null
@@ -1,31 +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;
-
-import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-/** Exception to signal features that are not supported until a given API level. */
-public class ApiLevelException extends CompilationError {
-
-  public ApiLevelException(
-      AndroidApiLevel minApiLevel, String unsupportedFeatures, String sourceString) {
-    super(makeMessage(minApiLevel, unsupportedFeatures, sourceString));
-    assert minApiLevel != null;
-    assert unsupportedFeatures != null;
-  }
-
-  public static String makeMessage(
-      AndroidApiLevel minApiLevel, String unsupportedFeatures, String sourceString) {
-    String message =
-        unsupportedFeatures
-            + " are only supported starting with "
-            + minApiLevel.getName()
-            + " (--min-api "
-            + minApiLevel.getLevel()
-            + ")";
-    message = (sourceString != null) ? message + ": " + sourceString : message;
-    return message;
-  }
-}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 203b1d6..7ea383c 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -374,6 +374,11 @@
       if (hasDesugaredLibraryConfiguration() && getDisableDesugaring()) {
         reporter.error("Using desugared library configuration requires desugaring to be enabled");
       }
+      if (getProgramConsumer() instanceof ClassFileConsumer
+          && getDisableDesugaring()
+          && isMinApiLevelSet()) {
+        reporter.error("Compiling to CF with --min-api and --no-desugaring is not supported");
+      }
       super.validate();
     }
 
@@ -397,12 +402,18 @@
             new InternalGlobalSyntheticsProgramProvider(globalSyntheticsResourceProviders));
       }
 
+      // If compiling to CF with --no-desugaring then the target API is B for consistency with R8.
+      int minApiLevel =
+          getProgramConsumer() instanceof ClassFileConsumer && getDisableDesugaring()
+              ? AndroidApiLevel.B.getLevel()
+              : getMinApiLevel();
+
       return new D8Command(
           getAppBuilder().build(),
           getMode(),
           getProgramConsumer(),
           getMainDexListConsumer(),
-          getMinApiLevel(),
+          minApiLevel,
           getReporter(),
           getDesugaringState(),
           intermediate,
@@ -576,8 +587,7 @@
     internal.debug = getMode() == CompilationMode.DEBUG;
     internal.programConsumer = getProgramConsumer();
     if (internal.isGeneratingClassFiles()) {
-      internal.cfToCfDesugar = true;
-      // Turn off switch optimizations when desugaring to class file format.
+      // Turn off switch optimizations when generating class files.
       assert internal.enableSwitchRewriting;
       internal.enableSwitchRewriting = false;
       assert internal.enableStringSwitchConversion;
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index dcd8595..4f5b985 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.ParseFlagInfoImpl.flag1;
+
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
@@ -34,6 +36,7 @@
           "--main-dex-list",
           "--main-dex-list-output",
           "--desugared-lib",
+          "--desugared-lib-pg-conf-output",
           THREAD_COUNT_FLAG);
 
   public static List<ParseFlagInfo> getFlags() {
@@ -64,6 +67,11 @@
                 "Synthetic classes are with their originating class."))
         .add(ParseFlagInfoImpl.flag0("--no-desugaring", "Force disable desugaring."))
         .add(ParseFlagInfoImpl.getDesugaredLib())
+        .add(
+            flag1(
+                "--desugared-lib-pg-conf-output",
+                "<file>",
+                "Output the Proguard configuration for L8 to <file>."))
         .add(ParseFlagInfoImpl.getMainDexRules())
         .add(ParseFlagInfoImpl.getMainDexList())
         .add(ParseFlagInfoImpl.getMainDexListOutput())
@@ -291,6 +299,9 @@
         builder.setDisableDesugaring(true);
       } else if (arg.equals("--desugared-lib")) {
         builder.addDesugaredLibraryConfiguration(StringResource.fromFile(Paths.get(nextArg)));
+      } else if (arg.equals("--desugared-lib-pg-conf-output")) {
+        StringConsumer consumer = new StringConsumer.FileConsumer(Paths.get(nextArg));
+        builder.setDesugaredLibraryKeepRuleConsumer(consumer);
       } else if (arg.startsWith("--")) {
         if (tryParseAssertionArgument(builder, arg, origin)) {
           continue;
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index f8d95ab..1bb6dc5 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -90,13 +90,12 @@
       ExecutorService executorService)
       throws CompilationFailedException {
     try {
-      assert !options.cfToCfDesugar;
       ExceptionUtils.withD8CompilationHandler(
           options.reporter,
           () -> {
             // Desugar to class file format and turn off switch optimizations, as the final
             // compilation with D8 or R8 will do that.
-            options.cfToCfDesugar = true;
+            assert options.isCfDesugaring();
             assert !options.forceAnnotateSynthetics;
             options.forceAnnotateSynthetics = true;
             assert options.enableSwitchRewriting;
@@ -106,12 +105,10 @@
 
             desugar(app, options, executorService);
 
-            options.cfToCfDesugar = false;
             options.forceAnnotateSynthetics = false;
             options.enableSwitchRewriting = true;
             options.enableStringSwitchConversion = true;
           });
-      assert !options.cfToCfDesugar;
       if (shrink) {
         R8.run(r8Command, executorService);
       } else if (d8Command != null) {
@@ -125,7 +122,7 @@
   private static void desugar(
       AndroidApp inputApp, InternalOptions options, ExecutorService executor) throws IOException {
     Timing timing = Timing.create("L8 desugaring", options);
-    assert options.cfToCfDesugar;
+    assert options.isCfDesugaring();
     try {
       // Since L8 Cf representation is temporary, just disable long running back-end optimizations
       // on it.
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 2fd907e..2145f96 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -72,6 +72,7 @@
 import com.android.tools.r8.optimize.MemberRebindingIdentityLensFactory;
 import com.android.tools.r8.optimize.VisibilityBridgeRemover;
 import com.android.tools.r8.optimize.bridgehoisting.BridgeHoisting;
+import com.android.tools.r8.optimize.interfaces.analysis.CfOpenClosedInterfacesAnalysis;
 import com.android.tools.r8.optimize.proto.ProtoNormalizer;
 import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.repackaging.Repackaging;
@@ -441,6 +442,8 @@
       assert appView.appInfo().hasLiveness();
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
 
+      assert new CfOpenClosedInterfacesAnalysis(appViewWithLiveness).run(executorService);
+
       new StartupInstrumentation(appView).instrumentAllClasses(executorService);
 
       assert verifyNoJarApplicationReaders(appView.appInfo().classes());
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 309a475..7923102 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -1013,6 +1013,8 @@
     //  have a kept subclass, in which case 'm' would leak into the public API.
     if (internal.isGeneratingClassFiles()) {
       horizontalClassMergerOptions.disable();
+      // R8 CF output does not support desugaring so disable it.
+      internal.desugarState = DesugarState.OFF;
     }
 
     // EXPERIMENTAL flags.
diff --git a/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java b/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java
index a2e9e87..3ff4dcb 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.androidapi;
 
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.structural.Equatable;
 import java.util.Objects;
 
@@ -70,6 +71,10 @@
     return this.equals(other);
   }
 
+  OptionalBool isLessThanOrEqualTo(AndroidApiLevel other);
+
+  OptionalBool isLessThanOrEqualTo(ComputedApiLevel other);
+
   class NotSetApiLevel implements ComputedApiLevel {
 
     private static final NotSetApiLevel INSTANCE = new NotSetApiLevel();
@@ -77,6 +82,18 @@
     private NotSetApiLevel() {}
 
     @Override
+    public OptionalBool isLessThanOrEqualTo(AndroidApiLevel other) {
+      assert false : "Cannot compute relationship for not set";
+      return OptionalBool.unknown();
+    }
+
+    @Override
+    public OptionalBool isLessThanOrEqualTo(ComputedApiLevel other) {
+      assert false : "Cannot compute relationship for not set";
+      return OptionalBool.unknown();
+    }
+
+    @Override
     public boolean isNotSetApiLevel() {
       return true;
     }
@@ -99,6 +116,16 @@
     private UnknownApiLevel() {}
 
     @Override
+    public OptionalBool isLessThanOrEqualTo(AndroidApiLevel other) {
+      return OptionalBool.unknown();
+    }
+
+    @Override
+    public OptionalBool isLessThanOrEqualTo(ComputedApiLevel other) {
+      return OptionalBool.unknown();
+    }
+
+    @Override
     public boolean isUnknownApiLevel() {
       return true;
     }
@@ -145,6 +172,20 @@
     }
 
     @Override
+    public OptionalBool isLessThanOrEqualTo(AndroidApiLevel other) {
+      return OptionalBool.of(apiLevel.isLessThanOrEqualTo(other));
+    }
+
+    @Override
+    public OptionalBool isLessThanOrEqualTo(ComputedApiLevel other) {
+      if (other.isKnownApiLevel()) {
+        return isLessThanOrEqualTo(other.asKnownApiLevel().getApiLevel());
+      }
+      assert other.isUnknownApiLevel() : "Cannot compute relationship for not set";
+      return OptionalBool.unknown();
+    }
+
+    @Override
     public String toString() {
       return apiLevel.toString();
     }
diff --git a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
index d2c931e..d1b5e9e 100644
--- a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
@@ -536,7 +536,14 @@
     } else if (frameType.isUninitializedNew()) {
       return frameTypeType() + ".uninitializedNew(new " + cfType("CfLabel") + "())";
     } else if (frameType.isPrimitive()) {
-      return frameTypeType() + "." + frameType.asPrimitive().getTypeName() + "Type()";
+      if (frameType.isWidePrimitiveHigh()) {
+        return frameTypeType()
+            + "."
+            + frameType.asWidePrimitive().getLowType().getTypeName()
+            + "HighType()";
+      } else {
+        return frameTypeType() + "." + frameType.asPrimitive().getTypeName() + "Type()";
+      }
     } else {
       assert frameType.isInitialized();
       if (frameType.isNullType()) {
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 0c4a184..c3dac32 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -456,14 +456,12 @@
   }
 
   private void print(FrameType type) {
-    if (type.isPrimitive()) {
-      builder.append(type.asPrimitive().getTypeName());
-    } else if (type.isInitialized()) {
+    if (type.isInitializedReferenceType()) {
       appendType(type.asInitializedReferenceType().getInitializedType());
     } else if (type.isUninitializedNew()) {
       builder.append("uninitialized ").append(getLabel(type.getUninitializedLabel()));
     } else {
-      builder.append(type.toString());
+      builder.append(type);
     }
   }
 
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 3ed5c3a..2c2c461 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
@@ -205,16 +205,12 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState state,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState state, AppView<?> appView, CfAnalysisConfig config) {
     // ..., value1, value2 →
     // ..., result
     return state
-        .popInitialized(appView, type)
-        .popInitialized(appView, type)
+        .popInitialized(appView, config, type)
+        .popInitialized(appView, config, type)
         .push(appView, config, type);
   }
 }
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 deb3f3b..6562ee0 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
@@ -81,13 +81,9 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., arrayref →
     // ..., length
-    return frame.popArray(appView).push(config, dexItemFactory.intType);
+    return frame.popArray(appView).push(config, appView.dexItemFactory().intType);
   }
 }
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 2d719fd..0bae2fd 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
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -101,25 +102,27 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., arrayref, index →
     // ..., value
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
     return frame
-        .popInitialized(appView, dexItemFactory.intType)
+        .popInitialized(appView, config, dexItemFactory.intType)
         .popInitialized(
             appView,
+            config,
             getExpectedArrayType(dexItemFactory),
-            (state, head) ->
-                head.isNullType()
-                    ? state.push(appView, config, getType())
-                    : state.push(
-                        config,
-                        head.asInitializedReferenceType()
-                            .getInitializedType()
-                            .toArrayElementType(dexItemFactory)));
+            (state, head) -> {
+              if (head.isNullType()) {
+                return getType() == MemberType.OBJECT
+                    ? state.push(config, FrameType.initialized(DexItemFactory.nullValueType))
+                    : state.push(appView, config, getType());
+              }
+              return state.push(
+                  config,
+                  head.asInitializedReferenceType()
+                      .getInitializedType()
+                      .toArrayElementType(dexItemFactory));
+            });
   }
 }
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 9435e85..918203f 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
@@ -92,16 +92,13 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., arrayref, index, value →
     // ...
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
     return frame
-        .popInitialized(appView, getType())
-        .popInitialized(appView, dexItemFactory.intType)
-        .popInitialized(appView, getExpectedArrayType(dexItemFactory));
+        .popInitialized(appView, config, getType())
+        .popInitialized(appView, config, dexItemFactory.intType)
+        .popInitialized(appView, config, getExpectedArrayType(dexItemFactory));
   }
 }
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
index 52854e4..5def6cf 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java
@@ -20,19 +20,25 @@
 
 public class CfAssignability {
 
-  public static boolean isFrameTypeAssignable(
-      FrameType source, FrameType target, AppView<?> appView) {
+  final AppView<?> appView;
+  final DexItemFactory dexItemFactory;
+
+  public CfAssignability(AppView<?> appView) {
+    this.appView = appView;
+    this.dexItemFactory = appView.dexItemFactory();
+  }
+
+  public boolean isFrameTypeAssignable(FrameType source, FrameType target) {
     if (source.isSingle() != target.isSingle()) {
       return false;
     }
     return source.isSingle()
-        ? isFrameTypeAssignable(source.asSingle(), target.asSingle(), appView)
+        ? isFrameTypeAssignable(source.asSingle(), target.asSingle())
         : isFrameTypeAssignable(source.asWide(), target.asWide());
   }
 
   // Based on https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.2.
-  public static boolean isFrameTypeAssignable(
-      SingleFrameType source, SingleFrameType target, AppView<?> appView) {
+  public boolean isFrameTypeAssignable(SingleFrameType source, SingleFrameType target) {
     if (source.equals(target) || target.isOneWord()) {
       return true;
     }
@@ -48,7 +54,6 @@
           || uninitializedNewTypeSource == uninitializedNewTypeTarget;
     }
     // TODO(b/168190267): Clean-up the lattice.
-    DexItemFactory factory = appView.dexItemFactory();
     if (target.isPrimitive()) {
       return source.isPrimitive()
           && source.asSinglePrimitive().hasIntVerificationType()
@@ -62,25 +67,23 @@
         // Both are instantiated types and we resort to primitive type/java type hierarchy checking.
         return isAssignable(
             source.asInitializedReferenceType().getInitializedType(),
-            target.asInitializedReferenceType().getInitializedType(),
-            appView);
+            target.asInitializedReferenceType().getInitializedType());
       }
-      return target.asInitializedReferenceType().getInitializedType() == factory.objectType;
+      return target.asInitializedReferenceType().getInitializedType() == dexItemFactory.objectType;
     }
     return false;
   }
 
-  public static boolean isFrameTypeAssignable(WideFrameType source, WideFrameType target) {
+  public boolean isFrameTypeAssignable(WideFrameType source, WideFrameType target) {
     assert !source.isTwoWord();
     return source.lessThanOrEqualTo(target);
   }
 
   // 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) {
+  public boolean isAssignable(DexType source, DexType target) {
     assert !target.isNullValueType();
-    DexItemFactory factory = appView.dexItemFactory();
-    source = byteCharShortOrBooleanToInt(source, factory);
-    target = byteCharShortOrBooleanToInt(target, factory);
+    source = byteCharShortOrBooleanToInt(source, dexItemFactory);
+    target = byteCharShortOrBooleanToInt(target, dexItemFactory);
     if (source == target) {
       return true;
     }
@@ -90,7 +93,7 @@
     // Both are now references - everything is assignable to object.
     assert source.isReferenceType();
     assert target.isReferenceType();
-    if (target == factory.objectType) {
+    if (target == dexItemFactory.objectType) {
       return true;
     }
     // isAssignable(null, class(_, _)).
@@ -101,21 +104,24 @@
     if (target.isArrayType()) {
       return source.isArrayType()
           && isAssignable(
-              source.toArrayElementType(factory), target.toArrayElementType(factory), appView);
+              source.toArrayElementType(dexItemFactory), target.toArrayElementType(dexItemFactory));
     }
     assert target.isClassType();
     if (source.isArrayType()) {
       // Array types are assignable to the class types Object, Cloneable and Serializable.
       // Object is handled above, so we only need to check the other two.
-      return target == factory.cloneableType || target == factory.serializableType;
+      return target == dexItemFactory.cloneableType || target == dexItemFactory.serializableType;
     }
     assert source.isClassType();
-    // TODO(b/166570659): Do a sub-type check that allows for missing classes in hierarchy.
+    return internalIsClassTypeAssignableToClassType(source, target);
+  }
+
+  boolean internalIsClassTypeAssignableToClassType(DexType source, DexType target) {
     return true;
   }
 
-  public static boolean isAssignable(DexType source, ValueType target, AppView<?> appView) {
-    return isAssignable(source, target.toDexType(appView.dexItemFactory()), appView);
+  public boolean isAssignable(DexType source, ValueType target) {
+    return isAssignable(source, target.toDexType(dexItemFactory));
   }
 
   private static DexType byteCharShortOrBooleanToInt(DexType type, DexItemFactory factory) {
@@ -131,19 +137,13 @@
         || type.isShortType();
   }
 
-  public static AssignabilityResult isFrameAssignable(
-      CfFrame source, CfFrame target, AppView<?> appView) {
-    AssignabilityResult result =
-        isLocalsAssignable(source.getLocals(), target.getLocals(), appView);
-    return result.isSuccessful()
-        ? isStackAssignable(source.getStack(), target.getStack(), appView)
-        : result;
+  public AssignabilityResult isFrameAssignable(CfFrame source, CfFrame target) {
+    AssignabilityResult result = isLocalsAssignable(source.getLocals(), target.getLocals());
+    return result.isSuccessful() ? isStackAssignable(source.getStack(), target.getStack()) : result;
   }
 
-  public static AssignabilityResult isLocalsAssignable(
-      Int2ObjectSortedMap<FrameType> sourceLocals,
-      Int2ObjectSortedMap<FrameType> targetLocals,
-      AppView<?> appView) {
+  public AssignabilityResult isLocalsAssignable(
+      Int2ObjectSortedMap<FrameType> sourceLocals, Int2ObjectSortedMap<FrameType> targetLocals) {
     // 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 = targetLocals.isEmpty() ? -1 : targetLocals.lastIntKey();
@@ -162,7 +162,7 @@
       if (sourceType.isWide() && destinationType.isOneWord()) {
         destinationType = FrameType.twoWord();
       }
-      if (!isFrameTypeAssignable(sourceType, destinationType, appView)) {
+      if (!isFrameTypeAssignable(sourceType, destinationType)) {
         return new FailedAssignabilityResult(
             "Could not assign '"
                 + MapUtils.toString(sourceLocals)
@@ -180,10 +180,8 @@
     return new SuccessfulAssignabilityResult();
   }
 
-  public static AssignabilityResult isStackAssignable(
-      Deque<PreciseFrameType> sourceStack,
-      Deque<PreciseFrameType> targetStack,
-      AppView<?> appView) {
+  public AssignabilityResult isStackAssignable(
+      Deque<PreciseFrameType> sourceStack, Deque<PreciseFrameType> targetStack) {
     if (sourceStack.size() != targetStack.size()) {
       return new FailedAssignabilityResult(
           "Source stack "
@@ -196,7 +194,7 @@
     int stackIndex = 0;
     for (PreciseFrameType sourceType : sourceStack) {
       PreciseFrameType destinationType = otherIterator.next();
-      if (!isFrameTypeAssignable(sourceType, destinationType, appView)) {
+      if (!isFrameTypeAssignable(sourceType, destinationType)) {
         return new FailedAssignabilityResult(
             "Could not assign '"
                 + Arrays.toString(sourceStack.toArray())
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 8d7fe37..a45285b 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
@@ -132,13 +132,10 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., objectref →
     // ..., objectref
-    return frame.popInitialized(appView, dexItemFactory.objectType).push(config, type);
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    return frame.popInitialized(appView, config, dexItemFactory.objectType).push(config, type);
   }
 }
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 c9edfea..4486ac5 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
@@ -130,16 +130,12 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., value1, value2 →
     // ..., result
     return frame
-        .popInitialized(appView, type)
-        .popInitialized(appView, type)
-        .push(config, dexItemFactory.intType);
+        .popInitialized(appView, config, type)
+        .popInitialized(appView, config, type)
+        .push(config, appView.dexItemFactory().intType);
   }
 }
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 b76a1f3..b61202a 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
@@ -139,13 +139,9 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., value
-    return frame.push(config, dexItemFactory.classType);
+    return frame.push(config, appView.dexItemFactory().classType);
   }
 }
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 48a6368..d52f83c 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
@@ -227,13 +227,9 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., value
-    return frame.push(config, dexItemFactory.classType);
+    return frame.push(config, appView.dexItemFactory().classType);
   }
 }
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 0932c43..c58e97a 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
@@ -103,13 +103,9 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., value
-    return frame.push(config, dexItemFactory.methodHandleType);
+    return frame.push(config, appView.dexItemFactory().methodHandleType);
   }
 }
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 74131ae..69a82a9 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
@@ -101,13 +101,9 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., value
-    return frame.push(config, dexItemFactory.methodTypeType);
+    return frame.push(config, appView.dexItemFactory().methodTypeType);
   }
 }
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 ed6f4ea..978b9df 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
@@ -73,11 +73,7 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., value
     return frame.push(config, DexItemFactory.nullValueType);
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 f777da5..c5841e8 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
@@ -230,11 +230,7 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., value
     assert type.isPrimitive();
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 a205cb7..a1cf1b8 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
@@ -104,13 +104,9 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., value
-    return frame.push(config, dexItemFactory.stringType);
+    return frame.push(config, appView.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 5dcc3fb..7d24f4b 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
@@ -123,13 +123,9 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., value
-    return frame.push(config, dexItemFactory.stringType);
+    return frame.push(config, appView.dexItemFactory().stringType);
   }
 }
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 8d00469..f989239 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
@@ -12,11 +12,13 @@
 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.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 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.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;
@@ -36,8 +38,10 @@
 import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMaps;
 import java.util.ArrayDeque;
 import java.util.Deque;
+import java.util.ListIterator;
 import java.util.Objects;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -98,7 +102,7 @@
 
   // Internal constructor that does not require locals to be of the type Int2ObjectAVLTreeMap.
   private CfFrame(Int2ObjectSortedMap<FrameType> locals, Deque<PreciseFrameType> stack) {
-    assert locals.values().stream().allMatch(Objects::nonNull);
+    assert CfFrameUtils.verifyLocals(locals);
     assert stack.stream().allMatch(Objects::nonNull);
     this.locals = locals;
     this.stack = stack;
@@ -248,6 +252,24 @@
   }
 
   @Override
+  void internalRegisterUse(
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+    locals.values().forEach(frameType -> internalRegisterUse(registry, frameType));
+    stack.forEach(frameType -> internalRegisterUse(registry, frameType));
+  }
+
+  private void internalRegisterUse(UseRegistry<?> registry, FrameType frameType) {
+    if (frameType.isInitializedReferenceType()) {
+      if (frameType.isNullType()) {
+        return;
+      }
+      registry.registerTypeReference(frameType.asInitializedReferenceType().getInitializedType());
+    } else if (frameType.isUninitializedNew()) {
+      registry.registerTypeReference(frameType.asUninitializedNew().getUninitializedNewType());
+    }
+  }
+
+  @Override
   public String toString() {
     return getClass().getSimpleName();
   }
@@ -274,12 +296,8 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
-    return frame.check(appView, this);
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
+    return frame.check(config, this);
   }
 
   public static PreciseFrameType getInitializedFrameType(
@@ -295,7 +313,7 @@
     return other;
   }
 
-  public CfFrame map(java.util.function.Function<DexType, DexType> func) {
+  public CfFrame mapReferenceTypes(Function<DexType, DexType> func) {
     boolean mapped = false;
     for (int var : locals.keySet()) {
       FrameType originalType = locals.get(var);
@@ -319,9 +337,17 @@
     }
     Builder builder = builder();
     for (Int2ObjectMap.Entry<FrameType> entry : locals.int2ObjectEntrySet()) {
-      builder.store(entry.getIntKey(), entry.getValue().map(func));
+      FrameType frameType = entry.getValue();
+      if (frameType.isWidePrimitiveHigh()) {
+        // This frame type has already been written as a result of processing the previous frame
+        // type.
+        assert builder.getLocal(entry.getIntKey()) == frameType;
+        continue;
+      }
+      builder.store(entry.getIntKey(), frameType.map(func));
     }
     for (PreciseFrameType frameType : stack) {
+      assert !frameType.isWidePrimitiveHigh();
       builder.push(frameType.map(func));
     }
     return builder.build();
@@ -395,11 +421,9 @@
     }
 
     private Builder internalStore(int localIndex, FrameType frameType) {
-      ensureMutableLocals();
-      locals.put(localIndex, frameType);
-      if (frameType.isWide()) {
-        locals.put(localIndex + 1, frameType);
-      }
+      assert !frameType.isTwoWord();
+      Int2ObjectAVLTreeMap<FrameType> mutableLocals = ensureMutableLocals();
+      CfFrameUtils.storeLocal(localIndex, frameType, mutableLocals);
       return this;
     }
 
@@ -413,10 +437,11 @@
       return build();
     }
 
-    private void ensureMutableLocals() {
+    private Int2ObjectAVLTreeMap<FrameType> ensureMutableLocals() {
       if (locals == EMPTY_LOCALS) {
         locals = new Int2ObjectAVLTreeMap<>();
       }
+      return (Int2ObjectAVLTreeMap<FrameType>) locals;
     }
 
     private void ensureMutableStack() {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameUtils.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameUtils.java
new file mode 100644
index 0000000..d632a03
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameUtils.java
@@ -0,0 +1,51 @@
+// 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.frame.FrameType;
+import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
+
+public class CfFrameUtils {
+
+  public static void storeLocal(
+      int localIndex, FrameType frameType, Int2ObjectAVLTreeMap<FrameType> locals) {
+    assert !frameType.isTwoWord();
+    // Write low register.
+    FrameType previousType = locals.put(localIndex, frameType);
+    // Set low register -1 to top if it is the start of a wide primitive.
+    if (previousType != null && previousType.isWidePrimitiveHigh()) {
+      FrameType previousLowType = locals.put(localIndex - 1, FrameType.oneWord());
+      assert previousLowType == previousType.asWidePrimitive().getLowType();
+    }
+    // Write high register.
+    if (frameType.isWidePrimitive()) {
+      assert frameType.isWidePrimitiveLow();
+      previousType = locals.put(localIndex + 1, frameType.asWidePrimitive().getHighType());
+    }
+    // Set high register + 1 to top if it is the end of a wide primitive.
+    if (previousType != null && previousType.isWidePrimitiveLow()) {
+      FrameType previousHighType =
+          locals.put(localIndex + frameType.getWidth(), FrameType.oneWord());
+      assert previousHighType == previousType.asWidePrimitive().getHighType();
+    }
+  }
+
+  public static boolean verifyLocals(Int2ObjectSortedMap<FrameType> locals) {
+    for (Int2ObjectMap.Entry<FrameType> entry : locals.int2ObjectEntrySet()) {
+      int localIndex = entry.getIntKey();
+      FrameType frameType = entry.getValue();
+      if (frameType.isWidePrimitiveLow()) {
+        assert locals.get(localIndex + 1) == frameType.asWidePrimitive().getHighType();
+      } else if (frameType.isWidePrimitiveHigh()) {
+        assert locals.get(localIndex - 1) == frameType.asWidePrimitive().getLowType();
+      } else {
+        assert !frameType.isTwoWord();
+      }
+    }
+    return true;
+  }
+}
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 4c21ed6..1a62143 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
@@ -31,6 +31,7 @@
 public class CfFrameVerificationHelper implements CfAnalysisConfig {
 
   private final AppView<?> appView;
+  private final CfAssignability assignability;
   private final CfCode code;
   private final GraphLens codeLens;
   private final DexItemFactory factory;
@@ -51,6 +52,7 @@
       Map<CfLabel, CfFrame> stateMap,
       List<CfTryCatch> tryCatchRanges) {
     this.appView = appView;
+    this.assignability = new CfAssignability(appView);
     this.code = code;
     this.codeLens = codeLens;
     this.method = method;
@@ -68,6 +70,11 @@
   }
 
   @Override
+  public CfAssignability getAssignability() {
+    return assignability;
+  }
+
+  @Override
   public DexMethod getCurrentContext() {
     return previousMethod;
   }
@@ -132,7 +139,7 @@
       }
       // From the spec: the handler's exception class is assignable to the class Throwable.
       for (DexType guard : tryCatchRange.guards) {
-        if (!CfAssignability.isAssignable(guard, factory.throwableType, appView)) {
+        if (!assignability.isAssignable(guard, factory.throwableType)) {
           return CfCodeStackMapValidatingException.invalidTryCatchRange(
               method,
               tryCatchRange,
@@ -141,7 +148,7 @@
         }
         Deque<PreciseFrameType> sourceStack = ImmutableDeque.of(FrameType.initialized(guard));
         AssignabilityResult assignabilityResult =
-            CfAssignability.isStackAssignable(sourceStack, destinationFrame.getStack(), appView);
+            assignability.isStackAssignable(sourceStack, destinationFrame.getStack());
         if (assignabilityResult.isFailed()) {
           return CfCodeStackMapValidatingException.invalidTryCatchRange(
               method, tryCatchRange, assignabilityResult.asFailed().getMessage(), appView);
@@ -158,7 +165,7 @@
         if (destinationFrame == null) {
           return CfFrameState.error("No frame for target catch range target");
         }
-        state = state.checkLocals(appView, destinationFrame);
+        state = state.checkLocals(this, destinationFrame);
       }
     }
     return state;
@@ -167,7 +174,7 @@
   public CfFrameState checkTarget(CfFrameState state, CfLabel label) {
     CfFrame destinationFrame = getDestinationFrame(label);
     return destinationFrame != null
-        ? state.checkLocals(appView, destinationFrame).checkStack(appView, destinationFrame)
+        ? state.checkLocals(this, destinationFrame).checkStack(this, destinationFrame)
         : CfFrameState.error("No destination frame");
   }
 
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 e00ee93..ac161ae 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
@@ -108,11 +108,7 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     return frame;
   }
 }
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 216543f..5858c6f 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
@@ -101,13 +101,9 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., value →
     // ...
-    return frame.popInitialized(appView, type);
+    return frame.popInitialized(appView, config, type);
   }
 }
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 2a2e561..63ff9ca 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
@@ -103,13 +103,9 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., value1, value2 →
     // ...
-    return frame.popInitialized(appView, type).popInitialized(appView, type);
+    return frame.popInitialized(appView, config, type).popInitialized(appView, config, type);
   }
 }
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 3267d60..164bf07 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
@@ -98,11 +98,8 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
-    return frame.readLocal(appView, getLocalIndex(), ValueType.INT, FunctionUtils::getFirst);
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
+    return frame.readLocal(
+        appView, config, getLocalIndex(), ValueType.INT, FunctionUtils::getFirst);
   }
 }
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 e253413..39735cb 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
@@ -116,13 +116,9 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., →
     // ..., value
-    return frame.push(config, dexItemFactory.intType);
+    return frame.push(config, appView.dexItemFactory().intType);
   }
 }
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 cfd7afa..089f5ee 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
@@ -9,7 +9,6 @@
 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.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -67,15 +66,11 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., objectref →
     // ..., value
     return frame
-        .popInitialized(appView, getField().getHolderType())
+        .popInitialized(appView, config, getField().getHolderType())
         .push(config, getField().getType());
   }
 }
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 d18681f..c492db1 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
@@ -11,7 +11,6 @@
 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.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -66,17 +65,12 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., objectref, value →
     // ...
     return frame
-        .popInitialized(appView, getField().getType())
+        .popInitialized(appView, config, getField().getType())
         .popObject(
-            appView,
             getField().getHolderType(),
             config,
             (state, head) -> head.isUninitializedNew() ? error(head) : state);
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 80da4fe..d13c3a9 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
@@ -126,15 +126,12 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., objectref →
     // ..., result
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
     return frame
-        .popInitialized(appView, dexItemFactory.objectType)
+        .popInitialized(appView, config, dexItemFactory.objectType)
         .push(config, dexItemFactory.intType);
   }
 }
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 932a283..66e3d69 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
@@ -343,6 +343,11 @@
     return false;
   }
 
+  @Override
+  public final boolean instructionTypeCanThrow() {
+    return canThrow();
+  }
+
   public abstract void buildIR(IRBuilder builder, CfState state, CfSourceCode code);
 
   /** Return true if this instruction directly emits IR instructions. */
@@ -354,8 +359,5 @@
       InliningConstraints inliningConstraints, CfCode code, ProgramMethod context);
 
   public abstract CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory);
+      CfFrameState frame, AppView<?> appView, CfAnalysisConfig config);
 }
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 b67dda7..2be9f2e 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
@@ -316,25 +316,22 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., objectref, [arg1, [arg2 ...]] →
     // ... [ returnType ]
     // OR, for static method calls:
     // ..., [arg1, [arg2 ...]] →
     // ...
-    frame = frame.popInitialized(appView, method.getParameters().getBacking());
+    frame = frame.popInitialized(appView, config, method.getParameters().getBacking());
     if (opcode != Opcodes.INVOKESTATIC) {
       if (method.getHolderType().isArrayType()) {
         frame = frame.popArray(appView);
       } else {
+        DexItemFactory dexItemFactory = appView.dexItemFactory();
         frame =
             opcode == Opcodes.INVOKESPECIAL && method.isInstanceInitializer(dexItemFactory)
                 ? frame.popAndInitialize(appView, method, config)
-                : frame.popInitialized(appView, method.getHolderType());
+                : frame.popInitialized(appView, config, method.getHolderType());
       }
     }
     if (method.getReturnType().isVoidType()) {
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 a716425..45228d7 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
@@ -167,14 +167,12 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., [arg1, [arg2 ...]] →
     // ...
-    frame = frame.popInitialized(appView, callSite.getMethodProto().getParameters().getBacking());
+    frame =
+        frame.popInitialized(
+            appView, config, callSite.getMethodProto().getParameters().getBacking());
     DexType returnType = callSite.getMethodProto().getReturnType();
     if (returnType.isVoidType()) {
       return frame;
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 698e91d..4eaeca8 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
@@ -83,11 +83,7 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     return CfFrameState.error("Unexpected JSR/RET instruction");
   }
 
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 011f97e..4bd6f52 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
@@ -96,11 +96,7 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     return frame;
   }
 }
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 6831f06..9992cc8 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
@@ -128,15 +128,12 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., objectref
     return frame.readLocal(
         appView,
+        config,
         getLocalIndex(),
         type,
         (state, frameType) ->
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 8109ca3..a5f499d 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
@@ -177,11 +177,7 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., value1, value2 →
     // ..., result
     NumericType value1Type = type;
@@ -196,8 +192,8 @@
         value2Type = NumericType.INT;
     }
     return frame
-        .popInitialized(appView, value2Type)
-        .popInitialized(appView, value1Type)
+        .popInitialized(appView, config, value2Type)
+        .popInitialized(appView, config, value1Type)
         .push(appView, config, value1Type);
   }
 }
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 f225050..db26e94 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
@@ -94,13 +94,9 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., objectref →
     // ...
-    return frame.popInitialized(appView, dexItemFactory.objectType);
+    return frame.popInitialized(appView, config, appView.dexItemFactory().objectType);
   }
 }
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 4c55cc4..672db34 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
@@ -129,15 +129,12 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., count1, [count2, ...] →
     // ..., arrayref
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
     for (int i = 0; i < dimensions; i++) {
-      frame = frame.popInitialized(appView, dexItemFactory.intType);
+      frame = frame.popInitialized(appView, config, dexItemFactory.intType);
     }
     return frame.push(config, type);
   }
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 bbed094..0cee97d 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
@@ -119,13 +119,9 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., value →
     // ..., result
-    return frame.popInitialized(appView, type).push(appView, config, type);
+    return frame.popInitialized(appView, config, type).push(appView, config, type);
   }
 }
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 ec6d665..00fb189 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
@@ -132,11 +132,7 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., objectref
     return frame.push(config, FrameType.uninitializedNew(getLabel(), 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 55a451f..68494c8 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
@@ -165,13 +165,10 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., count →
     // ..., arrayref
-    return frame.popInitialized(appView, dexItemFactory.intType).push(config, type);
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    return frame.popInitialized(appView, config, dexItemFactory.intType).push(config, type);
   }
 }
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 febd00e..4b759a2 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
@@ -117,11 +117,7 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ... →
     // ..., objectref
     return frame.push(config, type);
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 1e1cc75..b5d7d56 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
@@ -77,11 +77,7 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     return frame;
   }
 }
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 8b0e54e..40f7298 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
@@ -190,13 +190,9 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., value →
     // ..., result
-    return frame.popInitialized(appView, from).push(appView, config, to);
+    return frame.popInitialized(appView, config, from).push(appView, config, to);
   }
 }
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 f6d2d6c..6e7dc78 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
@@ -110,11 +110,7 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     return frame;
   }
 }
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 18728fd..794872f 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
@@ -110,13 +110,10 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
     for (DexField ignored : fields) {
-      frame = frame.popInitialized(appView, dexItemFactory.objectType);
+      frame = frame.popInitialized(appView, config, dexItemFactory.objectType);
     }
     return frame.push(config, dexItemFactory.objectArrayType);
   }
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 1bb122a..fe5af15 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
@@ -118,12 +118,10 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     assert !config.getCurrentContext().getReturnType().isVoidType();
-    return frame.popInitialized(appView, config.getCurrentContext().getReturnType()).clear();
+    return frame
+        .popInitialized(appView, config, config.getCurrentContext().getReturnType())
+        .clear();
   }
 }
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 fa1227c..2a25414 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
@@ -92,11 +92,7 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     return frame.clear();
   }
 }
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 6971f49..664e746 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
@@ -354,11 +354,7 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     switch (opcode) {
       case Pop:
         {
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 c60a5fe..73d6349 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
@@ -9,7 +9,6 @@
 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.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -70,11 +69,7 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., →
     // ..., value
     return frame.push(config, 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 fa7df86..bdad2a3 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
@@ -8,7 +8,6 @@
 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.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -66,13 +65,9 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., value →
     // ...
-    return frame.popInitialized(appView, getField().getType());
+    return frame.popInitialized(appView, config, getField().getType());
   }
 }
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 1015a15..6439925 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
@@ -125,11 +125,7 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., ref →
     // ...
     if (type.isObject()) {
@@ -138,6 +134,7 @@
       assert type.isPrimitive();
       return frame.popInitialized(
           appView,
+          config,
           type,
           (state, head) ->
               state.storeLocal(getLocalIndex(), type.toPrimitiveType(), appView, config));
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSubtypingAssignability.java b/src/main/java/com/android/tools/r8/cf/code/CfSubtypingAssignability.java
new file mode 100644
index 0000000..0343076
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSubtypingAssignability.java
@@ -0,0 +1,32 @@
+// 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.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+
+public class CfSubtypingAssignability extends CfAssignability {
+
+  public CfSubtypingAssignability(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    super(appView);
+  }
+
+  @Override
+  boolean internalIsClassTypeAssignableToClassType(DexType source, DexType target) {
+    if (source == target || target == dexItemFactory.objectType) {
+      return true;
+    }
+    if (source.toTypeElement(appView).lessThanOrEqual(target.toTypeElement(appView), appView)) {
+      return true;
+    }
+    DexClass targetClass = appView.definitionFor(target);
+    if (targetClass != null && targetClass.isInterface()) {
+      return true;
+    }
+    return false;
+  }
+}
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 3c3f06c..75ff36d 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
@@ -171,13 +171,9 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., index/key →
     // ...
-    return frame.popInitialized(appView, dexItemFactory.intType);
+    return frame.popInitialized(appView, config, appView.dexItemFactory().intType);
   }
 }
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 c7bc17e..bf50856 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
@@ -99,13 +99,9 @@
   }
 
   @Override
-  public CfFrameState evaluate(
-      CfFrameState frame,
-      AppView<?> appView,
-      CfAnalysisConfig config,
-      DexItemFactory dexItemFactory) {
+  public CfFrameState evaluate(CfFrameState frame, AppView<?> appView, CfAnalysisConfig config) {
     // ..., objectref →
     // objectref
-    return frame.popInitialized(appView, dexItemFactory.throwableType).clear();
+    return frame.popInitialized(appView, config, appView.dexItemFactory().throwableType).clear();
   }
 }
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 22aac9a..67f452d 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
@@ -12,7 +12,9 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 public class CfTryCatch {
@@ -29,6 +31,15 @@
     assert verifyAllNonNull(guards);
   }
 
+  public void forEach(BiConsumer<DexType, CfLabel> consumer) {
+    Iterator<DexType> guardIterator = guards.iterator();
+    Iterator<CfLabel> targetIterator = targets.iterator();
+    while (guardIterator.hasNext()) {
+      consumer.accept(guardIterator.next(), targetIterator.next());
+    }
+    assert !targetIterator.hasNext();
+  }
+
   public void forEachTarget(Consumer<CfLabel> consumer) {
     targets.forEach(consumer);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/BaseFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/BaseFrameType.java
index 392b16d..e8eb7b3 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/BaseFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/BaseFrameType.java
@@ -31,6 +31,16 @@
   }
 
   @Override
+  public boolean isDoubleLow() {
+    return false;
+  }
+
+  @Override
+  public boolean isDoubleHigh() {
+    return false;
+  }
+
+  @Override
   public boolean isFloat() {
     return false;
   }
@@ -46,6 +56,16 @@
   }
 
   @Override
+  public boolean isLongLow() {
+    return false;
+  }
+
+  @Override
+  public boolean isLongHigh() {
+    return false;
+  }
+
+  @Override
   public boolean isShort() {
     return false;
   }
@@ -99,13 +119,18 @@
   }
 
   @Override
+  public boolean isSinglePrimitive() {
+    return false;
+  }
+
+  @Override
   public SinglePrimitiveFrameType asSinglePrimitive() {
     return null;
   }
 
   @Override
   public boolean isInitializedReferenceType() {
-    return true;
+    return false;
   }
 
   @Override
@@ -124,6 +149,26 @@
   }
 
   @Override
+  public boolean isWidePrimitive() {
+    return false;
+  }
+
+  @Override
+  public WidePrimitiveFrameType asWidePrimitive() {
+    return null;
+  }
+
+  @Override
+  public boolean isWidePrimitiveLow() {
+    return false;
+  }
+
+  @Override
+  public boolean isWidePrimitiveHigh() {
+    return false;
+  }
+
+  @Override
   public int getWidth() {
     assert isSingle();
     return 1;
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/DoubleFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/DoubleFrameType.java
index 93c0687..41ee1b5 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/DoubleFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/DoubleFrameType.java
@@ -14,7 +14,17 @@
 
   static final DoubleFrameType SINGLETON = new DoubleFrameType();
 
-  private DoubleFrameType() {}
+  DoubleFrameType() {}
+
+  @Override
+  public DoubleFrameType getLowType() {
+    return FrameType.doubleType();
+  }
+
+  @Override
+  public DoubleHighFrameType getHighType() {
+    return FrameType.doubleHighType();
+  }
 
   @Override
   public boolean isDouble() {
@@ -22,6 +32,26 @@
   }
 
   @Override
+  public boolean isDoubleLow() {
+    return true;
+  }
+
+  @Override
+  public boolean isDoubleHigh() {
+    return false;
+  }
+
+  @Override
+  public boolean isWidePrimitiveLow() {
+    return true;
+  }
+
+  @Override
+  public boolean isWidePrimitiveHigh() {
+    return false;
+  }
+
+  @Override
   public DexType getInitializedType(DexItemFactory dexItemFactory) {
     return dexItemFactory.doubleType;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/DoubleHighFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/DoubleHighFrameType.java
new file mode 100644
index 0000000..7c89492
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/DoubleHighFrameType.java
@@ -0,0 +1,63 @@
+// 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.frame;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.naming.NamingLens;
+
+public class DoubleHighFrameType extends DoubleFrameType {
+
+  static final DoubleHighFrameType SINGLETON = new DoubleHighFrameType();
+
+  private DoubleHighFrameType() {}
+
+  @Override
+  public boolean isDouble() {
+    return true;
+  }
+
+  @Override
+  public boolean isDoubleLow() {
+    return false;
+  }
+
+  @Override
+  public boolean isDoubleHigh() {
+    return true;
+  }
+
+  @Override
+  public boolean isWidePrimitiveLow() {
+    return false;
+  }
+
+  @Override
+  public boolean isWidePrimitiveHigh() {
+    return true;
+  }
+
+  @Override
+  public DexType getInitializedType(DexItemFactory dexItemFactory) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public String getTypeName() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public String toString() {
+    return "double-high";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/FrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/FrameType.java
index f614d65..823380f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/FrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/FrameType.java
@@ -32,6 +32,10 @@
     return DoubleFrameType.SINGLETON;
   }
 
+  static DoubleHighFrameType doubleHighType() {
+    return DoubleHighFrameType.SINGLETON;
+  }
+
   static FloatFrameType floatType() {
     return FloatFrameType.SINGLETON;
   }
@@ -44,6 +48,10 @@
     return LongFrameType.SINGLETON;
   }
 
+  static LongHighFrameType longHighType() {
+    return LongHighFrameType.SINGLETON;
+  }
+
   static ShortFrameType shortType() {
     return ShortFrameType.SINGLETON;
   }
@@ -52,6 +60,11 @@
     if (type.isPrimitiveType()) {
       return primitive(type);
     }
+    return initializedReference(type);
+  }
+
+  static InitializedReferenceFrameType initializedReference(DexType type) {
+    assert type.isReferenceType();
     return new InitializedReferenceFrameType(type);
   }
 
@@ -140,6 +153,10 @@
 
   boolean isDouble();
 
+  boolean isDoubleLow();
+
+  boolean isDoubleHigh();
+
   boolean isFloat();
 
   boolean isInitialized();
@@ -152,6 +169,10 @@
 
   boolean isLong();
 
+  boolean isLongLow();
+
+  boolean isLongHigh();
+
   boolean isNullType();
 
   boolean isObject();
@@ -172,6 +193,8 @@
 
   SingleFrameType asSingle();
 
+  boolean isSinglePrimitive();
+
   SinglePrimitiveFrameType asSinglePrimitive();
 
   boolean isTwoWord();
@@ -192,6 +215,14 @@
 
   WideFrameType asWide();
 
+  boolean isWidePrimitive();
+
+  WidePrimitiveFrameType asWidePrimitive();
+
+  boolean isWidePrimitiveLow();
+
+  boolean isWidePrimitiveHigh();
+
   default FrameType map(Function<DexType, DexType> fn) {
     assert !isPrecise();
     return this;
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/InitializedReferenceFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedReferenceFrameType.java
index fd00888..27ada3e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/InitializedReferenceFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedReferenceFrameType.java
@@ -4,10 +4,12 @@
 
 package com.android.tools.r8.cf.code.frame;
 
-import com.android.tools.r8.errors.Unimplemented;
 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.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeUtils;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.Opcodes;
@@ -44,7 +46,8 @@
   }
 
   @Override
-  public SingleFrameType join(SingleFrameType frameType) {
+  public SingleFrameType join(
+      AppView<? extends AppInfoWithClassHierarchy> appView, SingleFrameType frameType) {
     if (equals(frameType)) {
       return this;
     }
@@ -62,8 +65,10 @@
     }
     assert type.isArrayType() || type.isClassType();
     assert otherType.isArrayType() || otherType.isClassType();
-    // TODO(b/214496607): Implement join of different reference types using class hierarchy.
-    throw new Unimplemented();
+    DexType joinType =
+        DexTypeUtils.toDexType(
+            appView, type.toTypeElement(appView).join(otherType.toTypeElement(appView), appView));
+    return FrameType.initializedReference(joinType);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/LongFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/LongFrameType.java
index 1fb59c7..507c164 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/LongFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/LongFrameType.java
@@ -14,7 +14,17 @@
 
   static final LongFrameType SINGLETON = new LongFrameType();
 
-  private LongFrameType() {}
+  LongFrameType() {}
+
+  @Override
+  public LongFrameType getLowType() {
+    return FrameType.longType();
+  }
+
+  @Override
+  public LongHighFrameType getHighType() {
+    return FrameType.longHighType();
+  }
 
   @Override
   public boolean isLong() {
@@ -22,6 +32,26 @@
   }
 
   @Override
+  public boolean isLongLow() {
+    return true;
+  }
+
+  @Override
+  public boolean isLongHigh() {
+    return false;
+  }
+
+  @Override
+  public boolean isWidePrimitiveLow() {
+    return true;
+  }
+
+  @Override
+  public boolean isWidePrimitiveHigh() {
+    return false;
+  }
+
+  @Override
   public DexType getInitializedType(DexItemFactory dexItemFactory) {
     return dexItemFactory.longType;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/LongHighFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/LongHighFrameType.java
new file mode 100644
index 0000000..fadcd79
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/LongHighFrameType.java
@@ -0,0 +1,63 @@
+// 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.frame;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.naming.NamingLens;
+
+public class LongHighFrameType extends LongFrameType {
+
+  static final LongHighFrameType SINGLETON = new LongHighFrameType();
+
+  private LongHighFrameType() {}
+
+  @Override
+  public boolean isLong() {
+    return true;
+  }
+
+  @Override
+  public boolean isLongLow() {
+    return false;
+  }
+
+  @Override
+  public boolean isLongHigh() {
+    return true;
+  }
+
+  @Override
+  public boolean isWidePrimitiveLow() {
+    return false;
+  }
+
+  @Override
+  public boolean isWidePrimitiveHigh() {
+    return true;
+  }
+
+  @Override
+  public DexType getInitializedType(DexItemFactory dexItemFactory) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public String getTypeName() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public String toString() {
+    return "long-high";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/OneWord.java b/src/main/java/com/android/tools/r8/cf/code/frame/OneWord.java
index 9ac5082..06add68 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/OneWord.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/OneWord.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.cf.code.frame;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.Opcodes;
@@ -25,7 +27,8 @@
   }
 
   @Override
-  public SingleFrameType join(SingleFrameType frameType) {
+  public SingleFrameType join(
+      AppView<? extends AppInfoWithClassHierarchy> appView, SingleFrameType frameType) {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/PreciseFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/PreciseFrameType.java
index aa105e0..dfa0786 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/PreciseFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/PreciseFrameType.java
@@ -16,7 +16,7 @@
         DexType type = asInitializedReferenceType().getInitializedType();
         DexType newType = fn.apply(type);
         if (type != newType) {
-          return FrameType.initialized(newType);
+          return FrameType.initializedReference(newType);
         }
       }
       if (isUninitializedNew()) {
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/SingleFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/SingleFrameType.java
index 657d493..39c6e70 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/SingleFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/SingleFrameType.java
@@ -4,8 +4,11 @@
 
 package com.android.tools.r8.cf.code.frame;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
 
 public interface SingleFrameType extends FrameType {
 
-  SingleFrameType join(SingleFrameType frameType);
+  SingleFrameType join(
+      AppView<? extends AppInfoWithClassHierarchy> appView, SingleFrameType frameType);
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/SinglePrimitiveFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/SinglePrimitiveFrameType.java
index 69ba4b6..5525646 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/SinglePrimitiveFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/SinglePrimitiveFrameType.java
@@ -4,6 +4,9 @@
 
 package com.android.tools.r8.cf.code.frame;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+
 public abstract class SinglePrimitiveFrameType extends SingletonFrameType
     implements PrimitiveFrameType, SingleFrameType {
 
@@ -42,12 +45,18 @@
   }
 
   @Override
+  public boolean isSinglePrimitive() {
+    return true;
+  }
+
+  @Override
   public final SinglePrimitiveFrameType asSinglePrimitive() {
     return this;
   }
 
   @Override
-  public final SingleFrameType join(SingleFrameType frameType) {
+  public final SingleFrameType join(
+      AppView<? extends AppInfoWithClassHierarchy> appView, SingleFrameType frameType) {
     if (this == frameType) {
       return this;
     }
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedNew.java b/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedNew.java
index 03eff42..a35e4b4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedNew.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.cf.code.frame;
 
 import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.naming.NamingLens;
@@ -16,6 +18,7 @@
   private final DexType type;
 
   public UninitializedNew(CfLabel label, DexType type) {
+    assert type.isClassType();
     this.label = label;
     this.type = type;
   }
@@ -51,7 +54,8 @@
   }
 
   @Override
-  public SingleFrameType join(SingleFrameType frameType) {
+  public SingleFrameType join(
+      AppView<? extends AppInfoWithClassHierarchy> appView, SingleFrameType frameType) {
     return equals(frameType) ? this : FrameType.oneWord();
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedThis.java b/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedThis.java
index 69d1ecb..5b6730d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedThis.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedThis.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.cf.code.frame;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.naming.NamingLens;
@@ -36,7 +38,8 @@
   }
 
   @Override
-  public SingleFrameType join(SingleFrameType frameType) {
+  public SingleFrameType join(
+      AppView<? extends AppInfoWithClassHierarchy> appView, SingleFrameType frameType) {
     if (this == frameType) {
       return this;
     }
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/WidePrimitiveFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/WidePrimitiveFrameType.java
index 11f7b75..2b46f31 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/WidePrimitiveFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/WidePrimitiveFrameType.java
@@ -7,6 +7,10 @@
 public abstract class WidePrimitiveFrameType extends SingletonFrameType
     implements PrimitiveFrameType, WideFrameType {
 
+  public abstract WidePrimitiveFrameType getLowType();
+
+  public abstract WidePrimitiveFrameType getHighType();
+
   @Override
   public boolean isInitialized() {
     return true;
@@ -43,6 +47,16 @@
   }
 
   @Override
+  public boolean isWidePrimitive() {
+    return true;
+  }
+
+  @Override
+  public WidePrimitiveFrameType asWidePrimitive() {
+    return this;
+  }
+
+  @Override
   public int getWidth() {
     return 2;
   }
@@ -53,7 +67,7 @@
   }
 
   @Override
-  public final String toString() {
+  public String toString() {
     return getTypeName();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index aef2eeb..52eafdb 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -316,8 +316,7 @@
   }
 
   private void checkName(DexString name) {
-    if (!options.itemFactory.getSkipNameValidationForTesting()
-        && !name.isValidSimpleName(options.getMinApiLevel())) {
+    if (!options.canUseSpacesInSimpleName() && !name.isValidSimpleName(options.getMinApiLevel())) {
       throw new CompilationError(
           "Space characters in SimpleName '"
               + name.toASCIIString()
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 2c9ca25..6167f9b 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -47,7 +47,9 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.position.Position;
 import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DexVersion;
@@ -265,10 +267,10 @@
     for (DexProgramClass clazz : mapping.getClasses()) {
       if (clazz.isInterface()) {
         for (DexEncodedMethod method : clazz.directMethods()) {
-          checkInterfaceMethod(method);
+          checkInterfaceMethod(clazz, method);
         }
         for (DexEncodedMethod method : clazz.virtualMethods()) {
-          checkInterfaceMethod(method);
+          checkInterfaceMethod(clazz, method);
         }
       }
     }
@@ -280,7 +282,7 @@
   //  -- starting with N interfaces may also have public or private
   //     static methods, as well as public non-abstract (default)
   //     and private instance methods.
-  private void checkInterfaceMethod(DexEncodedMethod method) {
+  private void checkInterfaceMethod(DexProgramClass holder, DexEncodedMethod method) {
     if (appView.dexItemFactory().isClassConstructor(method.getReference())) {
       return; // Class constructor is always OK.
     }
@@ -288,7 +290,7 @@
       if (!options.canUseDefaultAndStaticInterfaceMethods()
           && !options.testing.allowStaticInterfaceMethodsForPreNApiLevel) {
         throw options.reporter.fatalError(
-            new StaticInterfaceMethodDiagnostic(new MethodPosition(method.getReference())));
+            new StaticInterfaceMethodDiagnostic(holder.getOrigin(), MethodPosition.create(method)));
       }
 
     } else {
@@ -299,7 +301,8 @@
       if (!method.accessFlags.isAbstract() && !method.accessFlags.isPrivate() &&
           !options.canUseDefaultAndStaticInterfaceMethods()) {
         throw options.reporter.fatalError(
-            new DefaultInterfaceMethodDiagnostic(new MethodPosition(method.getReference())));
+            new DefaultInterfaceMethodDiagnostic(
+                holder.getOrigin(), MethodPosition.create(method)));
       }
     }
 
@@ -308,7 +311,7 @@
         return;
       }
       throw options.reporter.fatalError(
-          new PrivateInterfaceMethodDiagnostic(new MethodPosition(method.getReference())));
+          new PrivateInterfaceMethodDiagnostic(holder.getOrigin(), MethodPosition.create(method)));
     }
 
     if (!method.accessFlags.isPublic()) {
@@ -1378,7 +1381,8 @@
 
   private void checkThatInvokeCustomIsAllowed() {
     if (!options.canUseInvokeCustom()) {
-      throw options.reporter.fatalError(new InvokeCustomDiagnostic());
+      throw options.reporter.fatalError(
+          new InvokeCustomDiagnostic(Origin.unknown(), Position.UNKNOWN));
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/dex/code/CfOrDexInstruction.java b/src/main/java/com/android/tools/r8/dex/code/CfOrDexInstruction.java
index 7bd1e7a..8d7ef16 100644
--- a/src/main/java/com/android/tools/r8/dex/code/CfOrDexInstruction.java
+++ b/src/main/java/com/android/tools/r8/dex/code/CfOrDexInstruction.java
@@ -5,8 +5,9 @@
 package com.android.tools.r8.dex.code;
 
 import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractInstruction;
 
-public interface CfOrDexInstruction {
+public interface CfOrDexInstruction extends AbstractInstruction {
 
   CfInstruction asCfInstruction();
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java b/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java
index cc35c46..5efe526 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java
@@ -415,4 +415,9 @@
   public boolean canThrow() {
     return false;
   }
+
+  @Override
+  public final boolean instructionTypeCanThrow() {
+    return canThrow();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/errors/ApiLevelDiagnostic.java b/src/main/java/com/android/tools/r8/errors/ApiLevelDiagnostic.java
deleted file mode 100644
index fde6860..0000000
--- a/src/main/java/com/android/tools/r8/errors/ApiLevelDiagnostic.java
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.errors;
-
-import com.android.tools.r8.Diagnostic;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.Position;
-
-// TODO(b/154778581): Flesh out this class/interface and keep it.
-public abstract class ApiLevelDiagnostic implements Diagnostic {
-
-  @Override
-  public Origin getOrigin() {
-    return Origin.unknown();
-  }
-
-  @Override
-  public Position getPosition() {
-    return Position.UNKNOWN;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/errors/ConstMethodHandleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/ConstMethodHandleDiagnostic.java
new file mode 100644
index 0000000..d77a5f5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/ConstMethodHandleDiagnostic.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.errors;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.utils.AndroidApiLevel;
+
+public class ConstMethodHandleDiagnostic extends UnsupportedFeatureDiagnostic {
+
+  public ConstMethodHandleDiagnostic(Origin origin, MethodPosition position) {
+    super("const-method-handle", AndroidApiLevel.P, origin, position);
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return UnsupportedFeatureDiagnostic.makeMessage(
+        AndroidApiLevel.P, "Const-method-handle", getPosition().toString());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/ConstMethodTypeDiagnostic.java b/src/main/java/com/android/tools/r8/errors/ConstMethodTypeDiagnostic.java
new file mode 100644
index 0000000..2a9eff5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/ConstMethodTypeDiagnostic.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.errors;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.utils.AndroidApiLevel;
+
+public class ConstMethodTypeDiagnostic extends UnsupportedFeatureDiagnostic {
+
+  public ConstMethodTypeDiagnostic(Origin origin, MethodPosition position) {
+    super("const-method-type", AndroidApiLevel.P, origin, position);
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return UnsupportedFeatureDiagnostic.makeMessage(
+        AndroidApiLevel.P, "Const-method-type", getPosition().toString());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/DefaultInterfaceMethodDiagnostic.java b/src/main/java/com/android/tools/r8/errors/DefaultInterfaceMethodDiagnostic.java
index b0dfe4e..21b15b2 100644
--- a/src/main/java/com/android/tools/r8/errors/DefaultInterfaceMethodDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/DefaultInterfaceMethodDiagnostic.java
@@ -3,28 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.errors;
 
-import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.AndroidApiLevel;
 
-public class DefaultInterfaceMethodDiagnostic extends ApiLevelDiagnostic {
+public class DefaultInterfaceMethodDiagnostic extends UnsupportedFeatureDiagnostic {
 
-  private final MethodPosition position;
-
-  public DefaultInterfaceMethodDiagnostic(MethodPosition position) {
-    assert position != null;
-    this.position = position;
-  }
-
-  @Override
-  public Position getPosition() {
-    return position;
+  public DefaultInterfaceMethodDiagnostic(Origin origin, MethodPosition position) {
+    super("default-interface-method", AndroidApiLevel.N, origin, position);
   }
 
   @Override
   public String getDiagnosticMessage() {
-    return ApiLevelException.makeMessage(
-        AndroidApiLevel.N, "Default interface methods", position.toString());
+    return UnsupportedFeatureDiagnostic.makeMessage(
+        AndroidApiLevel.N, "Default interface methods", getPosition().toString());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/errors/InvokeCustomDiagnostic.java b/src/main/java/com/android/tools/r8/errors/InvokeCustomDiagnostic.java
index ab8ff24..0f8c6ab 100644
--- a/src/main/java/com/android/tools/r8/errors/InvokeCustomDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/InvokeCustomDiagnostic.java
@@ -3,13 +3,18 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.errors;
 
-import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.AndroidApiLevel;
 
-public class InvokeCustomDiagnostic extends ApiLevelDiagnostic {
+public class InvokeCustomDiagnostic extends UnsupportedFeatureDiagnostic {
+
+  public InvokeCustomDiagnostic(Origin origin, Position position) {
+    super("invoke-custom", AndroidApiLevel.O, origin, position);
+  }
 
   @Override
   public String getDiagnosticMessage() {
-    return ApiLevelException.makeMessage(AndroidApiLevel.O, "Invoke-customs", null);
+    return UnsupportedFeatureDiagnostic.makeMessage(AndroidApiLevel.O, "Invoke-customs", null);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/errors/InvokePolymorphicMethodHandleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/InvokePolymorphicMethodHandleDiagnostic.java
new file mode 100644
index 0000000..1efbb31
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/InvokePolymorphicMethodHandleDiagnostic.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.errors;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.utils.AndroidApiLevel;
+
+public class InvokePolymorphicMethodHandleDiagnostic extends UnsupportedFeatureDiagnostic {
+
+  public InvokePolymorphicMethodHandleDiagnostic(Origin origin, MethodPosition position) {
+    super("invoke-polymorphic-method-handle", AndroidApiLevel.O, origin, position);
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return UnsupportedFeatureDiagnostic.makeMessage(
+        AndroidApiLevel.O,
+        "MethodHandle.invoke and MethodHandle.invokeExact",
+        getPosition().toString());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/InvokePolymorphicVarHandleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/InvokePolymorphicVarHandleDiagnostic.java
new file mode 100644
index 0000000..9758581
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/InvokePolymorphicVarHandleDiagnostic.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.errors;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.utils.AndroidApiLevel;
+
+public class InvokePolymorphicVarHandleDiagnostic extends UnsupportedFeatureDiagnostic {
+
+  public InvokePolymorphicVarHandleDiagnostic(Origin origin, MethodPosition position) {
+    super("invoke-polymorphic-var-handle", AndroidApiLevel.P, origin, position);
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return UnsupportedFeatureDiagnostic.makeMessage(
+        AndroidApiLevel.P, "Call to polymorphic signature of VarHandle", getPosition().toString());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/PrivateInterfaceMethodDiagnostic.java b/src/main/java/com/android/tools/r8/errors/PrivateInterfaceMethodDiagnostic.java
index 568a7a0..9c85254 100644
--- a/src/main/java/com/android/tools/r8/errors/PrivateInterfaceMethodDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/PrivateInterfaceMethodDiagnostic.java
@@ -3,28 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.errors;
 
-import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.AndroidApiLevel;
 
-public class PrivateInterfaceMethodDiagnostic extends ApiLevelDiagnostic {
+public class PrivateInterfaceMethodDiagnostic extends UnsupportedFeatureDiagnostic {
 
-  private final MethodPosition position;
-
-  public PrivateInterfaceMethodDiagnostic(MethodPosition position) {
-    assert position != null;
-    this.position = position;
-  }
-
-  @Override
-  public Position getPosition() {
-    return position;
+  public PrivateInterfaceMethodDiagnostic(Origin origin, MethodPosition position) {
+    super("private-interface-method", AndroidApiLevel.N, origin, position);
   }
 
   @Override
   public String getDiagnosticMessage() {
-    return ApiLevelException.makeMessage(
-        AndroidApiLevel.N, "Private interface methods", position.toString());
+    return UnsupportedFeatureDiagnostic.makeMessage(
+        AndroidApiLevel.N, "Private interface methods", getPosition().toString());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/errors/StaticInterfaceMethodDiagnostic.java b/src/main/java/com/android/tools/r8/errors/StaticInterfaceMethodDiagnostic.java
index cb00b94..9bc4f48 100644
--- a/src/main/java/com/android/tools/r8/errors/StaticInterfaceMethodDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/StaticInterfaceMethodDiagnostic.java
@@ -3,28 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.errors;
 
-import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.AndroidApiLevel;
 
-public class StaticInterfaceMethodDiagnostic extends ApiLevelDiagnostic {
+public class StaticInterfaceMethodDiagnostic extends UnsupportedFeatureDiagnostic {
 
-  private final MethodPosition position;
-
-  public StaticInterfaceMethodDiagnostic(MethodPosition position) {
-    assert position != null;
-    this.position = position;
-  }
-
-  @Override
-  public Position getPosition() {
-    return position;
+  public StaticInterfaceMethodDiagnostic(Origin origin, MethodPosition position) {
+    super("static-interface-method", AndroidApiLevel.N, origin, position);
   }
 
   @Override
   public String getDiagnosticMessage() {
-    return ApiLevelException.makeMessage(
-        AndroidApiLevel.N, "Static interface methods", position.toString());
+    return UnsupportedFeatureDiagnostic.makeMessage(
+        AndroidApiLevel.N, "Static interface methods", getPosition().toString());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedFeatureDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedFeatureDiagnostic.java
new file mode 100644
index 0000000..74c3ad6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedFeatureDiagnostic.java
@@ -0,0 +1,70 @@
+// 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.errors;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.utils.AndroidApiLevel;
+
+@Keep
+public abstract class UnsupportedFeatureDiagnostic implements Diagnostic {
+
+  public static String makeMessage(
+      AndroidApiLevel minApiLevel, String unsupportedFeatures, String sourceString) {
+    String message =
+        unsupportedFeatures
+            + " are only supported starting with "
+            + minApiLevel.getName()
+            + " (--min-api "
+            + minApiLevel.getLevel()
+            + ")";
+    message = (sourceString != null) ? message + ": " + sourceString : message;
+    return message;
+  }
+
+  private final String descriptor;
+  private final AndroidApiLevel supportedApiLevel;
+  private final Origin origin;
+  private final Position position;
+
+  // Package-private constructor.
+  UnsupportedFeatureDiagnostic(
+      String descriptor, AndroidApiLevel supportedApiLevel, Origin origin, Position position) {
+    this.descriptor = descriptor;
+    this.supportedApiLevel = supportedApiLevel;
+    this.origin = origin;
+    this.position = position;
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  @Override
+  public Position getPosition() {
+    return position;
+  }
+
+  /**
+   * Get a descriptor for the unsupported feature.
+   *
+   * <p>This descriptor is guaranteed by the compiler to be unique for possible unsupported features
+   * and will remain unchanged in future versions of the compiler.
+   */
+  public String getFeatureDescriptor() {
+    return descriptor;
+  }
+
+  /**
+   * Get the API level at which this feature is supported.
+   *
+   * @return Supported level or -1 if unsupported at all API levels known to the compiler.
+   */
+  public int getSupportedApiLevel() {
+    return supportedApiLevel == null ? -1 : supportedApiLevel.getLevel();
+  }
+}
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 f0598cd..44289af 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -281,6 +281,10 @@
     return tryCatchRanges;
   }
 
+  public CfInstruction getInstruction(int index) {
+    return instructions.get(index);
+  }
+
   public List<CfInstruction> getInstructions() {
     return Collections.unmodifiableList(instructions);
   }
@@ -380,7 +384,7 @@
     // In cf to cf desugar we do pass through of code and don't move around methods.
     // TODO(b/169115389): Remove when we have a way to determine if we need parameter names per
     // method.
-    if (appView.options().cfToCfDesugar) {
+    if (appView.options().isCfDesugaring()) {
       return false;
     }
 
@@ -978,7 +982,7 @@
       if (instruction.isLabel()) {
         helper.seenLabel(instruction.asLabel());
       }
-      state = instruction.evaluate(state, appView, helper, appView.dexItemFactory());
+      state = instruction.evaluate(state, appView, helper);
       if (instruction.isJumpWithNormalTarget()) {
         CfInstruction fallthroughInstruction =
             (i + 1) < instructions.size() ? instructions.get(i + 1) : null;
@@ -1046,7 +1050,7 @@
       DexMethod previousMethodSignature,
       boolean previousMethodSignatureIsInstance) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
-    CfFrameState state = ConcreteCfFrameState.bottom();
+    CfFrameState state = new ConcreteCfFrameState();
     int localIndex = 0;
     if (previousMethodSignatureIsInstance) {
       state =
diff --git a/src/main/java/com/android/tools/r8/graph/ClassResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ClassResolutionResult.java
index 12baacf..17ba862 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassResolutionResult.java
@@ -14,10 +14,12 @@
 
   DexClass toSingleClassWithProgramOverLibrary();
 
+  DexClass toSingleClassWithLibraryOverProgram();
+
   // The alternative class is:
   // - the other class than the single class if the resolution resolves into multiple classes,
   // - null if the resolution resolves into a single class.
-  DexClass toAlternativeClassWithProgramOverLibrary();
+  DexClass toAlternativeClass();
 
   void forEachClassResolutionResult(Consumer<DexClass> consumer);
 
@@ -95,7 +97,12 @@
     }
 
     @Override
-    public DexClass toAlternativeClassWithProgramOverLibrary() {
+    public DexClass toSingleClassWithLibraryOverProgram() {
+      return null;
+    }
+
+    @Override
+    public DexClass toAlternativeClass() {
       return null;
     }
 
@@ -147,7 +154,12 @@
     }
 
     @Override
-    public DexClass toAlternativeClassWithProgramOverLibrary() {
+    public DexClass toSingleClassWithLibraryOverProgram() {
+      return libraryClass;
+    }
+
+    @Override
+    public DexClass toAlternativeClass() {
       return libraryClass;
     }
   }
@@ -166,7 +178,12 @@
     }
 
     @Override
-    public DexClass toAlternativeClassWithProgramOverLibrary() {
+    public DexClass toSingleClassWithLibraryOverProgram() {
+      return libraryClass;
+    }
+
+    @Override
+    public DexClass toAlternativeClass() {
       return programOrClasspathClass;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index f10a654..265fc65 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -156,7 +156,12 @@
   }
 
   @Override
-  public DexClass toAlternativeClassWithProgramOverLibrary() {
+  public DexClass toSingleClassWithLibraryOverProgram() {
+    return this;
+  }
+
+  @Override
+  public DexClass toAlternativeClass() {
     return null;
   }
 
@@ -287,6 +292,10 @@
     return Iterables.filter(virtualMethods(), predicate::test);
   }
 
+  public void addVirtualMethod(DexEncodedMethod method) {
+    methodCollection.addVirtualMethod(method);
+  }
+
   public void addVirtualMethods(Collection<DexEncodedMethod> methods) {
     methodCollection.addVirtualMethods(methods);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 73e3420..51a4a4a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1100,8 +1100,14 @@
             .build());
   }
 
+  public DexEncodedMethod toForwardingMethod(DexClass newHolder, AppView<?> definitions) {
+    return toForwardingMethod(newHolder, definitions, ConsumerUtils.emptyConsumer());
+  }
+
   public DexEncodedMethod toForwardingMethod(
-      DexClass newHolder, DexDefinitionSupplier definitions) {
+      DexClass newHolder,
+      AppView<?> definitions,
+      Consumer<DexEncodedMethod.Builder> builderConsumer) {
     DexMethod newMethod = getReference().withHolder(newHolder, definitions.dexItemFactory());
     checkIfObsolete();
 
@@ -1147,6 +1153,7 @@
                     .modifyAccessFlags(MethodAccessFlags::setBridge))
         .setIsLibraryMethodOverrideIf(
             !isStatic() && !isLibraryMethodOverride().isUnknown(), isLibraryMethodOverride())
+        .apply(builderConsumer)
         .build();
   }
 
@@ -1471,6 +1478,16 @@
       }
 
       if (argumentInfoCollection.hasArgumentPermutation()) {
+        // If we have missing parameter annotations we cannot reliably reorder without handling
+        // missing annotations. We could introduce empty annotations to fill in empty spots but the
+        // missing parameters are only bridged in the reflection api for enums or local/anonymous
+        // classes and permuting such method arguments destroys the "invariant" that these are
+        // shifted.
+        // Having a keep on the members will automatically remove the permutation so the developer
+        // can easily recover.
+        if (newNumberOfMissingParameterAnnotations > 0) {
+          return setParameterAnnotations(ParameterAnnotationsList.empty());
+        }
         List<DexAnnotationSet> newPermutedParameterAnnotations =
             Arrays.asList(new DexAnnotationSet[method.getParameters().size()]);
         for (int parameterIndex = newNumberOfMissingParameterAnnotations;
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index e9f0446..5c8046f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -2045,6 +2045,7 @@
     public final DexMethod appendObject;
     public final DexMethod appendString;
     public final DexMethod appendStringBuffer;
+    public final DexMethod capacity;
     public final DexMethod charSequenceConstructor;
     public final DexMethod defaultConstructor;
     public final DexMethod intConstructor;
@@ -2056,7 +2057,6 @@
 
     private StringBuildingMethods(DexType receiver) {
       DexString append = createString("append");
-
       appendBoolean = createMethod(receiver, createProto(receiver, booleanType), append);
       appendChar = createMethod(receiver, createProto(receiver, charType), append);
       appendCharArray = createMethod(receiver, createProto(receiver, charArrayType), append);
@@ -2072,6 +2072,7 @@
       appendObject = createMethod(receiver, createProto(receiver, objectType), append);
       appendString = createMethod(receiver, createProto(receiver, stringType), append);
       appendStringBuffer = createMethod(receiver, createProto(receiver, stringBufferType), append);
+      capacity = createMethod(receiver, createProto(intType), createString("capacity"));
       charSequenceConstructor =
           createMethod(receiver, createProto(voidType, charSequenceType), constructorMethodName);
       defaultConstructor = createMethod(receiver, createProto(voidType), constructorMethodName);
@@ -2114,6 +2115,14 @@
       return method == appendObject;
     }
 
+    public boolean isAppendCharSequenceMethod(DexMethod method) {
+      return method == appendCharSequence;
+    }
+
+    public boolean isAppendObjectOrCharSequenceMethod(DexMethod method) {
+      return isAppendObjectMethod(method) || isAppendCharSequenceMethod(method);
+    }
+
     public boolean isAppendPrimitiveMethod(DexMethod method) {
       return appendPrimitiveMethods.contains(method);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index b49abbe..5e6c879 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -694,10 +694,6 @@
     methodCollection.addMethod(method);
   }
 
-  public void addVirtualMethod(DexEncodedMethod virtualMethod) {
-    methodCollection.addVirtualMethod(virtualMethod);
-  }
-
   public void replaceVirtualMethod(
       DexMethod virtualMethod, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
     methodCollection.replaceVirtualMethod(virtualMethod, replacement);
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java b/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
index bff5528..62531ba 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
@@ -18,7 +18,7 @@
     return toDexType(appView, join);
   }
 
-  private static DexType toDexType(
+  public static DexType toDexType(
       AppView<? extends AppInfoWithClassHierarchy> appView, TypeElement type) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     if (type.isPrimitiveType()) {
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 0237c6d..f669406 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -562,7 +562,7 @@
     }
 
     private void checkName(String name) {
-      if (!application.getFactory().getSkipNameValidationForTesting()
+      if (!application.options.canUseSpacesInSimpleName()
           && !DexString.isValidSimpleName(application.options.getMinApiLevel(), name)) {
         throw new CompilationError("Space characters in SimpleName '"
           + name + "' are not allowed prior to DEX version 040");
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
index 8730c35..5b43e8d 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -344,6 +344,15 @@
       return null;
     }
 
+    private static DexClass definitionForHelper(AppInfoWithClassHierarchy appInfo, DexType type) {
+      if (type == null) {
+        return null;
+      }
+      return appInfo
+          .contextIndependentDefinitionForWithResolutionResult(type)
+          .toSingleClassWithLibraryOverProgram();
+    }
+
     /**
      * Lookup the target of an invoke-super.
      *
@@ -447,7 +456,7 @@
           && !symbolicReference.isInterface()
           && isSuperclass.test(symbolicReference, context)) {
         // If reference is a super type of the context then search starts at the immediate super.
-        initialType = context.superType == null ? null : appInfo.definitionFor(context.superType);
+        initialType = definitionForHelper(appInfo, context.superType);
       } else {
         // Otherwise it starts at the reference itself.
         initialType = symbolicReference;
@@ -465,7 +474,7 @@
         if (target != null) {
           break;
         }
-        current = current.superType == null ? null : appInfo.definitionFor(current.superType);
+        current = definitionForHelper(appInfo, current.superType);
       }
       // 4. Otherwise, it is the single maximally specific method:
       if (target == null) {
@@ -698,7 +707,7 @@
         Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
       if (lambdaInstance.getMainMethod().match(resolvedMethod)) {
         DexMethod methodReference = lambdaInstance.implHandle.asMethod();
-        DexClass holder = appInfo.definitionForHolder(methodReference);
+        DexClass holder = definitionForHelper(appInfo, methodReference.getHolderType());
         DexClassAndMethod method = methodReference.lookupMemberOnClass(holder);
         if (method == null) {
           // The targeted method might not exist, eg, Throwable.addSuppressed in an old library.
@@ -743,7 +752,7 @@
           if (current.type == overrideTarget.getHolderType()) {
             return null;
           }
-          current = current.superType == null ? null : appInfo.definitionFor(current.superType);
+          current = definitionForHelper(appInfo, current.superType);
           continue;
         }
         DexClassAndMethod target = DexClassAndMethod.create(current, candidate);
@@ -817,11 +826,11 @@
     }
 
     private static DexClassAndMethod findWideningOverride(
-        DexClassAndMethod resolvedMethod, DexClass clazz, AppInfoWithClassHierarchy appView) {
+        DexClassAndMethod resolvedMethod, DexClass clazz, AppInfoWithClassHierarchy appInfo) {
       // Otherwise, lookup to first override that is distinct from resolvedMethod.
       assert resolvedMethod.getDefinition().accessFlags.isPackagePrivate();
       while (clazz.superType != null) {
-        clazz = appView.definitionFor(clazz.superType);
+        clazz = definitionForHelper(appInfo, clazz.superType);
         if (clazz == null) {
           return resolvedMethod;
         }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java
index aebbc12..30a6dfa 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClasspathOrLibraryClass;
 import com.android.tools.r8.graph.DexClassAndMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.LookupTarget;
@@ -70,6 +71,11 @@
   }
 
   @Override
+  public void processNewLiveNonProgramType(ClasspathOrLibraryClass clazz) {
+    clazz.forEachClassMethod(this::computeAndSetApiLevelForDefinition);
+  }
+
+  @Override
   public void notifyMarkVirtualDispatchTargetAsLive(
       LookupTarget target, EnqueuerWorklist worklist) {
     target.accept(
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
index 8dc26d6..d587b61 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.graph.analysis;
 
+import com.android.tools.r8.graph.ClasspathOrLibraryClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupTarget;
@@ -35,6 +36,9 @@
       Enqueuer enqueuer,
       EnqueuerWorklist worklist) {}
 
+  /** Called when a non program class is visited and marked live */
+  public void processNewLiveNonProgramType(ClasspathOrLibraryClass clazz) {}
+
   /** Called when a method's code has been processed by the registry. */
   public void processTracedCode(
       ProgramMethod method, DefaultEnqueuerUseRegistry registry, EnqueuerWorklist worklist) {}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractInstruction.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractInstruction.java
new file mode 100644
index 0000000..d68129f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractInstruction.java
@@ -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.ir.analysis.framework.intraprocedural;
+
+public interface AbstractInstruction {
+
+  boolean instructionTypeCanThrow();
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractState.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractState.java
index 914cc9e..f940edf 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractState.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.analysis.framework.intraprocedural;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.code.BasicBlock;
 
 /** The abstract state of the dataflow analysis, which is computed for each {@link BasicBlock}. */
@@ -16,10 +17,10 @@
     return asAbstractState();
   }
 
-  public abstract StateType join(StateType state);
+  public abstract StateType join(AppView<?> appView, StateType state);
 
-  public boolean isGreaterThanOrEquals(StateType state) {
-    StateType leastUpperBound = join(state);
+  public boolean isGreaterThanOrEquals(AppView<?> appView, StateType state) {
+    StateType leastUpperBound = join(appView, state);
     return equals(leastUpperBound);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java
index 4e73e2a..ad58a5c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.analysis.framework.intraprocedural;
 
+import com.android.tools.r8.graph.DexType;
+
 /**
  * A transfer function that defines the abstract semantics of the instructions in the program
  * according to some abstract state {@link StateType}.
@@ -11,14 +13,48 @@
 public interface AbstractTransferFunction<
     Block, Instruction, StateType extends AbstractState<StateType>> {
 
+  /** Applies the effect of the given instruction on the given abstract state. */
   TransferFunctionResult<StateType> apply(Instruction instruction, StateType state);
 
+  default TransferFunctionResult<StateType> applyBlock(Block block, StateType state) {
+    return state;
+  }
+
+  /**
+   * Computes the analysis state for the method entry point, i.e., the state prior to the first
+   * instruction.
+   */
   default StateType computeInitialState(Block entryBlock, StateType bottom) {
     return bottom;
   }
 
+  /** Transfers the state from predecessor block to its successor block. */
   default StateType computeBlockEntryState(
       Block block, Block predecessor, StateType predecessorExitState) {
     return predecessorExitState;
   }
+
+  /**
+   * Returns true if (a function of) the abstract state at the given (throwing) instruction should
+   * be transferred to the active catch handlers.
+   */
+  default boolean shouldTransferExceptionalControlFlowFromInstruction(
+      Block throwBlock, Instruction throwInstruction) {
+    return true;
+  }
+
+  /**
+   * Transfers the state from the given (throwing) instruction to its catch handler.
+   *
+   * <p>Only called if {@link #shouldTransferExceptionalControlFlowFromInstruction} has returned
+   * true.
+   */
+  default StateType computeExceptionalBlockEntryState(
+      Block block,
+      DexType guard,
+      Block throwBlock,
+      Instruction throwInstruction,
+      StateType throwState) {
+    return throwState;
+  }
 }
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 db0d7ca..51e185c 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
@@ -4,20 +4,51 @@
 
 package com.android.tools.r8.ir.analysis.framework.intraprocedural;
 
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.TraversalUtils;
+import com.android.tools.r8.utils.TriFunction;
+import java.util.Collection;
+import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
 public interface ControlFlowGraph<Block, Instruction> {
 
+  Collection<? extends Block> getBlocks();
+
   Block getEntryBlock();
 
+  default Block getUniquePredecessor(Block block) {
+    assert hasUniquePredecessor(block);
+    return TraversalUtils.getFirst(collector -> traversePredecessors(block, collector));
+  }
+
+  default Block getUniqueSuccessor(Block block) {
+    assert hasUniqueSuccessor(block);
+    return TraversalUtils.getFirst(collector -> traverseSuccessors(block, collector));
+  }
+
+  default boolean hasExceptionalPredecessors(Block block) {
+    return TraversalUtils.hasNext(counter -> traverseExceptionalPredecessors(block, counter));
+  }
+
+  default boolean hasExceptionalSuccessors(Block block) {
+    return TraversalUtils.hasNext(
+        counter ->
+            traverseExceptionalSuccessors(
+                block, (exceptionalSuccessor, guard) -> counter.apply(exceptionalSuccessor)));
+  }
+
   default boolean hasUniquePredecessor(Block block) {
     return TraversalUtils.isSingleton(counter -> traversePredecessors(block, counter));
   }
 
+  default boolean hasUniquePredecessorWithUniqueSuccessor(Block block) {
+    return hasUniquePredecessor(block) && hasUniqueSuccessor(getUniquePredecessor(block));
+  }
+
   default boolean hasUniqueSuccessor(Block block) {
     return TraversalUtils.isSingleton(counter -> traverseSuccessors(block, counter));
   }
@@ -26,11 +57,6 @@
     return hasUniqueSuccessor(block) && hasUniquePredecessor(getUniqueSuccessor(block));
   }
 
-  default Block getUniqueSuccessor(Block block) {
-    assert hasUniqueSuccessor(block);
-    return TraversalUtils.getFirst(collector -> traverseSuccessors(block, collector));
-  }
-
   // Block traversal.
 
   default <BT, CT> TraversalContinuation<BT, CT> traversePredecessors(
@@ -60,8 +86,9 @@
   }
 
   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 block, BiFunction<? super Block, DexType, TraversalContinuation<BT, CT>> fn) {
+    return traverseExceptionalSuccessors(
+        block, (successor, guard, ignore) -> fn.apply(successor, guard), null);
   }
 
   // Block traversal with result.
@@ -93,7 +120,10 @@
     return traverseNormalSuccessors(block, fn, initialValue)
         .ifContinueThen(
             continuation ->
-                traverseExceptionalSuccessors(block, fn, continuation.getValueOrDefault(null)));
+                traverseExceptionalSuccessors(
+                    block,
+                    (exceptionalSuccessor, guard, value) -> fn.apply(exceptionalSuccessor, value),
+                    continuation.getValueOrDefault(null)));
   }
 
   <BT, CT> TraversalContinuation<BT, CT> traverseNormalSuccessors(
@@ -103,7 +133,7 @@
 
   <BT, CT> TraversalContinuation<BT, CT> traverseExceptionalSuccessors(
       Block block,
-      BiFunction<? super Block, ? super CT, TraversalContinuation<BT, CT>> fn,
+      TriFunction<? super Block, DexType, ? super CT, TraversalContinuation<BT, CT>> fn,
       CT initialValue);
 
   // Block iteration.
@@ -133,7 +163,8 @@
 
   default void forEachSuccessor(Block block, Consumer<Block> consumer) {
     forEachNormalSuccessor(block, consumer);
-    forEachExceptionalSuccessor(block, consumer);
+    forEachExceptionalSuccessor(
+        block, (exceptionalSuccessor, guard) -> consumer.accept(exceptionalSuccessor));
   }
 
   default void forEachNormalSuccessor(Block block, Consumer<Block> consumer) {
@@ -145,11 +176,11 @@
         });
   }
 
-  default void forEachExceptionalSuccessor(Block block, Consumer<Block> consumer) {
+  default void forEachExceptionalSuccessor(Block block, BiConsumer<Block, DexType> consumer) {
     traverseExceptionalSuccessors(
         block,
-        exceptionalSuccessor -> {
-          consumer.accept(exceptionalSuccessor);
+        (exceptionalSuccessor, guard) -> {
+          consumer.accept(exceptionalSuccessor, guard);
           return TraversalContinuation.doContinue();
         });
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
index 8974bbf..73eeb55 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.analysis.framework.intraprocedural;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.code.BasicBlock;
 import java.util.Map;
 
@@ -43,10 +44,10 @@
       this.blockExitStates = blockExitStates;
     }
 
-    public StateType join() {
+    public StateType join(AppView<?> appView) {
       StateType result = null;
       for (StateType blockExitState : blockExitStates.values()) {
-        result = result != null ? result.join(blockExitState) : blockExitState;
+        result = result != null ? result.join(appView, blockExitState) : blockExitState;
       }
       return result;
     }
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 2cc7448..3a8b75c 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
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.analysis.framework.intraprocedural;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.FailedDataflowAnalysisResult;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.SuccessfulDataflowAnalysisResult;
 import com.android.tools.r8.utils.Timing;
@@ -25,7 +26,9 @@
  * FailedDataflowAnalysisResult}.
  */
 public class IntraProceduralDataflowAnalysisBase<
-    Block, Instruction, StateType extends AbstractState<StateType>> {
+    Block, Instruction extends AbstractInstruction, StateType extends AbstractState<StateType>> {
+
+  final AppView<?> appView;
 
   final StateType bottom;
 
@@ -34,21 +37,30 @@
   // The transfer function that defines the abstract semantics for each instruction.
   final AbstractTransferFunction<Block, Instruction, StateType> transfer;
 
-  // The state of the analysis.
-  final Map<Block, StateType> blockExitStates = new IdentityHashMap<>();
-
   // The entry states for each block that satisfies the predicate
   // shouldCacheBlockEntryStateFor(block). These entry states can be computed from the exit states
   // of the predecessors, but doing so can be expensive when a block has many predecessors.
-  final Map<Block, StateType> blockEntryStatesCache = new IdentityHashMap<>();
+  final Map<Block, StateType> blockEntryStates = new IdentityHashMap<>();
+
+  // The state of the analysis.
+  final Map<Block, StateType> blockExitStates = new IdentityHashMap<>();
+
+  // The entry states for exceptional blocks.
+  final Map<Block, StateType> exceptionalBlockEntryStates = new IdentityHashMap<>();
+
+  final IntraProceduralDataflowAnalysisOptions options;
 
   public IntraProceduralDataflowAnalysisBase(
+      AppView<?> appView,
       StateType bottom,
       ControlFlowGraph<Block, Instruction> cfg,
-      AbstractTransferFunction<Block, Instruction, StateType> transfer) {
+      AbstractTransferFunction<Block, Instruction, StateType> transfer,
+      IntraProceduralDataflowAnalysisOptions options) {
+    this.appView = appView;
     this.bottom = bottom;
     this.cfg = cfg;
     this.transfer = transfer;
+    this.options = options;
   }
 
   public DataflowAnalysisResult run(Block root) {
@@ -69,12 +81,27 @@
       StateType state =
           timing.time("Compute block entry state", () -> computeBlockEntryState(initialBlock));
 
+      TransferFunctionResult<StateType> blockResult = transfer.applyBlock(initialBlock, state);
+      if (blockResult.isFailedTransferResult()) {
+        return new FailedDataflowAnalysisResult();
+      }
+      state = blockResult.asAbstractState();
+
       timing.begin("Compute transfers");
       do {
+        Block currentBlock = block;
+        boolean hasExceptionalSuccessors = cfg.hasExceptionalSuccessors(block);
         TraversalContinuation<FailedDataflowAnalysisResult, StateType> traversalContinuation =
             cfg.traverseInstructions(
                 block,
                 (instruction, previousState) -> {
+                  if (instruction.instructionTypeCanThrow()
+                      && hasExceptionalSuccessors
+                      && transfer.shouldTransferExceptionalControlFlowFromInstruction(
+                          currentBlock, instruction)) {
+                    updateBlockEntryStateCacheForExceptionalSuccessors(
+                        currentBlock, instruction, previousState);
+                  }
                   TransferFunctionResult<StateType> transferResult =
                       transfer.apply(instruction, previousState);
                   if (transferResult.isFailedTransferResult()) {
@@ -89,7 +116,7 @@
           return traversalContinuation.asBreak().getValue();
         }
         state = traversalContinuation.asContinue().getValue();
-        if (cfg.hasUniqueSuccessorWithUniquePredecessor(block)) {
+        if (isBlockWithIntermediateSuccessorBlock(block)) {
           block = cfg.getUniqueSuccessor(block);
         } else {
           end = block;
@@ -104,53 +131,97 @@
         cfg.forEachSuccessor(end, worklist::addIgnoringSeenSet);
       }
 
-      // Add the computed exit state to the entry state of each successor that satisfies the
+      // Add the computed exit state to the entry state of each normal successor that satisfies the
       // predicate shouldCacheBlockEntryStateFor(successor).
-      updateBlockEntryStateCacheForSuccessors(end, state);
+      updateBlockEntryStateCacheForNormalSuccessors(end, state);
     }
     return new SuccessfulDataflowAnalysisResult<>(blockExitStates);
   }
 
   public StateType computeBlockEntryState(Block block) {
     if (block == cfg.getEntryBlock()) {
-      return transfer.computeInitialState(block, bottom);
+      return transfer
+          .computeInitialState(block, bottom)
+          .join(appView, computeBlockEntryStateForNormalBlock(block));
     }
-    if (shouldCacheBlockEntryStateFor(block)) {
-      return blockEntryStatesCache.getOrDefault(block, bottom);
+    if (cfg.hasExceptionalPredecessors(block)) {
+      return exceptionalBlockEntryStates.getOrDefault(block, bottom).clone();
     }
+    return computeBlockEntryStateForNormalBlock(block);
+  }
+
+  private StateType computeBlockEntryStateForNormalBlock(Block block) {
+    if (shouldCacheBlockEntryStateForNormalBlock(block)) {
+      return blockEntryStates.getOrDefault(block, bottom).clone();
+    }
+    return computeBlockEntryStateFromPredecessorExitStates(block);
+  }
+
+  private StateType computeBlockEntryStateFromPredecessorExitStates(Block block) {
     TraversalContinuation<?, StateType> traversalContinuation =
-        cfg.traversePredecessors(
+        cfg.traverseNormalPredecessors(
             block,
             (predecessor, entryState) -> {
               StateType edgeState =
                   transfer.computeBlockEntryState(
-                      block, predecessor, blockExitStates.getOrDefault(predecessor, bottom));
-              return TraversalContinuation.doContinue(entryState.join(edgeState));
+                      block,
+                      predecessor,
+                      blockExitStates.getOrDefault(predecessor, bottom).clone());
+              return TraversalContinuation.doContinue(entryState.join(appView, edgeState));
             },
             bottom);
     return traversalContinuation.asContinue().getValue().clone();
   }
 
   boolean setBlockExitState(Block block, StateType state) {
-    assert !cfg.hasUniqueSuccessorWithUniquePredecessor(block);
+    assert !isBlockWithIntermediateSuccessorBlock(block);
     StateType previous = blockExitStates.put(block, state);
-    assert previous == null || state.isGreaterThanOrEquals(previous);
+    assert previous == null || state.isGreaterThanOrEquals(appView, previous);
     return !state.equals(previous);
   }
 
-  void updateBlockEntryStateCacheForSuccessors(Block block, StateType state) {
-    cfg.forEachSuccessor(
+  void updateBlockEntryStateCacheForNormalSuccessors(Block block, StateType state) {
+    cfg.forEachNormalSuccessor(
         block,
         successor -> {
-          if (shouldCacheBlockEntryStateFor(successor)) {
+          if (shouldCacheBlockEntryStateForNormalBlock(successor)) {
             StateType edgeState = transfer.computeBlockEntryState(successor, block, state);
-            StateType previous = blockEntryStatesCache.getOrDefault(successor, bottom);
-            blockEntryStatesCache.put(successor, previous.join(edgeState));
+            updateBlockEntryStateForBlock(successor, edgeState, blockEntryStates);
           }
         });
   }
 
-  boolean shouldCacheBlockEntryStateFor(Block block) {
+  void updateBlockEntryStateCacheForExceptionalSuccessors(
+      Block block, Instruction instruction, StateType state) {
+    cfg.forEachExceptionalSuccessor(
+        block,
+        (exceptionalSuccessor, guard) -> {
+          StateType edgeState =
+              transfer.computeExceptionalBlockEntryState(
+                  exceptionalSuccessor, guard, block, instruction, state);
+          updateBlockEntryStateForBlock(
+              exceptionalSuccessor, edgeState, exceptionalBlockEntryStates);
+        });
+  }
+
+  private void updateBlockEntryStateForBlock(
+      Block block, StateType edgeState, Map<Block, StateType> states) {
+    StateType previous = states.getOrDefault(block, bottom);
+    states.put(block, previous.join(appView, edgeState));
+  }
+
+  public boolean isIntermediateBlock(Block block) {
+    return options.isCollapsingOfTrivialEdgesEnabled()
+        && cfg.hasUniquePredecessorWithUniqueSuccessor(block)
+        && block != cfg.getEntryBlock()
+        && !cfg.hasExceptionalPredecessors(block);
+  }
+
+  public boolean isBlockWithIntermediateSuccessorBlock(Block block) {
+    return cfg.hasUniqueSuccessor(block) && isIntermediateBlock(cfg.getUniqueSuccessor(block));
+  }
+
+  boolean shouldCacheBlockEntryStateForNormalBlock(Block block) {
     return TraversalUtils.isSizeGreaterThan(counter -> cfg.traversePredecessors(block, counter), 2);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraProceduralDataflowAnalysisOptions.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraProceduralDataflowAnalysisOptions.java
new file mode 100644
index 0000000..c079bbf
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraProceduralDataflowAnalysisOptions.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;
+
+public class IntraProceduralDataflowAnalysisOptions {
+
+  private static final IntraProceduralDataflowAnalysisOptions COLLAPSE_INSTANCE =
+      new IntraProceduralDataflowAnalysisOptions(true);
+  private static final IntraProceduralDataflowAnalysisOptions NO_COLLAPSE_INSTANCE =
+      new IntraProceduralDataflowAnalysisOptions(false);
+
+  private final boolean isCollapsingOfTrivialEdgesEnabled;
+
+  IntraProceduralDataflowAnalysisOptions(boolean isCollapsingOfTrivialEdgesEnabled) {
+    this.isCollapsingOfTrivialEdgesEnabled = isCollapsingOfTrivialEdgesEnabled;
+  }
+
+  public boolean isCollapsingOfTrivialEdgesEnabled() {
+    return isCollapsingOfTrivialEdgesEnabled;
+  }
+
+  public static IntraProceduralDataflowAnalysisOptions getCollapseInstance() {
+    return COLLAPSE_INSTANCE;
+  }
+
+  public static IntraProceduralDataflowAnalysisOptions getNoCollapseInstance() {
+    return NO_COLLAPSE_INSTANCE;
+  }
+}
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 789a2cb..359456e 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
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.analysis.framework.intraprocedural;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -12,14 +13,29 @@
     extends IntraProceduralDataflowAnalysisBase<BasicBlock, Instruction, StateType> {
 
   public IntraproceduralDataflowAnalysis(
+      AppView<?> appView,
       StateType bottom,
       IRCode code,
       AbstractTransferFunction<BasicBlock, Instruction, StateType> transfer) {
-    super(bottom, code, transfer);
+    this(
+        appView,
+        bottom,
+        code,
+        transfer,
+        IntraProceduralDataflowAnalysisOptions.getCollapseInstance());
+  }
+
+  public IntraproceduralDataflowAnalysis(
+      AppView<?> appView,
+      StateType bottom,
+      IRCode code,
+      AbstractTransferFunction<BasicBlock, Instruction, StateType> transfer,
+      IntraProceduralDataflowAnalysisOptions options) {
+    super(appView, bottom, code, transfer, options);
   }
 
   @Override
-  boolean shouldCacheBlockEntryStateFor(BasicBlock block) {
+  boolean shouldCacheBlockEntryStateForNormalBlock(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
index 357ff33..dc0c539 100644
--- 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
@@ -6,9 +6,13 @@
 
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.StringUtils;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
@@ -19,6 +23,9 @@
   // The CfCode instruction index of the block's first instruction.
   int firstInstructionIndex = -1;
 
+  // The CfCode instruction index of the block's first throwing instruction.
+  int firstThrowingInstructionIndex = -1;
+
   // The CfCode instruction index of the block's last instruction.
   int lastInstructionIndex = -1;
 
@@ -30,7 +37,7 @@
   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<>();
+  final LinkedHashMap<DexType, CfBlock> exceptionalSuccessors = new LinkedHashMap<>();
 
   public CfInstruction getFallthroughInstruction(CfCode code) {
     int fallthroughInstructionIndex = getLastInstructionIndex() + 1;
@@ -43,6 +50,14 @@
     return firstInstructionIndex;
   }
 
+  public boolean hasThrowingInstruction() {
+    return firstThrowingInstructionIndex >= 0;
+  }
+
+  public int getFirstThrowingInstructionIndex() {
+    return firstThrowingInstructionIndex;
+  }
+
   public CfInstruction getLastInstruction(CfCode code) {
     return code.getInstructions().get(lastInstructionIndex);
   }
@@ -55,18 +70,30 @@
     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() {
+  public LinkedHashMap<DexType, CfBlock> getExceptionalSuccessors() {
     return exceptionalSuccessors;
   }
 
+  @Override
+  public String toString() {
+    List<String> predecessorStrings = new ArrayList<>();
+    predecessors.forEach(p -> predecessorStrings.add(p.getRangeString()));
+    exceptionalPredecessors.forEach(p -> predecessorStrings.add("*" + p.getRangeString()));
+    return "CfBlock(range="
+        + getRangeString()
+        + ", predecessors="
+        + StringUtils.join(", ", predecessorStrings)
+        + ")";
+  }
+
+  private String getRangeString() {
+    return firstInstructionIndex + "->" + lastInstructionIndex;
+  }
+
   // A mutable interface for block construction.
   static class MutableCfBlock extends CfBlock {
 
@@ -78,22 +105,35 @@
       exceptionalPredecessors.add(block);
     }
 
-    void addExceptionalSuccessor(CfBlock block) {
-      exceptionalSuccessors.add(block);
+    void addExceptionalSuccessor(CfBlock block, DexType guard) {
+      assert !exceptionalSuccessors.containsKey(guard);
+      exceptionalSuccessors.put(guard, block);
     }
 
     void setFirstInstructionIndex(int firstInstructionIndex) {
       this.firstInstructionIndex = firstInstructionIndex;
     }
 
+    void setFirstThrowingInstructionIndex(int firstThrowingInstructionIndex) {
+      this.firstThrowingInstructionIndex = firstThrowingInstructionIndex;
+    }
+
     void setLastInstructionIndex(int lastInstructionIndex) {
       this.lastInstructionIndex = lastInstructionIndex;
     }
 
-    boolean validate() {
+    boolean validate(CfControlFlowGraph cfg, InternalOptions options) {
       assert 0 <= firstInstructionIndex;
       assert firstInstructionIndex <= lastInstructionIndex;
+      assert firstThrowingInstructionIndex < 0
+          || firstInstructionIndex <= firstThrowingInstructionIndex;
+      assert firstThrowingInstructionIndex < 0
+          || firstThrowingInstructionIndex <= lastInstructionIndex;
       assert SetUtils.newIdentityHashSet(predecessors).size() == predecessors.size();
+      assert this == cfg.getEntryBlock()
+          || !predecessors.isEmpty()
+          || !exceptionalPredecessors.isEmpty()
+          || options.getCfCodeAnalysisOptions().isUnreachableCfBlocksAllowed();
       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
index ff69065..919e8d7 100644
--- 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
@@ -10,18 +10,22 @@
 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.graph.DexType;
 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.InternalOptions;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.TraversalUtils;
+import com.android.tools.r8.utils.TriFunction;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Collection;
+import java.util.Deque;
 import java.util.IdentityHashMap;
-import java.util.LinkedHashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.function.BiFunction;
 
 /**
@@ -48,8 +52,8 @@
     return new Builder(code);
   }
 
-  public static CfControlFlowGraph create(CfCode code) {
-    return builder(code).build();
+  public static CfControlFlowGraph create(CfCode code, InternalOptions options) {
+    return builder(code).build(options);
   }
 
   private CfBlock getBlock(CfInstruction blockEntry) {
@@ -58,8 +62,13 @@
   }
 
   @Override
+  public Collection<? extends CfBlock> getBlocks() {
+    return blocks.values();
+  }
+
+  @Override
   public CfBlock getEntryBlock() {
-    return getBlock(code.getInstructions().get(0));
+    return getBlock(code.getInstruction(0));
   }
 
   @Override
@@ -92,9 +101,12 @@
   @Override
   public <BT, CT> TraversalContinuation<BT, CT> traverseExceptionalSuccessors(
       CfBlock block,
-      BiFunction<? super CfBlock, ? super CT, TraversalContinuation<BT, CT>> fn,
+      TriFunction<? super CfBlock, DexType, ? super CT, TraversalContinuation<BT, CT>> fn,
       CT initialValue) {
-    return TraversalUtils.traverseIterable(block.getExceptionalSuccessors(), fn, initialValue);
+    return TraversalUtils.traverseMap(
+        block.getExceptionalSuccessors(),
+        (guard, exceptionalSuccessor, value) -> fn.apply(exceptionalSuccessor, guard, value),
+        initialValue);
   }
 
   @Override
@@ -107,7 +119,7 @@
     for (int instructionIndex = block.getFirstInstructionIndex();
         instructionIndex <= block.getLastInstructionIndex();
         instructionIndex++) {
-      CfInstruction instruction = code.getInstructions().get(instructionIndex);
+      CfInstruction instruction = code.getInstruction(instructionIndex);
       traversalContinuation = fn.apply(instruction, traversalContinuation.asContinue().getValue());
       if (traversalContinuation.shouldBreak()) {
         break;
@@ -127,7 +139,7 @@
       this.code = code;
     }
 
-    CfControlFlowGraph build() {
+    CfControlFlowGraph build(InternalOptions options) {
       // Perform an initial pass over the CfCode to identify all instructions that start a new
       // block.
       createBlocks();
@@ -137,9 +149,11 @@
       // identified all block entries up front.
       processBlocks();
 
-      assert blocks.values().stream().allMatch(MutableCfBlock::validate);
+      removeBlockForTrailingLabel();
 
-      return new CfControlFlowGraph(blocks, code);
+      CfControlFlowGraph cfg = new CfControlFlowGraph(blocks, code);
+      assert blocks.values().stream().allMatch(block -> block.validate(cfg, options));
+      return cfg;
     }
 
     private void createBlocks() {
@@ -158,6 +172,11 @@
                   ? instructions.get(fallthroughInstructionIndex)
                   : null;
           instruction.forEachNormalTarget(this::createBlockIfAbsent, fallthroughInstruction);
+
+          // Also create a block for the fallthrough instruction, though it may be unreachable.
+          if (!instruction.asJump().hasFallthrough() && fallthroughInstruction != null) {
+            createBlockIfAbsent(fallthroughInstruction);
+          }
         }
       }
 
@@ -173,9 +192,9 @@
     }
 
     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 active catch handlers. The most recently added catch handlers take
+      // precedence over catch handlers added before them.
+      Deque<CfTryCatch> activeCatchHandlers = new ArrayDeque<>();
 
       // 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.
@@ -199,12 +218,12 @@
                   instruction,
                   instructionIndex,
                   block,
-                  activeUntilCatchHandlers,
+                  activeCatchHandlers,
                   inactiveUntilCatchHandlers);
         }
       }
 
-      assert activeUntilCatchHandlers.isEmpty();
+      assert activeCatchHandlers.isEmpty();
       assert inactiveUntilCatchHandlers.isEmpty();
     }
 
@@ -212,31 +231,34 @@
         CfInstruction instruction,
         int instructionIndex,
         MutableCfBlock block,
-        Map<CfLabel, List<CfTryCatch>> activeUntilCatchHandlers,
+        Deque<CfTryCatch> activeCatchHandlers,
         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);
+        updateCatchHandlers(instruction.asLabel(), activeCatchHandlers, inactiveUntilCatchHandlers);
       }
 
       // Visit each instruction belonging to the current block.
-      Set<CfLabel> exceptionalSuccessors = new LinkedHashSet<>();
+      Map<DexType, CfLabel> exceptionalSuccessors = new LinkedHashMap<>();
+      Iterator<CfTryCatch> activeCatchHandlerIterator = activeCatchHandlers.descendingIterator();
+      while (activeCatchHandlerIterator.hasNext()) {
+        CfTryCatch activeCatchHandler = activeCatchHandlerIterator.next();
+        activeCatchHandler.forEach(exceptionalSuccessors::putIfAbsent);
+      }
+
       do {
         assert !instruction.isLabel()
             || verifyCatchHandlersUnchanged(
-                instruction.asLabel(), activeUntilCatchHandlers, inactiveUntilCatchHandlers);
-        if (instruction.canThrow()) {
-          for (CfTryCatch tryCatch : IterableUtils.flatten(activeUntilCatchHandlers.values())) {
-            exceptionalSuccessors.addAll(tryCatch.getTargets());
-          }
+                instruction.asLabel(), activeCatchHandlers, inactiveUntilCatchHandlers);
+        if (instruction.instructionTypeCanThrow() && !block.hasThrowingInstruction()) {
+          block.setFirstThrowingInstructionIndex(instructionIndex);
         }
         if (isBlockExit(instructionIndex)) {
           break;
         }
-        instruction = code.getInstructions().get(++instructionIndex);
+        instruction = code.getInstruction(++instructionIndex);
       } while (true);
 
       // Record the index of the last instruction of the block.
@@ -249,15 +271,22 @@
 
       // Add the current block as an exceptional predecessor of the exceptional successor blocks.
       exceptionalSuccessors.forEach(
-          exceptionalSuccessor -> {
+          (guard, exceptionalSuccessor) -> {
             MutableCfBlock exceptionalSuccessorBlock = getBlock(exceptionalSuccessor);
-            block.addExceptionalSuccessor(exceptionalSuccessorBlock);
+            block.addExceptionalSuccessor(exceptionalSuccessorBlock, guard);
             exceptionalSuccessorBlock.addExceptionalPredecessor(block);
           });
 
       return instructionIndex;
     }
 
+    private void removeBlockForTrailingLabel() {
+      CfInstruction lastInstruction = code.getInstruction(code.getInstructions().size() - 1);
+      if (lastInstruction.isLabel() && isBlockEntry(lastInstruction)) {
+        blocks.remove(lastInstruction);
+      }
+    }
+
     private boolean isBlockEntry(CfInstruction instruction) {
       return blocks.containsKey(instruction);
     }
@@ -267,32 +296,35 @@
       if (instructionIndex == lastInstructionIndex) {
         return true;
       }
-      CfInstruction nextInstruction = code.getInstructions().get(instructionIndex + 1);
+      CfInstruction nextInstruction = code.getInstruction(instructionIndex + 1);
       return isBlockEntry(nextInstruction);
     }
 
     private void updateCatchHandlers(
         CfLabel instruction,
-        Map<CfLabel, List<CfTryCatch>> activeUntilCatchHandlers,
+        Deque<CfTryCatch> activeCatchHandlers,
         Map<CfLabel, List<CfTryCatch>> inactiveUntilCatchHandlers) {
       // Remove active catch handlers that have expired at the current instruction.
-      activeUntilCatchHandlers.remove(instruction);
+      activeCatchHandlers.removeIf(
+          activeCatchHandler -> activeCatchHandler.getEnd() == 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);
+      List<CfTryCatch> newlyActiveCatchHandlers = inactiveUntilCatchHandlers.remove(instruction);
+      if (newlyActiveCatchHandlers != null) {
+        for (int i = newlyActiveCatchHandlers.size() - 1; i >= 0; i--) {
+          CfTryCatch newlyActiveCatchHandler = newlyActiveCatchHandlers.get(i);
+          assert newlyActiveCatchHandler.getEnd() != newlyActiveCatchHandler.getStart();
+          activeCatchHandlers.addLast(newlyActiveCatchHandler);
+        }
       }
     }
 
     private boolean verifyCatchHandlersUnchanged(
         CfLabel instruction,
-        Map<CfLabel, List<CfTryCatch>> activeUntilCatchHandlers,
+        Deque<CfTryCatch> activeCatchHandlers,
         Map<CfLabel, List<CfTryCatch>> inactiveUntilCatchHandlers) {
-      assert !activeUntilCatchHandlers.containsKey(instruction);
+      assert activeCatchHandlers.stream()
+          .allMatch(activeCatchHandler -> activeCatchHandler.getEnd() != instruction);
       assert !inactiveUntilCatchHandlers.containsKey(instruction);
       return true;
     }
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
index b4ca696..cef6a5d 100644
--- 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
@@ -5,17 +5,25 @@
 package com.android.tools.r8.ir.analysis.framework.intraprocedural.cf;
 
 import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.graph.AppView;
 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;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.IntraProceduralDataflowAnalysisOptions;
 
 public class CfIntraproceduralDataflowAnalysis<StateType extends AbstractState<StateType>>
     extends IntraProceduralDataflowAnalysisBase<CfBlock, CfInstruction, StateType> {
 
   public CfIntraproceduralDataflowAnalysis(
+      AppView<?> appView,
       StateType bottom,
       CfControlFlowGraph cfg,
       AbstractTransferFunction<CfBlock, CfInstruction, StateType> transfer) {
-    super(bottom, cfg, transfer);
+    super(
+        appView,
+        bottom,
+        cfg,
+        transfer,
+        IntraProceduralDataflowAnalysisOptions.getCollapseInstance());
   }
 }
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 0d1bf75..432311b 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
@@ -26,6 +26,7 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
 import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.TriFunction;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableList;
@@ -243,14 +244,17 @@
   }
 
   public <BT, CT> TraversalContinuation<BT, CT> traverseExceptionalSuccessors(
-      BiFunction<? super BasicBlock, ? super CT, TraversalContinuation<BT, CT>> fn,
+      TriFunction<? super BasicBlock, DexType, ? 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));
+          fn.apply(
+              successors.get(i),
+              catchHandlers.getGuard(i),
+              traversalContinuation.asContinue().getValueOrDefault(null));
       if (traversalContinuation.isBreak()) {
         break;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
index 6005e1f..03771cd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
@@ -64,6 +64,10 @@
     return guards;
   }
 
+  public DexType getGuard(int index) {
+    return guards.get(index);
+  }
+
   public List<T> getAllTargets() {
     return targets;
   }
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 8f310c8..dbcd9be 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
@@ -35,6 +35,7 @@
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.TriFunction;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -1363,6 +1364,7 @@
     }
   }
 
+  @Override
   public LinkedList<BasicBlock> getBlocks() {
     return blocks;
   }
@@ -1402,7 +1404,7 @@
   @Override
   public <BT, CT> TraversalContinuation<BT, CT> traverseExceptionalSuccessors(
       BasicBlock block,
-      BiFunction<? super BasicBlock, ? super CT, TraversalContinuation<BT, CT>> fn,
+      TriFunction<? super BasicBlock, DexType, ? super CT, TraversalContinuation<BT, CT>> fn,
       CT initialValue) {
     return block.traverseExceptionalSuccessors(fn, initialValue);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index feaca12..7cb6f50 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractInstruction;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
@@ -46,7 +47,8 @@
 import java.util.function.Function;
 import java.util.function.Predicate;
 
-public abstract class Instruction implements InstructionOrPhi, TypeAndLocalInfoSupplier {
+public abstract class Instruction
+    implements AbstractInstruction, InstructionOrPhi, TypeAndLocalInfoSupplier {
 
   protected Value outValue = null;
   protected final List<Value> inValues = new ArrayList<>();
@@ -604,9 +606,8 @@
     return false;
   }
 
-  /**
-   * Returns true if this instruction may throw an exception.
-   */
+  /** Returns true if this instruction may throw an exception. */
+  @Override
   public boolean instructionTypeCanThrow() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 86fa768..4f28ee9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -637,8 +637,16 @@
   }
 
   private DexType convertUninitialized(FrameType type) {
-    if (type.isInitialized()) {
-      return type.getInitializedType(appView.dexItemFactory());
+    if (type.isInitializedReferenceType()) {
+      return type.asInitializedReferenceType().getInitializedType();
+    }
+    if (type.isPrimitive()) {
+      if (type.isSinglePrimitive()) {
+        return type.asSinglePrimitive().getInitializedType(appView.dexItemFactory());
+      } else {
+        assert type.isWidePrimitive();
+        return type.asWidePrimitive().getLowType().getInitializedType(appView.dexItemFactory());
+      }
     }
     if (type.isUninitializedNew()) {
       int labelOffset = getLabelOffset(type.getUninitializedLabel());
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 85ac399..70fddd9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -15,11 +15,14 @@
 import static com.android.tools.r8.ir.analysis.type.TypeElement.getSingle;
 import static com.android.tools.r8.ir.analysis.type.TypeElement.getWide;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.ConstMethodHandleDiagnostic;
+import com.android.tools.r8.errors.ConstMethodTypeDiagnostic;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
+import com.android.tools.r8.errors.InvokePolymorphicMethodHandleDiagnostic;
+import com.android.tools.r8.errors.InvokePolymorphicVarHandleDiagnostic;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
@@ -121,7 +124,7 @@
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.utils.Pair;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
@@ -1224,10 +1227,9 @@
 
   public void addConstMethodHandle(int dest, DexMethodHandle methodHandle) {
     if (!appView.options().canUseConstantMethodHandle()) {
-      throw new ApiLevelException(
-          AndroidApiLevel.P,
-          "Const-method-handle",
-          null /* sourceString */);
+      throw appView
+          .reporter()
+          .fatalError(new ConstMethodHandleDiagnostic(origin, MethodPosition.create(method)));
     }
     TypeElement typeLattice =
         TypeElement.fromDexType(
@@ -1239,10 +1241,9 @@
 
   public void addConstMethodType(int dest, DexProto methodType) {
     if (!appView.options().canUseConstantMethodType()) {
-      throw new ApiLevelException(
-          AndroidApiLevel.P,
-          "Const-method-type",
-          null /* sourceString */);
+      throw appView
+          .reporter()
+          .fatalError(new ConstMethodTypeDiagnostic(origin, MethodPosition.create(method)));
     }
     TypeElement typeLattice =
         TypeElement.fromDexType(
@@ -1506,16 +1507,16 @@
     if (type == Type.POLYMORPHIC) {
       assert item instanceof DexMethod;
       if (!appView.options().canUseInvokePolymorphic()) {
-        throw new ApiLevelException(
-            AndroidApiLevel.O,
-            "MethodHandle.invoke and MethodHandle.invokeExact",
-            null /* sourceString */);
+        throw appView
+            .reporter()
+            .fatalError(
+                new InvokePolymorphicMethodHandleDiagnostic(origin, MethodPosition.create(method)));
       } else if (!appView.options().canUseInvokePolymorphicOnVarHandle()
           && ((DexMethod) item).holder == appView.dexItemFactory().varHandleType) {
-        throw new ApiLevelException(
-            AndroidApiLevel.P,
-            "Call to polymorphic signature of VarHandle",
-            null /* sourceString */);
+        throw appView
+            .reporter()
+            .fatalError(
+                new InvokePolymorphicVarHandleDiagnostic(origin, MethodPosition.create(method)));
       }
     }
     add(Invoke.create(type, item, callSiteProto, null, arguments, itf));
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 f94cde1..e504e3d 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
@@ -502,7 +502,7 @@
 
     // The class file version is downgraded after compilation. Some of the desugaring might need
     // the initial class file version to determine how far a method can be downgraded.
-    if (clazz.hasClassFileVersion()) {
+    if (options.isGeneratingClassFiles() && clazz.hasClassFileVersion()) {
       clazz.downgradeInitialClassFileVersion(
           appView.options().classFileVersionAfterDesugaring(clazz.getInitialClassFileVersion()));
     }
@@ -514,7 +514,7 @@
       MethodProcessor methodProcessor,
       MethodProcessingContext methodProcessingContext) {
     DexEncodedMethod definition = method.getDefinition();
-    if (definition.hasClassFileVersion()) {
+    if (options.isGeneratingClassFiles() && definition.hasClassFileVersion()) {
       definition.downgradeClassFileVersion(
           appView.options().classFileVersionAfterDesugaring(definition.getClassFileVersion()));
     }
@@ -552,7 +552,7 @@
     if (options.testing.forceIRForCfToCfDesugar) {
       return true;
     }
-    return !options.cfToCfDesugar;
+    return !options.isCfDesugaring();
   }
 
   private void checkPrefixMerging(ProgramMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 324ecfa..73aeb93 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -2169,13 +2169,13 @@
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
-                    new FrameType[] {FrameType.doubleType(), FrameType.doubleType()})),
+                    new FrameType[] {FrameType.doubleType(), FrameType.doubleHighType()})),
             new CfConstNumber(0, ValueType.INT),
             label2,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
-                    new FrameType[] {FrameType.doubleType(), FrameType.doubleType()}),
+                    new FrameType[] {FrameType.doubleType(), FrameType.doubleHighType()}),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
             new CfReturn(ValueType.INT),
             label3),
@@ -2729,7 +2729,7 @@
                       FrameType.initialized(options.itemFactory.stringType),
                       FrameType.intType(),
                       FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     })),
             new CfLoad(ValueType.LONG, 2),
             new CfNumberConversion(NumericType.LONG, NumericType.INT),
@@ -2943,13 +2943,13 @@
                     new int[] {0, 1, 2, 3, 4, 5, 6, 7},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     })),
             new CfConstNumber(1, ValueType.LONG),
             new CfReturn(ValueType.LONG),
@@ -2959,9 +2959,9 @@
                     new int[] {0, 1, 2, 3},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     })),
             new CfLoad(ValueType.LONG, 0),
             new CfConstNumber(0, ValueType.LONG),
@@ -2978,9 +2978,9 @@
                     new int[] {0, 1, 2, 3},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     })),
             new CfLoad(ValueType.LONG, 0),
             new CfConstNumber(1, ValueType.INT),
@@ -3021,17 +3021,17 @@
                     new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.longType()))),
             new CfConstNumber(0, ValueType.INT),
@@ -3041,17 +3041,17 @@
                     new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.longType(), FrameType.intType()))),
             new CfNumberConversion(NumericType.INT, NumericType.LONG),
@@ -3426,7 +3426,7 @@
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     })),
             new CfLoad(ValueType.INT, 1),
             label8,
@@ -3440,7 +3440,7 @@
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
             new CfStore(ValueType.INT, 7),
@@ -3461,10 +3461,10 @@
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType(),
                       FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType()
                     })),
             new CfLoad(ValueType.INT, 10),
@@ -3528,10 +3528,10 @@
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType(),
                       FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType(),
                       FrameType.intType()
                     })),
@@ -3575,10 +3575,10 @@
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType(),
                       FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType(),
                       FrameType.intType()
                     })),
@@ -3621,10 +3621,10 @@
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType(),
                       FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType(),
                       FrameType.intType()
                     })),
@@ -3650,10 +3650,10 @@
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType(),
                       FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     })),
             new CfLoad(ValueType.LONG, 8),
             new CfReturn(ValueType.LONG),
@@ -3752,13 +3752,13 @@
                     new int[] {0, 1, 2, 3, 4, 5, 6, 7},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     })),
             new CfLoad(ValueType.LONG, 0),
             new CfLoad(ValueType.LONG, 2),
@@ -3770,9 +3770,9 @@
                     new int[] {0, 1, 2, 3},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     })),
             new CfLoad(ValueType.LONG, 0),
             new CfConstNumber(0, ValueType.LONG),
@@ -3789,9 +3789,9 @@
                     new int[] {0, 1, 2, 3},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     })),
             new CfLoad(ValueType.LONG, 0),
             new CfConstNumber(1, ValueType.INT),
@@ -3832,17 +3832,17 @@
                     new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.longType()))),
             new CfConstNumber(0, ValueType.LONG),
@@ -3852,17 +3852,17 @@
                     new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.longType(), FrameType.longType()))),
             new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.LONG),
@@ -3946,7 +3946,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.longType(), FrameType.longType(), FrameType.intType()
+                      FrameType.longType(), FrameType.longHighType(), FrameType.intType()
                     })),
             new CfLoad(ValueType.LONG, 0),
             new CfConstNumber(0, ValueType.LONG),
@@ -3971,7 +3971,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.longType(), FrameType.longType(), FrameType.intType()
+                      FrameType.longType(), FrameType.longHighType(), FrameType.intType()
                     })),
             new CfLoad(ValueType.INT, 2),
             new CfConstNumber(2, ValueType.INT),
@@ -3984,7 +3984,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.longType(), FrameType.longType(), FrameType.intType()
+                      FrameType.longType(), FrameType.longHighType(), FrameType.intType()
                     })),
             new CfConstNumber(10, ValueType.INT),
             new CfStore(ValueType.INT, 2),
@@ -3993,7 +3993,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.longType(), FrameType.longType(), FrameType.intType()
+                      FrameType.longType(), FrameType.longHighType(), FrameType.intType()
                     })),
             new CfConstNumber(64, ValueType.INT),
             new CfNewArray(options.itemFactory.charArrayType),
@@ -4031,7 +4031,7 @@
                     new int[] {0, 1, 2, 3, 4, 5, 6},
                     new FrameType[] {
                       FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType(),
                       FrameType.initialized(options.itemFactory.charArrayType),
                       FrameType.intType(),
@@ -4075,7 +4075,7 @@
                     new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
                       FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType(),
                       FrameType.initialized(options.itemFactory.charArrayType),
                       FrameType.intType()
@@ -4102,7 +4102,7 @@
                     new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
                       FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType(),
                       FrameType.initialized(options.itemFactory.charArrayType),
                       FrameType.intType()
@@ -4127,12 +4127,12 @@
                     new int[] {0, 1, 2, 3, 4, 5, 6},
                     new FrameType[] {
                       FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType(),
                       FrameType.initialized(options.itemFactory.charArrayType),
                       FrameType.intType(),
                       FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     })),
             new CfLoad(ValueType.LONG, 0),
             new CfLoad(ValueType.LONG, 5),
@@ -4168,14 +4168,14 @@
                     new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8},
                     new FrameType[] {
                       FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType(),
                       FrameType.initialized(options.itemFactory.charArrayType),
                       FrameType.intType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     })),
             new CfLoad(ValueType.LONG, 0),
             new CfConstNumber(0, ValueType.LONG),
@@ -4215,7 +4215,7 @@
                     new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
                       FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType(),
                       FrameType.initialized(options.itemFactory.charArrayType),
                       FrameType.intType()
@@ -4285,7 +4285,7 @@
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType()
                     })),
             new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
@@ -4338,11 +4338,11 @@
                     new int[] {0, 1, 2, 3, 4, 5},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     })),
             new CfConstNumber(0, ValueType.INT),
             label3,
@@ -4351,11 +4351,11 @@
                     new int[] {0, 1, 2, 3, 4, 5},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
             new CfLoad(ValueType.LONG, 0),
@@ -4372,11 +4372,11 @@
                     new int[] {0, 1, 2, 3, 4, 5},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
             new CfConstNumber(0, ValueType.INT),
@@ -4386,11 +4386,11 @@
                     new int[] {0, 1, 2, 3, 4, 5},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType(), FrameType.intType()))),
             new CfLogicalBinop(CfLogicalBinop.Opcode.Or, NumericType.INT),
@@ -4404,11 +4404,11 @@
                     new int[] {0, 1, 2, 3, 4, 5},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     })),
             new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -4492,7 +4492,7 @@
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
-                    new FrameType[] {FrameType.longType(), FrameType.longType()})),
+                    new FrameType[] {FrameType.longType(), FrameType.longHighType()})),
             new CfLoad(ValueType.LONG, 0),
             new CfConstNumber(1, ValueType.LONG),
             new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.LONG),
@@ -4631,13 +4631,13 @@
                     new int[] {0, 1, 2, 3, 4, 5, 6, 7},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     })),
             new CfConstNumber(1, ValueType.LONG),
             new CfLoad(ValueType.LONG, 0),
@@ -4662,15 +4662,15 @@
                     new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     })),
             new CfLoad(ValueType.LONG, 4),
             label7,
@@ -4679,15 +4679,15 @@
                     new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.longType()))),
             new CfReturn(ValueType.LONG),
@@ -4832,11 +4832,11 @@
                     new int[] {0, 1, 2, 3, 4, 5},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     })),
             new CfConstNumber(1, ValueType.LONG),
             new CfLoad(ValueType.LONG, 0),
@@ -4859,13 +4859,13 @@
                     new int[] {0, 1, 2, 3, 4, 5, 6, 7},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     })),
             new CfLoad(ValueType.LONG, 4),
             new CfLoad(ValueType.LONG, 2),
@@ -4876,13 +4876,13 @@
                     new int[] {0, 1, 2, 3, 4, 5, 6, 7},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.longType()))),
             new CfReturn(ValueType.LONG),
@@ -4987,7 +4987,7 @@
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
-                    new FrameType[] {FrameType.longType(), FrameType.longType()})),
+                    new FrameType[] {FrameType.longType(), FrameType.longHighType()})),
             new CfLoad(ValueType.LONG, 0),
             new CfConstNumber(1, ValueType.LONG),
             new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.LONG),
@@ -5037,7 +5037,7 @@
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType()
                     })),
             new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
@@ -5142,9 +5142,9 @@
                     new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType()
                     })),
             new CfLoad(ValueType.INT, 4),
@@ -5162,9 +5162,9 @@
                     new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType()
                     })),
             new CfConstNumber(0, ValueType.INT),
@@ -5174,9 +5174,9 @@
                     new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType()
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
@@ -5192,9 +5192,9 @@
                     new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType()
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
@@ -5205,9 +5205,9 @@
                     new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType()
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType(), FrameType.intType()))),
@@ -5235,12 +5235,12 @@
                     new int[] {0, 1, 2, 3, 4, 5, 6},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType(),
                       FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     })),
             new CfLoad(ValueType.LONG, 5),
             new CfReturn(ValueType.LONG),
@@ -5250,9 +5250,9 @@
                     new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType()
                     })),
             new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
@@ -5488,7 +5488,7 @@
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
-                    new FrameType[] {FrameType.longType(), FrameType.longType()})),
+                    new FrameType[] {FrameType.longType(), FrameType.longHighType()})),
             new CfLoad(ValueType.LONG, 0),
             new CfNeg(NumericType.LONG),
             new CfReturn(ValueType.LONG),
@@ -5589,7 +5589,7 @@
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.intType()
                     })),
             new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
@@ -5642,11 +5642,11 @@
                     new int[] {0, 1, 2, 3, 4, 5},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     })),
             new CfConstNumber(0, ValueType.INT),
             label3,
@@ -5655,11 +5655,11 @@
                     new int[] {0, 1, 2, 3, 4, 5},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
             new CfLoad(ValueType.LONG, 0),
@@ -5676,11 +5676,11 @@
                     new int[] {0, 1, 2, 3, 4, 5},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
             new CfConstNumber(0, ValueType.INT),
@@ -5690,11 +5690,11 @@
                     new int[] {0, 1, 2, 3, 4, 5},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType(), FrameType.intType()))),
             new CfLogicalBinop(CfLogicalBinop.Opcode.Or, NumericType.INT),
@@ -5708,11 +5708,11 @@
                     new int[] {0, 1, 2, 3, 4, 5},
                     new FrameType[] {
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType(),
-                      FrameType.longType()
+                      FrameType.longHighType()
                     })),
             new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -5766,7 +5766,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.longType(), FrameType.longType(), FrameType.intType()
+                      FrameType.longType(), FrameType.longHighType(), FrameType.intType()
                     })),
             new CfLoad(ValueType.INT, 2),
             new CfReturn(ValueType.INT),
@@ -9470,7 +9470,7 @@
                       FrameType.initialized(options.itemFactory.createType("Lsun/misc/Unsafe;")),
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     })),
@@ -9503,7 +9503,7 @@
                       FrameType.initialized(options.itemFactory.createType("Lsun/misc/Unsafe;")),
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.longType(),
-                      FrameType.longType(),
+                      FrameType.longHighType(),
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     })),
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 00892fa..9788ee2 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
@@ -422,7 +422,7 @@
             code.getInstructions(),
             instruction ->
                 instruction.isFrame()
-                    ? instruction.asFrame().map(this::mapLookupTypeToObject)
+                    ? instruction.asFrame().mapReferenceTypes(this::mapLookupTypeToObject)
                     : instruction);
     return code.getInstructions() != newInstructions
         ? new CfCode(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
index 29ecfd2..0e6bd6a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion;
 
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.vivifiedTypeFor;
 
 import com.android.tools.r8.cf.code.CfArrayLoad;
 import com.android.tools.r8.cf.code.CfArrayStore;
@@ -156,9 +157,12 @@
         computeReturnConversion(method, false, eventConsumer, context, contextSupplier);
     DexMethod[] parameterConversions =
         computeParameterConversions(method, true, eventConsumer, context, contextSupplier);
+    DexType newHolder =
+        appView.typeRewriter.hasRewrittenType(method.getHolderType(), appView)
+            ? vivifiedTypeFor(method.getHolderType(), appView)
+            : method.getHolderType();
     DexMethod forwardMethod =
-        convertedMethod(
-            method, true, returnConversion, parameterConversions, wrapperField.getType());
+        convertedMethod(method, true, returnConversion, parameterConversions, newHolder);
     CfCode cfCode =
         new APIConversionCfCodeProvider(
                 appView,
@@ -467,12 +471,13 @@
 
   private DexMethod internalComputeReturnConversion(
       DexMethod invokedMethod,
-      BiFunction<DexType, DexType, DexMethod> methodSupplier,
+      BiFunction<DexType, DexMethod, DexMethod> methodSupplier,
       ProgramMethod context) {
     DexType returnType = invokedMethod.proto.returnType;
-    if (wrapperSynthesizer.shouldConvert(returnType, invokedMethod, context)) {
-      DexType apiConversionCollection = getReturnApiConversionCollection(invokedMethod);
-      return methodSupplier.apply(returnType, apiConversionCollection);
+    DexMethod apiGenericTypesConversion = getReturnApiGenericConversion(invokedMethod);
+    if (wrapperSynthesizer.shouldConvert(
+        returnType, apiGenericTypesConversion, invokedMethod, context)) {
+      return methodSupplier.apply(returnType, apiGenericTypesConversion);
     }
     return null;
   }
@@ -486,9 +491,9 @@
     return internalComputeParameterConversions(
         invokedMethod,
         wrapperSynthesizer,
-        (argType, apiConversionCollection) ->
+        (argType, apiGenericTypesConversion) ->
             wrapperSynthesizer.ensureConversionMethod(
-                argType, destIsVivified, apiConversionCollection, eventConsumer, contextSupplier),
+                argType, destIsVivified, apiGenericTypesConversion, eventConsumer, contextSupplier),
         context);
   }
 
@@ -501,41 +506,42 @@
     return internalComputeParameterConversions(
         invokedMethod,
         wrapperSynthesizer,
-        (argType, apiConversionCollection) ->
+        (argType, apiGenericTypesConversion) ->
             wrapperSynthesizer.getExistingProgramConversionMethod(
-                argType, destIsVivified, apiConversionCollection, eventConsumer, contextSupplier),
+                argType, destIsVivified, apiGenericTypesConversion, eventConsumer, contextSupplier),
         context);
   }
 
   private DexMethod[] internalComputeParameterConversions(
       DexMethod invokedMethod,
       DesugaredLibraryWrapperSynthesizer wrapperSynthesizor,
-      BiFunction<DexType, DexType, DexMethod> methodSupplier,
+      BiFunction<DexType, DexMethod, DexMethod> methodSupplier,
       ProgramMethod context) {
     DexMethod[] parameterConversions = new DexMethod[invokedMethod.getArity()];
     DexType[] parameters = invokedMethod.proto.parameters.values;
     for (int i = 0; i < parameters.length; i++) {
-      DexType apiConversionCollection = getApiConversionCollection(invokedMethod, i);
+      DexMethod apiGenericTypesConversion = getApiGenericConversion(invokedMethod, i);
       DexType argType = parameters[i];
-      if (wrapperSynthesizor.shouldConvert(argType, invokedMethod, context)) {
-        parameterConversions[i] = methodSupplier.apply(argType, apiConversionCollection);
+      if (wrapperSynthesizor.shouldConvert(
+          argType, apiGenericTypesConversion, invokedMethod, context)) {
+        parameterConversions[i] = methodSupplier.apply(argType, apiGenericTypesConversion);
       }
     }
     return parameterConversions;
   }
 
-  public DexType getReturnApiConversionCollection(DexMethod method) {
-    return getApiConversionCollection(method, method.getArity());
+  public DexMethod getReturnApiGenericConversion(DexMethod method) {
+    return getApiGenericConversion(method, method.getArity());
   }
 
-  public DexType getApiConversionCollection(DexMethod method, int parameterIndex) {
-    DexType[] dexTypes =
+  public DexMethod getApiGenericConversion(DexMethod method, int parameterIndex) {
+    DexMethod[] conversions =
         appView
             .options()
             .machineDesugaredLibrarySpecification
-            .getApiConversionCollection()
+            .getApiGenericConversion()
             .get(method);
-    return dexTypes == null ? null : dexTypes[parameterIndex];
+    return conversions == null ? null : conversions[parameterIndex];
   }
 
   private DexMethod convertedMethod(
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 a930b12..d28ccaf 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
@@ -33,7 +33,6 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider.ArrayConversionCfCodeProvider;
-import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider.CollectionConversionCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.apiconverter.WrapperConstructorCfCodeProvider;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
@@ -118,13 +117,15 @@
         || appView.getSyntheticItems().isSyntheticOfKind(type, kinds -> kinds.VIVIFIED_WRAPPER);
   }
 
-  public boolean shouldConvert(DexType type, DexMethod method) {
-    return shouldConvert(type, method, null);
-  }
-
-  public boolean shouldConvert(DexType type, DexMethod method, ProgramMethod context) {
+  public boolean shouldConvert(
+      DexType type, DexMethod apiGenericTypesConversion, DexMethod method, ProgramMethod context) {
     if (type.isArrayType()) {
-      return shouldConvert(type.toBaseType(appView.dexItemFactory()), method, context);
+      assert apiGenericTypesConversion == null;
+      return shouldConvert(
+          type.toBaseType(appView.dexItemFactory()), apiGenericTypesConversion, method, context);
+    }
+    if (apiGenericTypesConversion != null) {
+      return true;
     }
     if (!appView.typeRewriter.hasRewrittenType(type, appView)) {
       return false;
@@ -139,13 +140,12 @@
   public DexMethod ensureConversionMethod(
       DexType type,
       boolean destIsVivified,
-      DexType apiConversionCollection,
+      DexMethod apiGenericTypesConversion,
       DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
       Supplier<UniqueContext> contextSupplier) {
-    if (apiConversionCollection != null) {
+    if (apiGenericTypesConversion != null) {
       assert !type.isArrayType();
-      return ensureCollectionConversionMethod(
-          type, destIsVivified, apiConversionCollection, eventConsumer, contextSupplier);
+      return apiGenericTypesConversion;
     }
     DexType srcType = destIsVivified ? type : vivifiedTypeFor(type);
     DexType destType = destIsVivified ? vivifiedTypeFor(type) : type;
@@ -171,68 +171,6 @@
     return conversion;
   }
 
-  private DexMethod ensureCollectionConversionMethod(
-      DexType type,
-      boolean destIsVivified,
-      DexType apiConversionCollection,
-      DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
-      Supplier<UniqueContext> contextSupplier) {
-    assert type == factory.setType || type == factory.listType;
-    DexMethod conversion =
-        ensureConversionMethod(
-            apiConversionCollection,
-            destIsVivified,
-            null, // We do not support nested collections.
-            eventConsumer,
-            contextSupplier);
-    return ensureCollectionConversionMethod(type, eventConsumer, contextSupplier, conversion);
-  }
-
-  private DexMethod ensureCollectionConversionMethodFromExistingBaseConversion(
-      DexType type,
-      boolean destIsVivified,
-      DexType apiConversionCollection,
-      DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
-      Supplier<UniqueContext> contextSupplier) {
-    assert type == factory.setType || type == factory.listType;
-    DexMethod conversion =
-        getExistingProgramConversionMethod(
-            apiConversionCollection,
-            destIsVivified,
-            null, // We do not support nested collections.
-            eventConsumer,
-            contextSupplier);
-    return ensureCollectionConversionMethod(type, eventConsumer, contextSupplier, conversion);
-  }
-
-  private DexMethod ensureCollectionConversionMethod(
-      DexType collectionType,
-      DesugaredLibraryWrapperSynthesizerEventConsumer eventConsumer,
-      Supplier<UniqueContext> contextSupplier,
-      DexMethod conversion) {
-    ProgramMethod collectionConversion =
-        appView
-            .getSyntheticItems()
-            .createMethod(
-                kinds -> kinds.COLLECTION_CONVERSION,
-                contextSupplier.get(),
-                appView,
-                builder ->
-                    builder
-                        .setProto(factory.createProto(collectionType, collectionType))
-                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
-                        .setCode(
-                            codeSynthesizor ->
-                                new CollectionConversionCfCodeProvider(
-                                        appView,
-                                        codeSynthesizor.getHolderType(),
-                                        collectionType,
-                                        conversion)
-                                    .generateCfCode()));
-    eventConsumer.acceptCollectionConversion(collectionConversion);
-    return collectionConversion.getReference();
-  }
-
   private DexMethod ensureArrayConversionMethod(
       DexType type,
       DexType srcType,
@@ -300,13 +238,12 @@
   public DexMethod getExistingProgramConversionMethod(
       DexType type,
       boolean destIsVivified,
-      DexType apiConversionCollection,
+      DexMethod apiGenericTypesConversion,
       DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
       Supplier<UniqueContext> contextSupplier) {
-    if (apiConversionCollection != null) {
+    if (apiGenericTypesConversion != null) {
       assert !type.isArrayType();
-      return ensureCollectionConversionMethodFromExistingBaseConversion(
-          type, destIsVivified, apiConversionCollection, eventConsumer, contextSupplier);
+      return apiGenericTypesConversion;
     }
     DexType srcType = destIsVivified ? type : vivifiedTypeFor(type);
     DexType destType = destIsVivified ? vivifiedTypeFor(type) : type;
@@ -699,7 +636,6 @@
         .getWrappers()
         .forEach(
             (type, methods) -> {
-              assert !librarySpecification.getCustomConversions().containsKey(type);
               DexClass validClassToWrap = getValidClassToWrap(type);
               // In broken set-ups we can end up having a json files containing wrappers of non
               // desugared classes. Such wrappers are not required since the class won't be
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 189dca8..1aa5f62 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
@@ -48,7 +48,7 @@
 
   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 API_CONVERSION_COLLECTION = "api_conversion_collection";
+  static final String API_GENERIC_TYPES_CONVERSION = "api_generic_types_conversion";
   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";
@@ -264,15 +264,15 @@
         builder.putDontRewritePrefix(dontRewritePrefix.getAsString());
       }
     }
-    if (jsonFlagSet.has(API_CONVERSION_COLLECTION)) {
+    if (jsonFlagSet.has(API_GENERIC_TYPES_CONVERSION)) {
       for (Map.Entry<String, JsonElement> methodAndDescription :
-          jsonFlagSet.get(API_CONVERSION_COLLECTION).getAsJsonObject().entrySet()) {
+          jsonFlagSet.get(API_GENERIC_TYPES_CONVERSION).getAsJsonObject().entrySet()) {
         JsonArray array = methodAndDescription.getValue().getAsJsonArray();
         for (int i = 0; i < array.size(); i += 2) {
-          builder.addApiConversionCollection(
+          builder.addApiGenericTypesConversion(
               parseMethod(methodAndDescription.getKey()),
               array.get(i).getAsInt(),
-              stringDescriptorToDexType(array.get(i + 1).getAsString()));
+              parseMethod(array.get(i + 1).getAsString()));
         }
       }
     }
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 94ae8d0..f17235f 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
@@ -15,13 +15,11 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
-import com.google.common.collect.Sets.SetView;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 public class HumanRewritingFlags {
 
@@ -34,7 +32,7 @@
   private final Map<DexMethod, DexType> covariantRetarget;
   private final Map<DexMethod, DexType> retargetMethod;
   private final Map<DexMethod, DexType> retargetMethodEmulatedDispatch;
-  private final Map<DexMethod, DexType[]> apiConversionCollection;
+  private final Map<DexMethod, DexMethod[]> apiGenericTypesConversion;
   private final Map<DexType, DexType> legacyBackport;
   private final Map<DexType, DexType> customConversions;
   private final Set<DexMethod> dontRewriteInvocation;
@@ -53,7 +51,7 @@
       Map<DexMethod, DexType> covariantRetarget,
       Map<DexMethod, DexType> retargetMethod,
       Map<DexMethod, DexType> retargetMethodEmulatedDispatch,
-      Map<DexMethod, DexType[]> apiConversionCollection,
+      Map<DexMethod, DexMethod[]> apiGenericTypesConversion,
       Map<DexType, DexType> legacyBackport,
       Map<DexType, DexType> customConversion,
       Set<DexMethod> dontRewriteInvocation,
@@ -70,7 +68,7 @@
     this.covariantRetarget = covariantRetarget;
     this.retargetMethod = retargetMethod;
     this.retargetMethodEmulatedDispatch = retargetMethodEmulatedDispatch;
-    this.apiConversionCollection = apiConversionCollection;
+    this.apiGenericTypesConversion = apiGenericTypesConversion;
     this.legacyBackport = legacyBackport;
     this.customConversions = customConversion;
     this.dontRewriteInvocation = dontRewriteInvocation;
@@ -118,7 +116,7 @@
         covariantRetarget,
         retargetMethod,
         retargetMethodEmulatedDispatch,
-        apiConversionCollection,
+        apiGenericTypesConversion,
         legacyBackport,
         customConversions,
         dontRewriteInvocation,
@@ -164,8 +162,8 @@
     return retargetMethodEmulatedDispatch;
   }
 
-  public Map<DexMethod, DexType[]> getApiConversionCollection() {
-    return apiConversionCollection;
+  public Map<DexMethod, DexMethod[]> getApiGenericConversion() {
+    return apiGenericTypesConversion;
   }
 
   public Map<DexType, DexType> getLegacyBackport() {
@@ -221,7 +219,7 @@
     private final Map<DexMethod, DexType> covariantRetarget;
     private final Map<DexMethod, DexType> retargetMethod;
     private final Map<DexMethod, DexType> retargetMethodEmulatedDispatch;
-    private final Map<DexMethod, DexType[]> apiConversionCollection;
+    private final Map<DexMethod, DexMethod[]> apiGenericTypesConversion;
     private final Map<DexType, DexType> legacyBackport;
     private final Map<DexType, DexType> customConversions;
     private final Set<DexMethod> dontRewriteInvocation;
@@ -265,7 +263,7 @@
         Map<DexMethod, DexType> covariantRetarget,
         Map<DexMethod, DexType> retargetMethod,
         Map<DexMethod, DexType> retargetMethodEmulatedDispatch,
-        Map<DexMethod, DexType[]> apiConversionCollection,
+        Map<DexMethod, DexMethod[]> apiConversionCollection,
         Map<DexType, DexType> backportCoreLibraryMember,
         Map<DexType, DexType> customConversions,
         Set<DexMethod> dontRewriteInvocation,
@@ -284,7 +282,7 @@
       this.covariantRetarget = new IdentityHashMap<>(covariantRetarget);
       this.retargetMethod = new IdentityHashMap<>(retargetMethod);
       this.retargetMethodEmulatedDispatch = new IdentityHashMap<>(retargetMethodEmulatedDispatch);
-      this.apiConversionCollection = new IdentityHashMap<>(apiConversionCollection);
+      this.apiGenericTypesConversion = new IdentityHashMap<>(apiConversionCollection);
       this.legacyBackport = new IdentityHashMap<>(backportCoreLibraryMember);
       this.customConversions = new IdentityHashMap<>(customConversions);
       this.dontRewriteInvocation = Sets.newIdentityHashSet();
@@ -406,12 +404,13 @@
       return this;
     }
 
-    public void addApiConversionCollection(DexMethod method, int index, DexType type) {
-      DexType[] types =
-          apiConversionCollection.computeIfAbsent(method, k -> new DexType[method.getArity() + 1]);
+    public void addApiGenericTypesConversion(DexMethod method, int index, DexMethod conversion) {
+      DexMethod[] types =
+          apiGenericTypesConversion.computeIfAbsent(
+              method, k -> new DexMethod[method.getArity() + 1]);
       int actualIndex = index == -1 ? method.getArity() : index;
       assert types[actualIndex] == null;
-      types[actualIndex] = type;
+      types[actualIndex] = conversion;
     }
 
     public Builder putLegacyBackport(DexType backportType, DexType rewrittenBackportType) {
@@ -444,7 +443,6 @@
     }
 
     public HumanRewritingFlags build() {
-      validate();
       return new HumanRewritingFlags(
           ImmutableMap.copyOf(rewritePrefix),
           ImmutableSet.copyOf(dontRewritePrefix),
@@ -455,7 +453,7 @@
           ImmutableMap.copyOf(covariantRetarget),
           ImmutableMap.copyOf(retargetMethod),
           ImmutableMap.copyOf(retargetMethodEmulatedDispatch),
-          ImmutableMap.copyOf(apiConversionCollection),
+          ImmutableMap.copyOf(apiGenericTypesConversion),
           ImmutableMap.copyOf(legacyBackport),
           ImmutableMap.copyOf(customConversions),
           ImmutableSet.copyOf(dontRewriteInvocation),
@@ -464,19 +462,5 @@
           ImmutableMap.copyOf(amendLibraryMethod),
           ImmutableMap.copyOf(amendLibraryField));
     }
-
-    private void validate() {
-      SetView<DexType> dups =
-          Sets.intersection(customConversions.keySet(), wrapperConversions.keySet());
-      if (!dups.isEmpty()) {
-        throw reporter.fatalError(
-            new StringDiagnostic(
-                "Invalid desugared library configuration. "
-                    + "Duplicate types in custom conversions and wrapper conversions: "
-                    + String.join(
-                        ", ", dups.stream().map(DexType::toString).collect(Collectors.toSet())),
-                origin));
-      }
-    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
index 4e08dc9..16b0b9f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
@@ -120,8 +120,8 @@
     return rewritingFlags.getEmulatedVirtualRetargetThroughEmulatedInterface();
   }
 
-  public Map<DexMethod, DexType[]> getApiConversionCollection() {
-    return rewritingFlags.getApiConversionCollection();
+  public Map<DexMethod, DexMethod[]> getApiGenericConversion() {
+    return rewritingFlags.getApiGenericConversion();
   }
 
   public void forEachRetargetMethod(Consumer<DexMethod> consumer) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
index 7245ddc..e125c53 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification;
 
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -13,6 +14,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
+import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
@@ -35,7 +37,7 @@
       Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget,
       Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget,
       Map<DexMethod, DexMethod> emulatedVirtualRetargetThroughEmulatedInterface,
-      Map<DexMethod, DexType[]> apiConversionCollection,
+      Map<DexMethod, DexMethod[]> apiGenericTypesConversion,
       Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces,
       Map<DexType, List<DexMethod>> wrappers,
       Map<DexType, DexType> legacyBackport,
@@ -53,7 +55,7 @@
     this.emulatedVirtualRetarget = emulatedVirtualRetarget;
     this.emulatedVirtualRetargetThroughEmulatedInterface =
         emulatedVirtualRetargetThroughEmulatedInterface;
-    this.apiConversionCollection = apiConversionCollection;
+    this.apiGenericTypesConversion = apiGenericTypesConversion;
     this.emulatedInterfaces = emulatedInterfaces;
     this.wrappers = wrappers;
     this.legacyBackport = legacyBackport;
@@ -94,7 +96,7 @@
   private final Map<DexMethod, DexMethod> emulatedVirtualRetargetThroughEmulatedInterface;
 
   // Encodes weither specific parameter collections need to be wrapped differently.
-  private final Map<DexMethod, DexType[]> apiConversionCollection;
+  private final Map<DexMethod, DexMethod[]> apiGenericTypesConversion;
 
   // Emulated interface descriptors.
   private final Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces;
@@ -144,8 +146,8 @@
     return emulatedVirtualRetargetThroughEmulatedInterface;
   }
 
-  public Map<DexMethod, DexType[]> getApiConversionCollection() {
-    return apiConversionCollection;
+  public Map<DexMethod, DexMethod[]> getApiGenericConversion() {
+    return apiGenericTypesConversion;
   }
 
   public void forEachRetargetMethod(Consumer<DexMethod> consumer) {
@@ -243,7 +245,7 @@
         emulatedVirtualRetarget = ImmutableMap.builder();
     private final ImmutableMap.Builder<DexMethod, DexMethod>
         emulatedVirtualRetargetThroughEmulatedInterface = ImmutableMap.builder();
-    private final ImmutableMap.Builder<DexMethod, DexType[]> apiConversionCollection =
+    private final ImmutableMap.Builder<DexMethod, DexMethod[]> apiGenericTypesConversion =
         ImmutableMap.builder();
     private final ImmutableMap.Builder<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces =
         ImmutableMap.builder();
@@ -302,8 +304,8 @@
       emulatedVirtualRetargetThroughEmulatedInterface.put(src, dest);
     }
 
-    public void addApiConversionCollection(DexMethod method, DexType[] dexTypes) {
-      apiConversionCollection.put(method, dexTypes);
+    public void addApiGenericTypesConversion(DexMethod method, DexMethod[] conversions) {
+      apiGenericTypesConversion.put(method, conversions);
     }
 
     public void addWrapper(DexType wrapperConversion, List<DexMethod> methods) {
@@ -334,10 +336,28 @@
       return rewriteType.get(type);
     }
 
+    private void validate(Set<DexType> maintainTypeBuilt) {
+      ArrayList<DexType> warnings = new ArrayList<>();
+      for (DexType toRewrite : rewriteType.keySet()) {
+        if (maintainTypeBuilt.contains(toRewrite)) {
+          warnings.add(toRewrite);
+        }
+      }
+      if (!warnings.isEmpty()) {
+        throw new CompilationError(
+            "The compilation cannot proceed because the desugared library specification contains"
+                + " ambiguous flags that the compiler cannot interpret: The following types are"
+                + " both rewritten and maintained "
+                + warnings);
+      }
+    }
+
     public MachineRewritingFlags build() {
+      Set<DexType> maintainTypeBuilt = maintainType.build();
+      validate(maintainTypeBuilt);
       return new MachineRewritingFlags(
           rewriteType,
-          maintainType.build(),
+          maintainTypeBuilt,
           rewriteDerivedTypeOnly,
           staticFieldRetarget.build(),
           covariantRetarget.build(),
@@ -345,7 +365,7 @@
           nonEmulatedVirtualRetarget.build(),
           emulatedVirtualRetarget.build(),
           emulatedVirtualRetargetThroughEmulatedInterface.build(),
-          apiConversionCollection.build(),
+          apiGenericTypesConversion.build(),
           emulatedInterfaces.build(),
           wrappers.build(),
           legacyBackport.build(),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
index dea440e..1097b06 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
@@ -97,7 +97,7 @@
         ComputedApiLevel.unknown());
     rewritingFlags.getAmendLibraryMethod().forEach(builder::amendLibraryMethod);
     rewritingFlags.getAmendLibraryField().forEach(builder::amendLibraryField);
-    rewritingFlags.getApiConversionCollection().forEach(builder::addApiConversionCollection);
+    rewritingFlags.getApiGenericConversion().forEach(builder::addApiGenericTypesConversion);
     new HumanToMachineRetargetConverter(appInfo)
         .convertRetargetFlags(rewritingFlags, builder, this::warnMissingReferences);
     new HumanToMachineEmulatedInterfaceConverter(appInfo)
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 2d360c0..d5168da 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
@@ -577,7 +577,7 @@
       Set<DexType> emulatesInterfaces,
       Map<DexType, GenericSignature.ClassTypeSignature> extraInterfaceSignatures) {
     // TODO(b/182329331): Only handle type arguments for Cf to Cf desugar.
-    if (appView.options().cfToCfDesugar && clazz.validInterfaceSignatures()) {
+    if (appView.options().isCfDesugaring() && clazz.validInterfaceSignatures()) {
       clazz.forEachImmediateSupertypeWithSignature(
           (type, signature) -> {
             if (emulatesInterfaces.contains(type)) {
@@ -612,7 +612,7 @@
       return;
     }
     // TODO(b/182329331): Only handle type arguments for Cf to Cf desugar.
-    if (appView.options().cfToCfDesugar && clazz.validInterfaceSignatures()) {
+    if (appView.options().isCfDesugaring() && clazz.validInterfaceSignatures()) {
       assert typeArguments != null;
       clazz.forEachImmediateSupertypeWithAppliedTypeArguments(
           typeArguments,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
index 89a9163..59dcd83 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -223,7 +223,7 @@
     assert !appView.enableWholeProgramOptimizations();
     DexProgramClass iface = method.getHolder();
     if (method.getAccessFlags().isBridge()) {
-      if (appView.options().cfToCfDesugar) {
+      if (appView.options().isCfDesugaring()) {
         // TODO(b/187176895): Find the compilation causing this to not be removed.
         return false;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index bf34207..6551955 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -218,43 +218,47 @@
       Instruction canonicalizedConstant = entry.getKey();
       assert canonicalizedConstant.instructionTypeCanBeCanonicalized();
       Instruction newConst;
-      switch (canonicalizedConstant.opcode()) {
-        case CONST_CLASS:
-          if (Log.ENABLED) {
-            numberOfConstClassCanonicalization++;
-          }
-          newConst = ConstClass.copyOf(code, canonicalizedConstant.asConstClass());
-          break;
-        case CONST_NUMBER:
-          if (Log.ENABLED) {
-            numberOfConstNumberCanonicalization++;
-          }
-          newConst = ConstNumber.copyOf(code, canonicalizedConstant.asConstNumber());
-          break;
-        case CONST_STRING:
-          if (Log.ENABLED) {
-            numberOfConstStringCanonicalization++;
-          }
-          newConst = ConstString.copyOf(code, canonicalizedConstant.asConstString());
-          break;
-        case DEX_ITEM_BASED_CONST_STRING:
-          if (Log.ENABLED) {
-            numberOfDexItemBasedConstStringCanonicalization++;
-          }
-          newConst =
-              DexItemBasedConstString.copyOf(
-                  code, canonicalizedConstant.asDexItemBasedConstString());
-          break;
-        case STATIC_GET:
-          if (Log.ENABLED) {
-            numberOfEffectivelyFinalFieldCanonicalization++;
-          }
-          newConst = StaticGet.copyOf(code, canonicalizedConstant.asStaticGet());
-          break;
-        default:
-          throw new Unreachable();
+      if (canonicalizedConstant.getBlock().isEntry()) {
+        newConst = canonicalizedConstant;
+      } else {
+        switch (canonicalizedConstant.opcode()) {
+          case CONST_CLASS:
+            if (Log.ENABLED) {
+              numberOfConstClassCanonicalization++;
+            }
+            newConst = ConstClass.copyOf(code, canonicalizedConstant.asConstClass());
+            break;
+          case CONST_NUMBER:
+            if (Log.ENABLED) {
+              numberOfConstNumberCanonicalization++;
+            }
+            newConst = ConstNumber.copyOf(code, canonicalizedConstant.asConstNumber());
+            break;
+          case CONST_STRING:
+            if (Log.ENABLED) {
+              numberOfConstStringCanonicalization++;
+            }
+            newConst = ConstString.copyOf(code, canonicalizedConstant.asConstString());
+            break;
+          case DEX_ITEM_BASED_CONST_STRING:
+            if (Log.ENABLED) {
+              numberOfDexItemBasedConstStringCanonicalization++;
+            }
+            newConst =
+                DexItemBasedConstString.copyOf(
+                    code, canonicalizedConstant.asDexItemBasedConstString());
+            break;
+          case STATIC_GET:
+            if (Log.ENABLED) {
+              numberOfEffectivelyFinalFieldCanonicalization++;
+            }
+            newConst = StaticGet.copyOf(code, canonicalizedConstant.asStaticGet());
+            break;
+          default:
+            throw new Unreachable();
+        }
+        insertCanonicalizedConstant(code, newConst);
       }
-      insertCanonicalizedConstant(code, newConst);
       for (Value outValue : entry.getValue()) {
         outValue.replaceUsers(newConst.outValue());
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ClassInlinerMethodConstraintAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ClassInlinerMethodConstraintAnalysis.java
index f45344f..8842317 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ClassInlinerMethodConstraintAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ClassInlinerMethodConstraintAnalysis.java
@@ -26,7 +26,7 @@
     // Analyze code.
     IntraproceduralDataflowAnalysis<ParameterUsages> analysis =
         new IntraproceduralDataflowAnalysis<>(
-            ParameterUsages.bottom(), code, new TransferFunction(appView, method, code));
+            appView, ParameterUsages.bottom(), code, new TransferFunction(appView, method, code));
     SuccessfulDataflowAnalysisResult<?, ParameterUsages> result =
         timing.time(
             "Data flow analysis",
@@ -34,7 +34,7 @@
     if (result == null) {
       return ClassInlinerMethodConstraint.alwaysFalse();
     }
-    ParameterUsages usages = timing.time("Externalize", () -> result.join().externalize());
+    ParameterUsages usages = timing.time("Externalize", () -> result.join(appView).externalize());
     if (usages.isBottom()) {
       return ClassInlinerMethodConstraint.alwaysTrue();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsages.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsages.java
index ac81581..f4a45a8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsages.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsages.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.classinliner.analysis;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractState;
 import com.android.tools.r8.utils.IntObjToObjFunction;
 
@@ -42,7 +43,7 @@
   }
 
   @Override
-  public ParameterUsages join(ParameterUsages state) {
+  public ParameterUsages join(AppView<?> appView, ParameterUsages state) {
     if (isBottom()) {
       return state;
     }
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 e4bbb6c..f6bc6e6 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
@@ -149,6 +149,16 @@
     return predecessorExitState;
   }
 
+  @Override
+  public ParameterUsages computeExceptionalBlockEntryState(
+      BasicBlock block,
+      DexType guard,
+      BasicBlock throwBlock,
+      Instruction throwInstruction,
+      ParameterUsages throwState) {
+    return throwState;
+  }
+
   private ParameterUsages analyzeArgument(Argument argument, ParameterUsages state) {
     // Only consider arguments that could store an instance eligible for class inlining. Note that
     // we can't ignore parameters with a library type, since instances of program classes could
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
index df2c436..0c41415 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexItemFactory.EnumMembers;
@@ -14,7 +13,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
-import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 import com.android.tools.r8.ir.optimize.info.LibraryOptimizationInfoInitializerFeedback;
 import com.android.tools.r8.ir.optimize.info.field.EmptyInstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
@@ -87,16 +85,6 @@
     }
   }
 
-  private void modelStaticFinalLibraryFields(Set<DexEncodedField> finalLibraryFields) {
-    for (DexEncodedField field : finalLibraryFields) {
-      if (field.isStatic()) {
-        feedback.recordLibraryFieldHasAbstractValue(
-            field,
-            abstractValueFactory.createSingleFieldValue(field.getReference(), ObjectState.empty()));
-      }
-    }
-  }
-
   private void modelLibraryMethodsNonNullParamOrThrow() {
     dexItemFactory.libraryMethodsNonNullParamOrThrow.forEach(
         (method, nonNullParamOrThrow) -> {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAction.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAction.java
new file mode 100644
index 0000000..5ecb6bb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAction.java
@@ -0,0 +1,170 @@
+// 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.string;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Value;
+import com.google.common.collect.ImmutableList;
+
+/** StringBuilderAction defines an interface for updating the IR code based on optimizations. */
+public interface StringBuilderAction {
+
+  void perform(
+      AppView<?> appView,
+      IRCode code,
+      InstructionListIterator iterator,
+      Instruction instruction,
+      StringBuilderOracle oracle);
+
+  /** The RemoveStringBuilderAction will simply remove the instruction completely. */
+  class RemoveStringBuilderAction implements StringBuilderAction {
+
+    private static final RemoveStringBuilderAction INSTANCE = new RemoveStringBuilderAction();
+
+    @Override
+    public void perform(
+        AppView<?> appView,
+        IRCode code,
+        InstructionListIterator iterator,
+        Instruction instruction,
+        StringBuilderOracle oracle) {
+      assert oracle.isModeledStringBuilderInstruction(instruction);
+      if (oracle.isAppend(instruction) && instruction.outValue() != null) {
+        // Append will return the string builder instance. Before removing, ensure that
+        // all users of the output values uses the receiver.
+        instruction.outValue().replaceUsers(instruction.getFirstOperand());
+      }
+      iterator.removeOrReplaceByDebugLocalRead();
+    }
+
+    static RemoveStringBuilderAction getInstance() {
+      return INSTANCE;
+    }
+  }
+
+  /**
+   * ReplaceByConstantString will replace a toString() call on StringBuilder with a constant string.
+   */
+  class ReplaceByConstantString implements StringBuilderAction {
+
+    private final String replacement;
+
+    ReplaceByConstantString(String replacement) {
+      this.replacement = replacement;
+    }
+
+    @Override
+    public void perform(
+        AppView<?> appView,
+        IRCode code,
+        InstructionListIterator iterator,
+        Instruction instruction,
+        StringBuilderOracle oracle) {
+      assert oracle.isToString(instruction);
+      iterator.replaceCurrentInstructionWithConstString(appView, code, replacement);
+    }
+  }
+
+  /**
+   * AppendWithNewConstantString will change the current instruction to be an append with a constant
+   * string. If the current instruction is init the instruction will be changed to an init taking a
+   * string as argument.
+   */
+  class AppendWithNewConstantString implements StringBuilderAction {
+
+    private final String replacement;
+
+    AppendWithNewConstantString(String replacement) {
+      this.replacement = replacement;
+    }
+
+    @Override
+    public void perform(
+        AppView<?> appView,
+        IRCode code,
+        InstructionListIterator iterator,
+        Instruction instruction,
+        StringBuilderOracle oracle) {
+      Instruction previous = iterator.previous();
+      InvokeMethodWithReceiver invoke = previous.asInvokeMethodWithReceiver();
+      assert invoke != null;
+      // If the block has catch handlers, inserting a constant string in the same block as the
+      // append violates our block representation in DEX since constant string is throwing. If the
+      // append is in a block with catch handlers, we simply insert a new constant string in the
+      // entry block after all arguments.
+      Value value;
+      if (!invoke.getBlock().hasCatchHandlers()) {
+        value =
+            iterator.insertConstStringInstruction(
+                appView, code, appView.dexItemFactory().createString(replacement));
+      } else {
+        InstructionListIterator stringInsertIterator = code.entryBlock().listIterator(code);
+        while (stringInsertIterator.hasNext()) {
+          Instruction next = stringInsertIterator.next();
+          if (!next.isArgument()) {
+            stringInsertIterator.previous();
+            break;
+          }
+        }
+        value =
+            stringInsertIterator.insertConstStringInstruction(
+                appView, code, appView.dexItemFactory().createString(replacement));
+      }
+      iterator.next();
+      DexMethod invokedMethod = invoke.getInvokedMethod();
+      if (invoke.isInvokeConstructor(appView.dexItemFactory())) {
+        iterator.replaceCurrentInstruction(
+            InvokeDirect.builder()
+                .setArguments(ImmutableList.of(invoke.getReceiver(), value))
+                .setMethod(
+                    getConstructorWithStringParameter(invokedMethod, appView.dexItemFactory()))
+                .setOutValue(invoke.outValue())
+                .build());
+      } else if (!isAppendWithString(invokedMethod, appView.dexItemFactory())) {
+        iterator.replaceCurrentInstruction(
+            InvokeVirtual.builder()
+                .setArguments(ImmutableList.of(invoke.getReceiver(), value))
+                .setMethod(getAppendWithStringParameter(invokedMethod, appView.dexItemFactory()))
+                .setOutValue(invoke.outValue())
+                .build());
+      } else {
+        invoke.replaceValue(1, value);
+      }
+    }
+
+    private boolean isAppendWithString(DexMethod method, DexItemFactory factory) {
+      return factory.stringBufferMethods.isAppendStringMethod(method)
+          || factory.stringBuilderMethods.isAppendStringMethod(method);
+    }
+
+    private DexMethod getConstructorWithStringParameter(
+        DexMethod invokedMethod, DexItemFactory factory) {
+      if (invokedMethod.getHolderType() == factory.stringBufferType) {
+        return factory.stringBufferMethods.stringConstructor;
+      } else {
+        assert invokedMethod.getHolderType() == factory.stringBuilderType;
+        return factory.stringBuilderMethods.stringConstructor;
+      }
+    }
+
+    private DexMethod getAppendWithStringParameter(
+        DexMethod invokedMethod, DexItemFactory factory) {
+      if (invokedMethod.getHolderType() == factory.stringBufferType) {
+        return factory.stringBufferMethods.appendString;
+      } else {
+        assert invokedMethod.getHolderType() == factory.stringBuilderType;
+        return factory.stringBuilderMethods.appendString;
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java
index 1179c6d..bb75dc2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.string;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractState;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractTransferFunction;
@@ -40,10 +41,16 @@
    * loop.
    */
   static boolean hasAppendInstructionInLoop(
-      IRCode code, Value builder, StringBuilderOptimizationConfiguration configuration) {
+      AppView<?> appView,
+      IRCode code,
+      Value builder,
+      StringBuilderOptimizationConfiguration configuration) {
     IntraproceduralDataflowAnalysis<AbstractStateImpl> analysis =
         new IntraproceduralDataflowAnalysis<>(
-            AbstractStateImpl.bottom(), code, new TransferFunction(builder, configuration));
+            appView,
+            AbstractStateImpl.bottom(),
+            code,
+            new TransferFunction(builder, configuration));
     DataflowAnalysisResult result = analysis.run(builder.definition.getBlock());
     return result.isFailedAnalysisResult();
   }
@@ -86,7 +93,7 @@
     }
 
     @Override
-    public AbstractStateImpl join(AbstractStateImpl state) {
+    public AbstractStateImpl join(AppView<?> appView, AbstractStateImpl state) {
       if (liveAppendInstructions.isEmpty()) {
         return state;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
new file mode 100644
index 0000000..caf8432
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
@@ -0,0 +1,562 @@
+// 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.string;
+
+import static com.android.tools.r8.ir.optimize.string.StringBuilderHelper.canMutate;
+import static com.android.tools.r8.ir.optimize.string.StringBuilderNode.createAppendNode;
+import static com.android.tools.r8.ir.optimize.string.StringBuilderNode.createEscapeNode;
+import static com.android.tools.r8.ir.optimize.string.StringBuilderNode.createImplicitToStringNode;
+import static com.android.tools.r8.ir.optimize.string.StringBuilderNode.createInitNode;
+import static com.android.tools.r8.ir.optimize.string.StringBuilderNode.createInspectionNode;
+import static com.android.tools.r8.ir.optimize.string.StringBuilderNode.createMutateNode;
+import static com.android.tools.r8.ir.optimize.string.StringBuilderNode.createNewInstanceNode;
+import static com.android.tools.r8.ir.optimize.string.StringBuilderNode.createOtherStringBuilderNode;
+import static com.android.tools.r8.ir.optimize.string.StringBuilderNode.createToStringNode;
+import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.SuccessfulDataflowAnalysisResult;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.IntraProceduralDataflowAnalysisOptions;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.IntraproceduralDataflowAnalysis;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunctionResult;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.string.StringBuilderNode.AppendNode;
+import com.android.tools.r8.ir.optimize.string.StringBuilderNode.EscapeNode;
+import com.android.tools.r8.ir.optimize.string.StringBuilderNode.ImplicitToStringNode;
+import com.android.tools.r8.ir.optimize.string.StringBuilderNode.InitNode;
+import com.android.tools.r8.ir.optimize.string.StringBuilderNode.InitOrAppend;
+import com.android.tools.r8.ir.optimize.string.StringBuilderNode.LoopNode;
+import com.android.tools.r8.ir.optimize.string.StringBuilderNodeMuncher.MunchingState;
+import com.android.tools.r8.ir.optimize.string.StringBuilderOracle.DefaultStringBuilderOracle;
+import com.android.tools.r8.utils.DepthFirstSearchWorkListBase.DepthFirstSearchWorkList;
+import com.android.tools.r8.utils.DepthFirstSearchWorkListBase.StatefulDepthFirstSearchWorkList;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+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.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * {@link StringBuilderAppendOptimizer} will try to optimize string builders by joining appends with
+ * constant arguments and materializing toStrings into constant strings to be able to remove entire
+ * StringBuilders (or StringBuffers).
+ *
+ * <p>The algorithm works by producing graphs for all string builders of a method. The graphs follow
+ * the control flow of the method, thus, if a string builder is assigned a value in two blocks from
+ * a certain point, and there is no linear path between the two assignments, they will show up in a
+ * graph as two successor nodes to a common predecessor. StringBuilder graphs are not tracked
+ * through phis, instead a phi and subsequent uses of that phi will also occur as a graph.
+ *
+ * <p>When all graphs are constructed the graphs are optimized by running a munching algorithm on
+ * them.
+ *
+ * <p>Finally, based on all optimizations, the IR is updated to reflect the optimizations.
+ */
+public class StringBuilderAppendOptimizer {
+
+  private final AppView<?> appView;
+  private final StringBuilderOracle oracle;
+  private final IRCode code;
+
+  private static final int NUMBER_OF_MUNCHING_PASSES = 3;
+
+  private StringBuilderAppendOptimizer(AppView<?> appView, IRCode code) {
+    this.appView = appView;
+    this.code = code;
+    oracle = new DefaultStringBuilderOracle(appView.dexItemFactory());
+  }
+
+  public static void run(AppView<?> appView, IRCode code) {
+    new StringBuilderAppendOptimizer(appView, code).run();
+  }
+
+  private void run() {
+    Map<Value, StringBuilderNode> stringBuilderGraphs = computeStringBuilderGraphs();
+    Map<Instruction, StringBuilderAction> actions = optimizeOnGraphs(stringBuilderGraphs);
+    if (actions.isEmpty()) {
+      return;
+    }
+    InstructionListIterator it = code.instructionListIterator();
+    while (it.hasNext()) {
+      Instruction instruction = it.next();
+      StringBuilderAction stringBuilderAction = actions.get(instruction);
+      if (stringBuilderAction != null) {
+        stringBuilderAction.perform(appView, code, it, instruction, oracle);
+      }
+    }
+  }
+
+  private static class StringBuilderGraphState {
+
+    private final Map<Value, StringBuilderNode> roots;
+    private final Map<Value, StringBuilderNode> tails;
+    private boolean isPartOfLoop;
+
+    private StringBuilderGraphState(
+        Map<Value, StringBuilderNode> roots, Map<Value, StringBuilderNode> tails) {
+      this.roots = roots;
+      this.tails = tails;
+    }
+  }
+
+  /***
+   * This will compute a collection of graphs from StringBuilders and return a map from value to
+   * the root of the string builder graph.
+   *
+   * The graphs are constructed by doing a DFS traversel and computing all string builder actions
+   * inside a block. The flow here is known to be linear. When backtracking, the linear collection
+   * of nodes (represented by root and tail) are then combined in a way that ensure the control flow
+   * of the method is maintained in the graph.
+   *
+   * To ensure that we correctly compute when a string builder escapes and when it can be mutated
+   * we first compute a {@link StringBuilderEscapeState} by using a flow analysis, enabling us to
+   * answer, for a given instruction, is a string builder value escaping and all escaped values
+   * at the instruction.
+   */
+  private Map<Value, StringBuilderNode> computeStringBuilderGraphs() {
+    StringBuilderEscapeTransferFunction transferFunction =
+        new StringBuilderEscapeTransferFunction(oracle);
+    IntraproceduralDataflowAnalysis<StringBuilderEscapeState> analysis =
+        new IntraproceduralDataflowAnalysis<>(
+            appView,
+            StringBuilderEscapeState.bottom(),
+            code,
+            transferFunction,
+            IntraProceduralDataflowAnalysisOptions.getNoCollapseInstance());
+    SuccessfulDataflowAnalysisResult<?, StringBuilderEscapeState> stringBuilderEscapeResult =
+        analysis.run(code.entryBlock()).asSuccessfulAnalysisResult();
+
+    if (stringBuilderEscapeResult == null) {
+      return Collections.emptyMap();
+    }
+
+    TraversalContinuation<Void, StringBuilderGraphState> graphResult =
+        new StatefulDepthFirstSearchWorkList<BasicBlock, StringBuilderGraphState, Void>() {
+
+          @Override
+          @SuppressWarnings("ReturnValueIgnored")
+          protected TraversalContinuation<Void, StringBuilderGraphState> process(
+              DFSNodeWithState<BasicBlock, StringBuilderGraphState> node,
+              Function<BasicBlock, DFSNodeWithState<BasicBlock, StringBuilderGraphState>>
+                  childNodeConsumer) {
+            Map<Value, StringBuilderNode> currentRoots = new IdentityHashMap<>();
+            Map<Value, StringBuilderNode> currentTails = new IdentityHashMap<>();
+            BasicBlock block = node.getNode();
+            StringBuilderEscapeState previousState = analysis.computeBlockEntryState(block);
+            TransferFunctionResult<StringBuilderEscapeState> result =
+                transferFunction.applyBlock(block, previousState);
+            if (result.isFailedTransferResult()) {
+              assert false : "Computing the state should never fail";
+              return TraversalContinuation.doBreak();
+            }
+            previousState = result.asAbstractState();
+            for (Phi phi : block.getPhis()) {
+              if (previousState.isLiveStringBuilder(phi)) {
+                visitAllAliasing(
+                    phi,
+                    previousState,
+                    value -> {},
+                    alias -> {
+                      EscapeNode escapeNode = new EscapeNode();
+                      currentRoots.put(alias, escapeNode);
+                      currentTails.put(alias, escapeNode);
+                    });
+              }
+            }
+            for (Instruction instruction : block.getInstructions()) {
+              result = transferFunction.apply(instruction, previousState);
+              if (result.isFailedTransferResult()) {
+                assert false : "Computing the state should never fail";
+                return TraversalContinuation.doBreak();
+              }
+              previousState = result.asAbstractState();
+              createNodesForInstruction(
+                  instruction,
+                  previousState,
+                  (value, sbNode) -> {
+                    StringBuilderNode currentTail = currentTails.get(value);
+                    if (currentTail == null) {
+                      currentRoots.put(value, sbNode);
+                      currentTails.put(value, sbNode);
+                    } else if (shouldAddNodeToGraph(currentTail, sbNode)) {
+                      currentTail.addSuccessor(sbNode);
+                      currentTails.put(value, sbNode);
+                    }
+                  });
+            }
+            assert currentRoots.keySet().equals(currentTails.keySet());
+            assert previousState.getLiveStringBuilders().containsAll(currentRoots.keySet())
+                : "Seen root that is not a live string builder";
+            node.setState(new StringBuilderGraphState(currentRoots, currentTails));
+            for (BasicBlock successor : block.getSuccessors()) {
+              childNodeConsumer.apply(successor);
+            }
+            return TraversalContinuation.doContinue();
+          }
+
+          private boolean shouldAddNodeToGraph(
+              StringBuilderNode insertedNode, StringBuilderNode newNode) {
+            // No need for multiple mutating nodes or inspecting nodes.
+            if (insertedNode.isMutateNode()) {
+              return !newNode.isMutateNode() && !newNode.isInspectingNode();
+            } else if (insertedNode.isInspectingNode()) {
+              return !newNode.isInspectingNode();
+            }
+            return true;
+          }
+
+          private void createNodesForInstruction(
+              Instruction instruction,
+              StringBuilderEscapeState escapeState,
+              BiConsumer<Value, StringBuilderNode> nodeConsumer) {
+            // Do not build nodes for assume values.
+            if (instruction.isAssume()) {
+              return;
+            }
+            if (oracle.isModeledStringBuilderInstruction(instruction)) {
+              createNodesForStringBuilderInstruction(instruction, escapeState, nodeConsumer);
+            } else {
+              for (Value newEscapedValue : escapeState.getNewlyEscaped()) {
+                visitStringBuilderValues(
+                    newEscapedValue,
+                    escapeState,
+                    nonAlias -> nodeConsumer.accept(nonAlias, createEscapeNode()),
+                    alias -> nodeConsumer.accept(alias, createEscapeNode()));
+              }
+              if (canMutate(instruction)) {
+                for (Value escapedStringBuilder : escapeState.getEscaping()) {
+                  visitStringBuilderValues(
+                      escapedStringBuilder,
+                      escapeState,
+                      nonAlias -> nodeConsumer.accept(nonAlias, createMutateNode()),
+                      alias -> nodeConsumer.accept(alias, createMutateNode()));
+                }
+              }
+            }
+          }
+
+          private void createNodesForStringBuilderInstruction(
+              Instruction instruction,
+              StringBuilderEscapeState escapeState,
+              BiConsumer<Value, StringBuilderNode> nodeConsumer) {
+            if (instruction.isNewInstance()) {
+              Value newInstanceValue = instruction.outValue();
+              assert newInstanceValue != null;
+              nodeConsumer.accept(
+                  newInstanceValue, createNewInstanceNode(instruction.asNewInstance()));
+            } else {
+              InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
+              Value receiver = invoke.getReceiver();
+              if (oracle.isInit(instruction)) {
+                InitNode initNode = createInitNode(instruction.asInvokeDirect());
+                initNode.setConstantArgument(oracle.getConstantArgument(instruction));
+                if (invoke.arguments().size() == 2) {
+                  Value arg = invoke.getOperand(1);
+                  if (oracle.hasStringBuilderType(arg)) {
+                    insertImplicitToStringNode(
+                        arg, instruction, initNode, escapeState, nodeConsumer);
+                  }
+                }
+                visitStringBuilderValues(
+                    receiver,
+                    escapeState,
+                    actual -> nodeConsumer.accept(actual, initNode),
+                    escaped -> nodeConsumer.accept(escaped, createInspectionNode(instruction)));
+              } else if (oracle.isAppend(instruction)) {
+                AppendNode appendNode = createAppendNode(instruction.asInvokeVirtual());
+                appendNode.setConstantArgument(oracle.getConstantArgument(instruction));
+                Value arg = invoke.getOperand(1).getAliasedValue();
+                if (oracle.hasStringBuilderType(arg)) {
+                  insertImplicitToStringNode(
+                      arg, instruction, appendNode, escapeState, nodeConsumer);
+                }
+                visitStringBuilderValues(
+                    receiver,
+                    escapeState,
+                    actual -> nodeConsumer.accept(actual, appendNode),
+                    escaped -> nodeConsumer.accept(escaped, createMutateNode()));
+              } else if (oracle.isToString(instruction)) {
+                visitStringBuilderValues(
+                    receiver,
+                    escapeState,
+                    actual ->
+                        nodeConsumer.accept(
+                            actual, createToStringNode(instruction.asInvokeVirtual())),
+                    escaped -> nodeConsumer.accept(escaped, createInspectionNode(instruction)));
+              } else if (oracle.isInspecting(instruction)) {
+                visitStringBuilderValues(
+                    receiver,
+                    escapeState,
+                    actual -> nodeConsumer.accept(actual, createInspectionNode(instruction)),
+                    escaped -> nodeConsumer.accept(escaped, createInspectionNode(instruction)));
+              } else {
+                visitStringBuilderValues(
+                    receiver,
+                    escapeState,
+                    actual ->
+                        nodeConsumer.accept(actual, createOtherStringBuilderNode(instruction)),
+                    escaped ->
+                        nodeConsumer.accept(escaped, createOtherStringBuilderNode(instruction)));
+              }
+            }
+          }
+
+          // For tracking propagating constant string builder values through append or init we
+          // build a link between the two graphs.
+          private void insertImplicitToStringNode(
+              Value value,
+              Instruction instruction,
+              InitOrAppend node,
+              StringBuilderEscapeState escapeState,
+              BiConsumer<Value, StringBuilderNode> nodeConsumer) {
+            assert escapeState.isLiveStringBuilder(value);
+            ImplicitToStringNode implicitToStringNode = createImplicitToStringNode(node);
+            visitStringBuilderValues(
+                value,
+                escapeState,
+                actual -> nodeConsumer.accept(actual, implicitToStringNode),
+                escaped -> nodeConsumer.accept(escaped, createInspectionNode(instruction)));
+            node.setImplicitToStringNode(implicitToStringNode);
+          }
+
+          private void visitStringBuilderValues(
+              Value value,
+              StringBuilderEscapeState state,
+              Consumer<Value> actualConsumer,
+              Consumer<Value> aliasAndEscapedConsumer) {
+            assert state.isLiveStringBuilder(value);
+            boolean seenEscaped =
+                visitAllAliasing(value, state, actualConsumer, aliasAndEscapedConsumer);
+            seenEscaped |= visitAllAliases(value, state, aliasAndEscapedConsumer);
+            if (seenEscaped) {
+              for (Value escapingValue : state.getEscaping()) {
+                aliasAndEscapedConsumer.accept(escapingValue);
+              }
+            }
+          }
+
+          private boolean visitAllAliasing(
+              Value value,
+              StringBuilderEscapeState state,
+              Consumer<Value> nonAliasConsumer,
+              Consumer<Value> aliasAndEscapedConsumer) {
+            WorkList<Value> valueWorkList = WorkList.newIdentityWorkList(value);
+            boolean seenUnknownAlias = false;
+            boolean seenEscaped = false;
+            while (valueWorkList.hasNext()) {
+              Value next = valueWorkList.next();
+              seenEscaped |= state.isEscaped(next);
+              Set<Value> aliasing =
+                  state.getAliasesToDefinitions().getOrDefault(next, Collections.emptySet());
+              valueWorkList.addIfNotSeen(aliasing);
+              // Check if we have a direct alias such as Assume, CheckCast or StringBuilder.append.
+              if (aliasing.size() != 1 || next.isPhi()) {
+                if (!seenUnknownAlias) {
+                  nonAliasConsumer.accept(next);
+                  seenUnknownAlias = true;
+                } else {
+                  aliasAndEscapedConsumer.accept(next);
+                }
+              }
+            }
+            return seenEscaped;
+          }
+
+          private boolean visitAllAliases(
+              Value value, StringBuilderEscapeState state, Consumer<Value> aliasConsumer) {
+            Map<Value, Set<Value>> escapedDefinitionsToKnown = state.getDefinitionsToAliases();
+            WorkList<Value> valueWorkList =
+                WorkList.newIdentityWorkList(
+                    escapedDefinitionsToKnown.getOrDefault(value, Collections.emptySet()));
+            boolean seenEscaped = false;
+            while (valueWorkList.hasNext()) {
+              Value next = valueWorkList.next();
+              seenEscaped |= state.isEscaped(next);
+              if (next.isPhi()) {
+                aliasConsumer.accept(next);
+              }
+              valueWorkList.addIfNotSeen(
+                  escapedDefinitionsToKnown.getOrDefault(next, Collections.emptySet()));
+            }
+            return seenEscaped;
+          }
+
+          @Override
+          protected TraversalContinuation<Void, StringBuilderGraphState> joiner(
+              DFSNodeWithState<BasicBlock, StringBuilderGraphState> node,
+              List<DFSNodeWithState<BasicBlock, StringBuilderGraphState>> childStates) {
+            StringBuilderGraphState state = node.getState();
+            for (DFSNodeWithState<BasicBlock, StringBuilderGraphState> childState : childStates) {
+              StringBuilderGraphState childGraphState = childState.getState();
+              childGraphState.roots.forEach(
+                  (value, sbNode) -> {
+                    StringBuilderNode currentRoot = state.roots.get(value);
+                    StringBuilderNode currentTail = state.tails.get(value);
+                    if (currentRoot == null) {
+                      assert currentTail == null;
+                      if (childStates.size() == 1) {
+                        state.roots.put(value, sbNode);
+                        state.tails.put(value, sbNode);
+                        return;
+                      }
+                      // We are adding a value coming only from a child state and not defined here.
+                      // To ensure proper ordering we add a sentinel node. If it turns out there is
+                      // only a single reference, we rely on munching to remove it.
+                      currentRoot = StringBuilderNode.createSplitReferenceNode();
+                      currentTail = currentRoot;
+                      state.roots.put(value, currentRoot);
+                      state.tails.put(value, currentTail);
+                    }
+                    assert currentTail != null;
+                    // Link next node from successor
+                    currentTail.addSuccessor(sbNode);
+                    sbNode.addPredecessor(currentTail);
+                  });
+              if (childState.seenAndNotProcessed()) {
+                childGraphState.isPartOfLoop = true;
+              }
+            }
+            if (state.isPartOfLoop) {
+              for (Value value : state.roots.keySet()) {
+                LoopNode loopNode = StringBuilderNode.createLoopNode();
+                loopNode.addSuccessor(state.roots.get(value));
+                state.roots.put(value, loopNode);
+              }
+            }
+            return TraversalContinuation.doContinue(state);
+          }
+        }.run(code.entryBlock());
+
+    return graphResult.shouldBreak()
+        ? Collections.emptyMap()
+        : graphResult.asContinue().getValue().roots;
+  }
+
+  /**
+   * optimizeOnGraphs will compute some state that will make munching easier. When computing the
+   * state the search will also do a topological sort over string builder references such that
+   * string builders without a direct dependency on another string builder will be computed first.
+   *
+   * <p>In general, this would not really matter since we munch over all graphs, but we are limiting
+   * the munching and care about performance.
+   */
+  private Map<Instruction, StringBuilderAction> optimizeOnGraphs(
+      Map<Value, StringBuilderNode> stringBuilderGraphs) {
+    Map<Instruction, StringBuilderAction> actions = new IdentityHashMap<>();
+    // Build state to allow munching over the string builder graphs.
+    Set<StringBuilderNode> inspectingCapacity = Sets.newIdentityHashSet();
+    Set<StringBuilderNode> looping = Sets.newIdentityHashSet();
+    Map<StringBuilderNode, Set<StringBuilderNode>> materializing = new IdentityHashMap<>();
+    Set<StringBuilderNode> escaping = Sets.newIdentityHashSet();
+
+    Map<StringBuilderNode, StringBuilderNode> nodeToRoots = new IdentityHashMap<>();
+    Map<StringBuilderNode, Set<StringBuilderNode>> stringBuilderDependencies =
+        new IdentityHashMap<>();
+
+    stringBuilderGraphs.forEach(
+        (value, root) -> {
+          WorkList<StringBuilderNode> workList = WorkList.newIdentityWorkList(root);
+          Set<StringBuilderNode> materializingInstructions = Sets.newIdentityHashSet();
+          materializing.put(root, materializingInstructions);
+          while (workList.hasNext()) {
+            StringBuilderNode next = workList.next();
+            nodeToRoots.put(next, root);
+            if (next.isInitOrAppend()) {
+              ImplicitToStringNode dependency = next.asInitOrAppend().getImplicitToStringNode();
+              if (dependency != null) {
+                stringBuilderDependencies
+                    .computeIfAbsent(root, ignoreArgument(Sets::newIdentityHashSet))
+                    .add(dependency);
+              }
+            }
+            if (next.isLoopNode()) {
+              looping.add(root);
+            }
+            if (next.isEscapeNode()) {
+              inspectingCapacity.add(root);
+              escaping.add(root);
+            }
+            if (next.isToStringNode() || next.isImplicitToStringNode()) {
+              materializingInstructions.add(root);
+            }
+            if (next.isInspectingNode()) {
+              inspectingCapacity.add(root);
+            }
+            next.getSuccessors().forEach(workList::addFirstIfNotSeen);
+          }
+        });
+
+    MunchingState munchingState =
+        new MunchingState(actions, escaping, inspectingCapacity, looping, materializing, oracle);
+
+    boolean keepMunching = true;
+    for (int i = 0; i < NUMBER_OF_MUNCHING_PASSES && keepMunching; i++) {
+      keepMunching = false;
+      for (StringBuilderNode root :
+          computeProcessingOrder(stringBuilderGraphs, stringBuilderDependencies, nodeToRoots)) {
+        WorkList<StringBuilderNode> workList = WorkList.newIdentityWorkList(root);
+        while (workList.hasNext()) {
+          StringBuilderNode next = workList.next();
+          keepMunching |= StringBuilderNodeMuncher.optimize(root, next, munchingState);
+          next.getSuccessors().forEach(workList::addFirstIfNotSeen);
+        }
+      }
+    }
+    return actions;
+  }
+
+  private Collection<StringBuilderNode> computeProcessingOrder(
+      Map<Value, StringBuilderNode> stringBuilderGraphs,
+      Map<StringBuilderNode, Set<StringBuilderNode>> stringBuilderDependencies,
+      Map<StringBuilderNode, StringBuilderNode> nodeToRoots) {
+    // Make a topological sort to ensure we visit all nodes in the best order for optimizing nested
+    // string builders.
+    Set<StringBuilderNode> processingOrder = new LinkedHashSet<>();
+    new DepthFirstSearchWorkList<StringBuilderNode, Void, Void>() {
+
+      @Override
+      @SuppressWarnings("ReturnValueIgnored")
+      protected TraversalContinuation<Void, Void> process(
+          DFSNode<StringBuilderNode> node,
+          Function<StringBuilderNode, DFSNode<StringBuilderNode>> childNodeConsumer) {
+        StringBuilderNode root = node.getNode();
+        Set<StringBuilderNode> stringBuilderNodes = stringBuilderDependencies.get(root);
+        if (stringBuilderNodes != null) {
+          for (StringBuilderNode dependency : stringBuilderNodes) {
+            childNodeConsumer.apply(nodeToRoots.get(dependency));
+          }
+        }
+        return TraversalContinuation.doContinue();
+      }
+
+      @Override
+      protected List<Void> getFinalStateForRoots(Collection<StringBuilderNode> roots) {
+        return null;
+      }
+
+      @Override
+      public TraversalContinuation<Void, Void> joiner(DFSNode<StringBuilderNode> node) {
+        StringBuilderNode node1 = node.getNode();
+        processingOrder.add(node1);
+        return TraversalContinuation.doContinue();
+      }
+    }.run(stringBuilderGraphs.values());
+    return processingOrder;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderEscapeState.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderEscapeState.java
new file mode 100644
index 0000000..f1e7fb8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderEscapeState.java
@@ -0,0 +1,279 @@
+// 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.string;
+
+import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractState;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.MapUtils;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+public class StringBuilderEscapeState extends AbstractState<StringBuilderEscapeState> {
+
+  private static final StringBuilderEscapeState BOTTOM = new StringBuilderEscapeState();
+
+  public static StringBuilderEscapeState bottom() {
+    return BOTTOM;
+  }
+
+  private final Map<Value, Set<Value>> aliasesToDefinitions;
+  private final Map<Value, Set<Value>> definitionsToAliases;
+  private final Set<Value> escaping;
+  private final Set<Value> liveStringBuilders;
+
+  // Special set for finding new escaped string builders to save us from computing the delta between
+  // two states. The set is not part of the fixed-point computation (and is not checked for equals).
+  private final Set<Value> newlyEscaped;
+
+  private StringBuilderEscapeState() {
+    aliasesToDefinitions = Collections.emptyMap();
+    definitionsToAliases = Collections.emptyMap();
+    escaping = Collections.emptySet();
+    liveStringBuilders = Collections.emptySet();
+    newlyEscaped = Collections.emptySet();
+  }
+
+  public StringBuilderEscapeState(
+      Map<Value, Set<Value>> aliasesToDefinitions,
+      Map<Value, Set<Value>> definitionsToAliases,
+      Set<Value> escaping,
+      Set<Value> liveStringBuilders,
+      Set<Value> newlyEscaped) {
+    assert !aliasesToDefinitions.isEmpty()
+            || !escaping.isEmpty()
+            || !definitionsToAliases.isEmpty()
+            || !liveStringBuilders.isEmpty()
+        : "Creating an instance of BOTTOM";
+    this.aliasesToDefinitions = aliasesToDefinitions;
+    this.definitionsToAliases = definitionsToAliases;
+    this.escaping = escaping;
+    this.liveStringBuilders = liveStringBuilders;
+    this.newlyEscaped = newlyEscaped;
+  }
+
+  public Set<Value> getEscaping() {
+    return escaping;
+  }
+
+  public Map<Value, Set<Value>> getAliasesToDefinitions() {
+    return aliasesToDefinitions;
+  }
+
+  public Map<Value, Set<Value>> getDefinitionsToAliases() {
+    return definitionsToAliases;
+  }
+
+  public Set<Value> getLiveStringBuilders() {
+    return liveStringBuilders;
+  }
+
+  public boolean isLiveStringBuilder(Value sb) {
+    return liveStringBuilders.contains(sb);
+  }
+
+  public boolean isEscaped(Value sb) {
+    return escaping.contains(sb);
+  }
+
+  /**
+   * Should only be used when iterating a completed fix point computation and stepping through
+   * instructions applying the transfer function directly.
+   */
+  public Set<Value> getNewlyEscaped() {
+    return newlyEscaped;
+  }
+
+  public boolean isBottom() {
+    return this == BOTTOM;
+  }
+
+  @Override
+  public StringBuilderEscapeState join(AppView<?> appView, StringBuilderEscapeState other) {
+    if (this.isBottom()) {
+      return other;
+    } else if (other.isBottom()) {
+      return this;
+    } else {
+      Builder builder =
+          builder().addEscaping(other.escaping).addLiveStringBuilders(other.liveStringBuilders);
+      other.aliasesToDefinitions.forEach(builder::addAliasesToDefinitions);
+      other.definitionsToAliases.forEach(builder::addDefinitionsToAliases);
+      return builder.build();
+    }
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof StringBuilderEscapeState)) {
+      return false;
+    }
+    StringBuilderEscapeState that = (StringBuilderEscapeState) o;
+    return MapUtils.equals(aliasesToDefinitions, that.aliasesToDefinitions)
+        && MapUtils.equals(definitionsToAliases, that.definitionsToAliases)
+        && escaping.equals(that.escaping)
+        && liveStringBuilders.equals(that.liveStringBuilders);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(aliasesToDefinitions, definitionsToAliases, escaping, liveStringBuilders);
+  }
+
+  @Override
+  public StringBuilderEscapeState asAbstractState() {
+    return this;
+  }
+
+  public Builder builder() {
+    return new Builder(this);
+  }
+
+  public static class Builder {
+
+    private Map<Value, Set<Value>> aliasesToDefinitions;
+    private Map<Value, Set<Value>> definitionsToAliases;
+    private Set<Value> escaped;
+    private Set<Value> liveStringBuilders;
+    private final Set<Value> newlyEscaping = new HashSet<>();
+
+    private final StringBuilderEscapeState previous;
+
+    public Builder(StringBuilderEscapeState previous) {
+      aliasesToDefinitions = previous.aliasesToDefinitions;
+      definitionsToAliases = previous.definitionsToAliases;
+      escaped = previous.escaping;
+      liveStringBuilders = previous.liveStringBuilders;
+      this.previous = previous;
+    }
+
+    public Builder addAliasesToDefinitions(Value key, Set<Value> stringBuilders) {
+      ensureAliasesToDefinitions();
+      aliasesToDefinitions
+          .computeIfAbsent(key, ignoreArgument(HashSet::new))
+          .addAll(stringBuilders);
+      return this;
+    }
+
+    public Builder addDefinitionsToAliases(Value key, Set<Value> stringBuilders) {
+      ensureDefinitionToAliases();
+      definitionsToAliases
+          .computeIfAbsent(key, ignoreArgument(HashSet::new))
+          .addAll(stringBuilders);
+      return this;
+    }
+
+    public Builder addAlias(Value key, Value stringBuilder) {
+      ensureAliasesToDefinitions();
+      ensureDefinitionToAliases();
+      aliasesToDefinitions.computeIfAbsent(key, ignoreArgument(HashSet::new)).add(stringBuilder);
+      definitionsToAliases.computeIfAbsent(stringBuilder, ignoreArgument(HashSet::new)).add(key);
+      return this;
+    }
+
+    public Builder addEscaping(Collection<Value> escaping) {
+      if (escaping == null) {
+        return this;
+      }
+      ensureNewEscaping();
+      this.escaped.addAll(escaping);
+      return this;
+    }
+
+    public Builder addEscaping(Value value) {
+      ensureNewEscaping();
+      if (escaped.add(value)) {
+        newlyEscaping.add(value);
+      }
+      return this;
+    }
+
+    public Builder addLiveStringBuilders(Collection<Value> liveStringBuilders) {
+      ensureNewLiveStringBuilders();
+      this.liveStringBuilders.addAll(liveStringBuilders);
+      return this;
+    }
+
+    public Builder addLiveStringBuilder(Value stringBuilder) {
+      ensureNewLiveStringBuilders();
+      liveStringBuilders.add(stringBuilder);
+      return this;
+    }
+
+    private void ensureAliasesToDefinitions() {
+      if (aliasesToDefinitions == previous.aliasesToDefinitions) {
+        aliasesToDefinitions = new HashMap<>(previous.aliasesToDefinitions.size() + 1);
+        previous.aliasesToDefinitions.forEach(
+            (key, value) -> aliasesToDefinitions.put(key, Sets.newHashSet(value)));
+      }
+    }
+
+    private void ensureDefinitionToAliases() {
+      if (definitionsToAliases == previous.definitionsToAliases) {
+        definitionsToAliases = new HashMap<>(previous.definitionsToAliases.size() + 1);
+        previous.definitionsToAliases.forEach(
+            (key, value) -> definitionsToAliases.put(key, Sets.newHashSet(value)));
+      }
+    }
+
+    private void ensureNewEscaping() {
+      if (escaped == previous.escaping) {
+        escaped = new HashSet<>(escaped);
+      }
+    }
+
+    private void ensureNewLiveStringBuilders() {
+      if (liveStringBuilders == previous.liveStringBuilders) {
+        liveStringBuilders = new HashSet<>(liveStringBuilders);
+      }
+    }
+
+    public Set<Value> getLiveStringBuilders() {
+      return liveStringBuilders;
+    }
+
+    public Map<Value, Set<Value>> getAliasesToDefinitions() {
+      return aliasesToDefinitions;
+    }
+
+    public Map<Value, Set<Value>> getDefinitionsToAliases() {
+      return definitionsToAliases;
+    }
+
+    public StringBuilderEscapeState build() {
+      assert liveStringBuilders.containsAll(escaped)
+          : "Escaping is not a subset of live string builders";
+      assert liveStringBuilders.containsAll(aliasesToDefinitions.keySet())
+          : "Aliases is not a subset of live string builders";
+      assert escaped.containsAll(newlyEscaping)
+          : "Unexpected value in newlyEscaping not in escaping";
+      assert liveStringBuilders.containsAll(definitionsToAliases.keySet())
+          : "Escaped definitions should all be live";
+      assert definitionsToAliases.values().stream()
+              .allMatch(phis -> liveStringBuilders.containsAll(phis))
+          : "All known escaping definitions should be live string builders";
+      if (previous.liveStringBuilders == liveStringBuilders
+          && previous.escaping == escaped
+          && previous.aliasesToDefinitions == aliasesToDefinitions
+          && previous.definitionsToAliases == definitionsToAliases) {
+        previous.getNewlyEscaped().clear();
+        return previous;
+      }
+      return new StringBuilderEscapeState(
+          aliasesToDefinitions, definitionsToAliases, escaped, liveStringBuilders, newlyEscaping);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderEscapeTransferFunction.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderEscapeTransferFunction.java
new file mode 100644
index 0000000..ff4a62a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderEscapeTransferFunction.java
@@ -0,0 +1,88 @@
+// 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.string;
+
+import static com.android.tools.r8.ir.optimize.string.StringBuilderHelper.isEscapingInstructionForInValues;
+import static com.android.tools.r8.ir.optimize.string.StringBuilderHelper.isEscapingInstructionForOutValues;
+import static com.android.tools.r8.ir.optimize.string.StringBuilderHelper.isInstructionThatIntroducesDefiniteAlias;
+
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractTransferFunction;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunctionResult;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Value;
+
+/**
+ * The StringBuilderEscapeTransferFunction will compute all escaping string builders at any point in
+ * the program. It does so by maintaining a state keeping track of all values known to be string
+ * builders (phi's, assumes, checkcasts) and track when a value escapes.
+ */
+public class StringBuilderEscapeTransferFunction
+    implements AbstractTransferFunction<BasicBlock, Instruction, StringBuilderEscapeState> {
+
+  private final StringBuilderOracle oracle;
+
+  public StringBuilderEscapeTransferFunction(StringBuilderOracle oracle) {
+    this.oracle = oracle;
+  }
+
+  @Override
+  public TransferFunctionResult<StringBuilderEscapeState> applyBlock(
+      BasicBlock block, StringBuilderEscapeState state) {
+    StringBuilderEscapeState.Builder builder = state.builder();
+    block
+        .getPhis()
+        .forEach(
+            phi -> {
+              if (oracle.hasStringBuilderType(phi)) {
+                builder.addLiveStringBuilder(phi);
+              }
+              for (Value operand : phi.getOperands()) {
+                if (isLiveStringBuilder(builder, operand)) {
+                  builder.addLiveStringBuilder(phi);
+                  builder.addAlias(phi, operand);
+                }
+              }
+            });
+    return builder.build();
+  }
+
+  @Override
+  public TransferFunctionResult<StringBuilderEscapeState> apply(
+      Instruction instruction, StringBuilderEscapeState state) {
+    StringBuilderEscapeState.Builder builder = state.builder();
+    boolean isStringBuilderInstruction = oracle.isModeledStringBuilderInstruction(instruction);
+    if (!isStringBuilderInstruction && isEscapingInstructionForInValues(instruction)) {
+      for (Value inValue : instruction.inValues()) {
+        if (isLiveStringBuilder(builder, inValue)) {
+          // TODO(b/232377424): Account for calls to Object (such as Object.toString()).
+          builder.addEscaping(inValue);
+        }
+      }
+    }
+    assert !isStringBuilderInstruction
+        || builder.getLiveStringBuilders().contains(instruction.getFirstOperand());
+    Value outValue = instruction.outValue();
+    if (outValue != null) {
+      if (isInstructionThatIntroducesDefiniteAlias(instruction, oracle)
+          && isLiveStringBuilder(builder, instruction.getFirstOperand())) {
+        builder.addLiveStringBuilder(outValue);
+        builder.addAlias(outValue, instruction.getFirstOperand());
+      } else if (oracle.hasStringBuilderType(outValue)) {
+        builder.addLiveStringBuilder(outValue);
+      }
+      if (!isStringBuilderInstruction
+          && isLiveStringBuilder(builder, instruction.outValue())
+          && (isEscapingInstructionForOutValues(instruction))) {
+        builder.addEscaping(instruction.outValue());
+      }
+    }
+    return builder.build();
+  }
+
+  private boolean isLiveStringBuilder(StringBuilderEscapeState.Builder builderState, Value value) {
+    return builderState.getLiveStringBuilders().contains(value);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderHelper.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderHelper.java
new file mode 100644
index 0000000..a795308
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderHelper.java
@@ -0,0 +1,127 @@
+// 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.string;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.NumberConversion;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
+
+public class StringBuilderHelper {
+
+  static boolean isEscapingInstructionForInValues(Instruction instruction) {
+    return instruction.isFieldPut()
+        || instruction.isInvoke()
+        || instruction.isReturn()
+        || instruction.isArrayPut();
+  }
+
+  static boolean isEscapingInstructionForOutValues(Instruction instruction) {
+    return instruction.isArgument()
+        || instruction.isInvoke()
+        || instruction.isFieldGet()
+        || instruction.isArrayGet()
+        || instruction.isCheckCast();
+  }
+
+  static boolean canMutate(Instruction instruction) {
+    return instruction.isInvoke()
+        || instruction.isFieldInstruction()
+        || instruction.isNewInstance();
+  }
+
+  static boolean isInstructionThatIntroducesDefiniteAlias(
+      Instruction instruction, StringBuilderOracle oracle) {
+    return instruction.isAssume() || instruction.isCheckCast() || oracle.isAppend(instruction);
+  }
+
+  static String extractConstantArgument(
+      DexItemFactory factory, DexMethod method, Value arg, DexType argumentType) {
+    if (arg.isPhi()) {
+      return null;
+    }
+    if (arg.isConstString()) {
+      return arg.definition.asConstString().getValue().toString();
+    }
+    Number constantNumber = extractConstantNumber(factory, arg);
+    if (constantNumber == null) {
+      return null;
+    }
+    if (arg.getType().isPrimitiveType()) {
+      if (argumentType == factory.booleanType) {
+        return String.valueOf(constantNumber.intValue() != 0);
+      } else if (argumentType == factory.byteType) {
+        return String.valueOf(constantNumber.byteValue());
+      } else if (argumentType == factory.shortType) {
+        return String.valueOf(constantNumber.shortValue());
+      } else if (argumentType == factory.charType) {
+        return String.valueOf((char) constantNumber.intValue());
+      } else if (argumentType == factory.intType) {
+        return String.valueOf(constantNumber.intValue());
+      } else if (argumentType == factory.longType) {
+        return String.valueOf(constantNumber.longValue());
+      } else if (argumentType == factory.floatType) {
+        return String.valueOf(constantNumber.floatValue());
+      } else if (argumentType == factory.doubleType) {
+        return String.valueOf(constantNumber.doubleValue());
+      }
+    } else if (arg.getType().isNullType()
+        && !method.isInstanceInitializer(factory)
+        && argumentType != factory.charArrayType) {
+      assert constantNumber.intValue() == 0;
+      return "null";
+    }
+    return null;
+  }
+
+  static Number extractConstantNumber(DexItemFactory factory, Value arg) {
+    if (arg.isPhi()) {
+      return null;
+    }
+    if (arg.definition.isConstNumber()) {
+      ConstNumber cst = arg.definition.asConstNumber();
+      if (cst.outType() == ValueType.LONG) {
+        return cst.getLongValue();
+      } else if (cst.outType() == ValueType.FLOAT) {
+        return cst.getFloatValue();
+      } else if (cst.outType() == ValueType.DOUBLE) {
+        return cst.getDoubleValue();
+      } else {
+        assert cst.outType() == ValueType.INT || cst.outType() == ValueType.OBJECT;
+        return cst.getIntValue();
+      }
+    } else if (arg.definition.isNumberConversion()) {
+      NumberConversion conversion = arg.definition.asNumberConversion();
+      assert conversion.inValues().size() == 1;
+      Number temp = extractConstantNumber(factory, conversion.inValues().get(0));
+      if (temp == null) {
+        return null;
+      }
+      DexType conversionType = conversion.to.toDexType(factory);
+      if (conversionType == factory.booleanType) {
+        return temp.intValue() != 0 ? 1 : 0;
+      } else if (conversionType == factory.byteType) {
+        return temp.byteValue();
+      } else if (conversionType == factory.shortType) {
+        return temp.shortValue();
+      } else if (conversionType == factory.charType) {
+        return temp.intValue();
+      } else if (conversionType == factory.intType) {
+        return temp.intValue();
+      } else if (conversionType == factory.longType) {
+        return temp.longValue();
+      } else if (conversionType == factory.floatType) {
+        return temp.floatValue();
+      } else if (conversionType == factory.doubleType) {
+        return temp.doubleValue();
+      }
+    }
+    return null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNode.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNode.java
new file mode 100644
index 0000000..acd5c47
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNode.java
@@ -0,0 +1,606 @@
+// 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.string;
+
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+/**
+ * StringBuilderNode defines a single point where a string builder operation occur or some abstract
+ * state changes. The node can be assembled into a graph by defining predecessors and successors.
+ */
+class StringBuilderNode {
+
+  interface StringBuilderInstruction {
+
+    Instruction getInstruction();
+
+    boolean isStringBuilderInstructionNode();
+
+    StringBuilderInstruction asStringBuilderInstructionNode();
+  }
+
+  interface InitOrAppend extends StringBuilderInstruction {
+
+    boolean hasConstantArgument();
+
+    String getConstantArgument();
+
+    void setConstantArgument(String constantArgument);
+
+    void setImplicitToStringNode(ImplicitToStringNode node);
+
+    ImplicitToStringNode getImplicitToStringNode();
+  }
+
+  private final Set<StringBuilderNode> successors = Sets.newIdentityHashSet();
+  private final Set<StringBuilderNode> predecessors = Sets.newIdentityHashSet();
+
+  // Field uses to ensure that munching will not operate on the same value multiple times. If all
+  // peep holes would look in the same direction, this field could be removed.
+  private boolean isDead = false;
+
+  private StringBuilderNode() {}
+
+  boolean isEscapeNode() {
+    return false;
+  }
+
+  boolean isMutateNode() {
+    return false;
+  }
+
+  boolean isSplitReferenceNode() {
+    return false;
+  }
+
+  boolean isLoopNode() {
+    return false;
+  }
+
+  boolean isNewInstanceNode() {
+    return false;
+  }
+
+  boolean isInitNode() {
+    return false;
+  }
+
+  boolean isAppendNode() {
+    return false;
+  }
+
+  boolean isInitOrAppend() {
+    return false;
+  }
+
+  boolean isToStringNode() {
+    return false;
+  }
+
+  boolean isInspectingNode() {
+    return false;
+  }
+
+  boolean isOtherStringBuilderNode() {
+    return false;
+  }
+
+  boolean isImplicitToStringNode() {
+    return false;
+  }
+
+  boolean isStringBuilderInstructionNode() {
+    return false;
+  }
+
+  NewInstanceNode asNewInstanceNode() {
+    return null;
+  }
+
+  InitNode asInitNode() {
+    return null;
+  }
+
+  AppendNode asAppendNode() {
+    return null;
+  }
+
+  InitOrAppend asInitOrAppend() {
+    return null;
+  }
+
+  ToStringNode asToStringNode() {
+    return null;
+  }
+
+  InspectingNode asInspectingNode() {
+    return null;
+  }
+
+  OtherStringBuilderNode asOtherStringBuilderNode() {
+    return null;
+  }
+
+  ImplicitToStringNode asImplicitToStringNode() {
+    return null;
+  }
+
+  StringBuilderInstruction asStringBuilderInstructionNode() {
+    return null;
+  }
+
+  boolean isDead() {
+    return isDead;
+  }
+
+  boolean hasSingleSuccessor() {
+    return successors.size() == 1;
+  }
+
+  StringBuilderNode getSingleSuccessor() {
+    assert hasSingleSuccessor();
+    return successors.iterator().next();
+  }
+
+  void addSuccessor(StringBuilderNode successor) {
+    successors.add(successor);
+    successor.predecessors.add(this);
+  }
+
+  Set<StringBuilderNode> getSuccessors() {
+    return successors;
+  }
+
+  boolean hasSinglePredecessor() {
+    return predecessors.size() == 1;
+  }
+
+  StringBuilderNode getSinglePredecessor() {
+    assert hasSinglePredecessor();
+    return predecessors.iterator().next();
+  }
+
+  Set<StringBuilderNode> getPredecessors() {
+    return predecessors;
+  }
+
+  void addPredecessor(StringBuilderNode predecessor) {
+    predecessors.add(predecessor);
+  }
+
+  void removeNode() {
+    for (StringBuilderNode successor : this.getSuccessors()) {
+      successor.getPredecessors().remove(this);
+      successor.getPredecessors().addAll(this.getPredecessors());
+    }
+    for (StringBuilderNode predecessor : this.getPredecessors()) {
+      predecessor.getSuccessors().remove(this);
+      predecessor.getSuccessors().addAll(this.getSuccessors());
+    }
+    isDead = true;
+  }
+
+  /** EscapeNode is used to explicitly define an escape point for a StringBuilder. */
+  static class EscapeNode extends StringBuilderNode {
+
+    @Override
+    boolean isEscapeNode() {
+      return true;
+    }
+  }
+
+  /**
+   * MutateNode defines if a string builder could be possibly mutated or inspected. An example could
+   * be:
+   *
+   * <pre>
+   * sb = new StringBuilder();
+   * escape(sb);
+   * sb.append("foo");
+   * canMutate(); <-- This is represented by a MutateNode.
+   * sb.append("bar");
+   * </pre>
+   */
+  static class MutateNode extends StringBuilderNode {
+
+    @Override
+    boolean isMutateNode() {
+      return true;
+    }
+  }
+
+  /**
+   * SplitReferenceNodes are synthetic nodes inserted when a string builder is used in multiple
+   * successor blocks.
+   */
+  static class SplitReferenceNode extends StringBuilderNode {
+
+    @Override
+    boolean isSplitReferenceNode() {
+      return true;
+    }
+  }
+
+  /**
+   * LoopNode is a node indicating that all successor paths are part of a loop. LoopNode's are only
+   * inserted once ensuring there are no loops in a StringBuilderGraph.
+   */
+  static class LoopNode extends StringBuilderNode {
+
+    @Override
+    boolean isLoopNode() {
+      return true;
+    }
+  }
+
+  /** A NewInstanceNode is a new instance of either StringBuilder or StringBuffer. */
+  static class NewInstanceNode extends StringBuilderNode implements StringBuilderInstruction {
+
+    private final NewInstance instruction;
+
+    private NewInstanceNode(NewInstance instruction) {
+      this.instruction = instruction;
+    }
+
+    @Override
+    boolean isNewInstanceNode() {
+      return true;
+    }
+
+    @Override
+    NewInstanceNode asNewInstanceNode() {
+      return this;
+    }
+
+    @Override
+    public Instruction getInstruction() {
+      return instruction;
+    }
+
+    @Override
+    public boolean isStringBuilderInstructionNode() {
+      return true;
+    }
+
+    @Override
+    public StringBuilderInstruction asStringBuilderInstructionNode() {
+      return this;
+    }
+  }
+
+  /** An initNode is where a StringBuilder/StringBuffer is initialized. */
+  static class InitNode extends StringBuilderNode
+      implements InitOrAppend, StringBuilderInstruction {
+
+    private final InvokeDirect instruction;
+    private ImplicitToStringNode implicitToStringNode;
+    private String constantArgument;
+
+    private InitNode(InvokeDirect instruction) {
+      this.instruction = instruction;
+    }
+
+    @Override
+    boolean isInitNode() {
+      return true;
+    }
+
+    @Override
+    boolean isInitOrAppend() {
+      return true;
+    }
+
+    @Override
+    InitNode asInitNode() {
+      return this;
+    }
+
+    @Override
+    InitOrAppend asInitOrAppend() {
+      return this;
+    }
+
+    @Override
+    public Instruction getInstruction() {
+      return instruction;
+    }
+
+    @Override
+    public boolean isStringBuilderInstructionNode() {
+      return true;
+    }
+
+    @Override
+    public StringBuilderInstruction asStringBuilderInstructionNode() {
+      return this;
+    }
+
+    @Override
+    public void setConstantArgument(String constantArgument) {
+      this.constantArgument = constantArgument;
+    }
+
+    @Override
+    public void setImplicitToStringNode(ImplicitToStringNode node) {
+      implicitToStringNode = node;
+    }
+
+    @Override
+    public ImplicitToStringNode getImplicitToStringNode() {
+      return implicitToStringNode;
+    }
+
+    @Override
+    public String getConstantArgument() {
+      return constantArgument;
+    }
+
+    @Override
+    public boolean hasConstantArgument() {
+      return constantArgument != null;
+    }
+  }
+
+  /** AppendNodes are StringBuilder.append or StringBuffer.append. */
+  static class AppendNode extends StringBuilderNode
+      implements InitOrAppend, StringBuilderInstruction {
+
+    private final InvokeVirtual instruction;
+    private ImplicitToStringNode implicitToStringNode;
+    private String constantArgument;
+
+    private AppendNode(InvokeVirtual instruction) {
+      this.instruction = instruction;
+    }
+
+    @Override
+    boolean isAppendNode() {
+      return true;
+    }
+
+    @Override
+    boolean isInitOrAppend() {
+      return true;
+    }
+
+    @Override
+    AppendNode asAppendNode() {
+      return this;
+    }
+
+    @Override
+    InitOrAppend asInitOrAppend() {
+      return this;
+    }
+
+    @Override
+    public Instruction getInstruction() {
+      return instruction;
+    }
+
+    @Override
+    public boolean isStringBuilderInstructionNode() {
+      return true;
+    }
+
+    @Override
+    public StringBuilderInstruction asStringBuilderInstructionNode() {
+      return this;
+    }
+
+    @Override
+    public void setConstantArgument(String constantArgument) {
+      this.constantArgument = constantArgument;
+    }
+
+    @Override
+    public void setImplicitToStringNode(ImplicitToStringNode node) {
+      implicitToStringNode = node;
+    }
+
+    @Override
+    public ImplicitToStringNode getImplicitToStringNode() {
+      return implicitToStringNode;
+    }
+
+    @Override
+    public String getConstantArgument() {
+      return constantArgument;
+    }
+
+    @Override
+    public boolean hasConstantArgument() {
+      return constantArgument != null;
+    }
+  }
+
+  /**
+   * ToStringNodes marked a point where a StringBuilder/StringBuffer is materialized to a string.
+   */
+  static class ToStringNode extends StringBuilderNode implements StringBuilderInstruction {
+
+    private final InvokeVirtual instruction;
+
+    private ToStringNode(InvokeVirtual instruction) {
+      this.instruction = instruction;
+    }
+
+    @Override
+    boolean isToStringNode() {
+      return true;
+    }
+
+    @Override
+    ToStringNode asToStringNode() {
+      return this;
+    }
+
+    @Override
+    public Instruction getInstruction() {
+      return instruction;
+    }
+
+    @Override
+    public boolean isStringBuilderInstructionNode() {
+      return true;
+    }
+
+    @Override
+    public StringBuilderInstruction asStringBuilderInstructionNode() {
+      return this;
+    }
+  }
+
+  /**
+   * InspectingNode is inserted if there is inspection of the capacity of a
+   * StringBuilder/StringBuffer. Special care needs to be taken since we try to ensure that capacity
+   * will be the same after optimizations.
+   */
+  static class InspectingNode extends StringBuilderNode implements StringBuilderInstruction {
+
+    private final Instruction instruction;
+
+    private InspectingNode(Instruction instruction) {
+      this.instruction = instruction;
+    }
+
+    @Override
+    boolean isInspectingNode() {
+      return true;
+    }
+
+    @Override
+    InspectingNode asInspectingNode() {
+      return this;
+    }
+
+    @Override
+    public Instruction getInstruction() {
+      return instruction;
+    }
+
+    @Override
+    public boolean isStringBuilderInstructionNode() {
+      return true;
+    }
+
+    @Override
+    public StringBuilderInstruction asStringBuilderInstructionNode() {
+      return this;
+    }
+  }
+
+  /** OtherStringBuilderNode marks operations on string builders we do not model. */
+  static class OtherStringBuilderNode extends StringBuilderNode
+      implements StringBuilderInstruction {
+
+    private final Instruction instruction;
+
+    private OtherStringBuilderNode(Instruction instruction) {
+      this.instruction = instruction;
+    }
+
+    @Override
+    boolean isOtherStringBuilderNode() {
+      return true;
+    }
+
+    @Override
+    OtherStringBuilderNode asOtherStringBuilderNode() {
+      return this;
+    }
+
+    @Override
+    public Instruction getInstruction() {
+      return instruction;
+    }
+
+    @Override
+    public boolean isStringBuilderInstructionNode() {
+      return true;
+    }
+
+    @Override
+    public StringBuilderInstruction asStringBuilderInstructionNode() {
+      return this;
+    }
+  }
+
+  /**
+   * ImplicitToStringNode are placed a StringBuilder/StringBuffer is appended to another
+   * StringBuilder/StringBuffer.
+   */
+  static class ImplicitToStringNode extends StringBuilderNode {
+
+    private final InitOrAppend initOrAppend;
+
+    ImplicitToStringNode(InitOrAppend initOrAppend) {
+      this.initOrAppend = initOrAppend;
+    }
+
+    public InitOrAppend getInitOrAppend() {
+      return initOrAppend;
+    }
+
+    @Override
+    boolean isImplicitToStringNode() {
+      return true;
+    }
+
+    @Override
+    ImplicitToStringNode asImplicitToStringNode() {
+      return this;
+    }
+  }
+
+  static EscapeNode createEscapeNode() {
+    return new EscapeNode();
+  }
+
+  static MutateNode createMutateNode() {
+    return new MutateNode();
+  }
+
+  static SplitReferenceNode createSplitReferenceNode() {
+    return new SplitReferenceNode();
+  }
+
+  static LoopNode createLoopNode() {
+    return new LoopNode();
+  }
+
+  static NewInstanceNode createNewInstanceNode(NewInstance instruction) {
+    return new NewInstanceNode(instruction);
+  }
+
+  static InitNode createInitNode(InvokeDirect instruction) {
+    return new InitNode(instruction);
+  }
+
+  static AppendNode createAppendNode(InvokeVirtual instruction) {
+    return new AppendNode(instruction);
+  }
+
+  static ToStringNode createToStringNode(InvokeVirtual instruction) {
+    return new ToStringNode(instruction);
+  }
+
+  static InspectingNode createInspectionNode(Instruction instruction) {
+    return new InspectingNode(instruction);
+  }
+
+  static OtherStringBuilderNode createOtherStringBuilderNode(Instruction instruction) {
+    return new OtherStringBuilderNode(instruction);
+  }
+
+  static ImplicitToStringNode createImplicitToStringNode(InitOrAppend otherNode) {
+    return new ImplicitToStringNode(otherNode);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java
new file mode 100644
index 0000000..000ca52
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java
@@ -0,0 +1,340 @@
+// 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.string;
+
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.string.StringBuilderAction.AppendWithNewConstantString;
+import com.android.tools.r8.ir.optimize.string.StringBuilderAction.RemoveStringBuilderAction;
+import com.android.tools.r8.ir.optimize.string.StringBuilderAction.ReplaceByConstantString;
+import com.android.tools.r8.ir.optimize.string.StringBuilderNode.AppendNode;
+import com.android.tools.r8.ir.optimize.string.StringBuilderNode.ImplicitToStringNode;
+import com.android.tools.r8.ir.optimize.string.StringBuilderNode.InitNode;
+import com.android.tools.r8.ir.optimize.string.StringBuilderNode.InitOrAppend;
+import com.android.tools.r8.ir.optimize.string.StringBuilderNode.StringBuilderInstruction;
+import com.android.tools.r8.ir.optimize.string.StringBuilderNode.ToStringNode;
+import com.android.tools.r8.utils.WorkList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * StringBuilderNodeMuncher is a classic munching algorithm that will try to remove nodes on string
+ * builders by looking at small view of it {@link PeepholePattern}. If a pattern can be optimized
+ * the graph will be updated in place and if the IR needs to be updated, an action will be added.
+ */
+class StringBuilderNodeMuncher {
+
+  static class MunchingState {
+
+    private final Map<Instruction, StringBuilderAction> actions;
+    private final StringBuilderOracle oracle;
+
+    // The below state can all be computed by searching the graph but are tracked here to improve
+    // performance.
+    private final Set<StringBuilderNode> escaping;
+    private final Set<StringBuilderNode> inspectingCapacity;
+    private final Set<StringBuilderNode> looping;
+    private final Map<StringBuilderNode, Set<StringBuilderNode>> materializingInstructions;
+    private final Map<Value, String> optimizedStrings = new IdentityHashMap<>();
+
+    MunchingState(
+        Map<Instruction, StringBuilderAction> actions,
+        Set<StringBuilderNode> escaping,
+        Set<StringBuilderNode> inspectingCapacity,
+        Set<StringBuilderNode> looping,
+        Map<StringBuilderNode, Set<StringBuilderNode>> materializingInstructions,
+        StringBuilderOracle oracle) {
+      this.actions = actions;
+      this.escaping = escaping;
+      this.inspectingCapacity = inspectingCapacity;
+      this.looping = looping;
+      this.materializingInstructions = materializingInstructions;
+      this.oracle = oracle;
+    }
+  }
+
+  private interface PeepholePattern {
+
+    boolean optimize(
+        StringBuilderNode root, StringBuilderNode currentNode, MunchingState munchingState);
+  }
+
+  /**
+   * This peephole will try to optimize two sequential appends:
+   *
+   * <pre>
+   * append("foo") -> append("bar") =>
+   * append("foobar")
+   *
+   * or
+   *
+   * init("foo") -> append("bar") =>
+   * init("foobar")
+   * </pre>
+   */
+  private static class MunchAppends implements PeepholePattern {
+
+    @Override
+    public boolean optimize(
+        StringBuilderNode root, StringBuilderNode currentNode, MunchingState munchingState) {
+      if (!currentNode.isAppendNode()) {
+        return false;
+      }
+      String currentConstantArgument = getConstantArgumentForNode(currentNode, munchingState);
+      if (currentConstantArgument == null || !currentNode.hasSinglePredecessor()) {
+        return false;
+      }
+      StringBuilderNode previous = currentNode.getSinglePredecessor();
+      String previousConstantArgument = getConstantArgumentForNode(previous, munchingState);
+      if (previousConstantArgument == null || !previous.hasSingleSuccessor()) {
+        return false;
+      }
+      // The capacity changes based on the init call (on JVM it adds 16 to length of input).
+      if (previous.isInitNode() && munchingState.inspectingCapacity.contains(root)) {
+        return false;
+      }
+      assert previous.isInitOrAppend();
+      String newConstant = previousConstantArgument + currentConstantArgument;
+      InitOrAppend initOrAppend = previous.asInitOrAppend();
+      initOrAppend.setConstantArgument(newConstant);
+      munchingState.actions.put(
+          initOrAppend.getInstruction(), new AppendWithNewConstantString(newConstant));
+      munchingState.actions.put(
+          currentNode.asAppendNode().getInstruction(), RemoveStringBuilderAction.getInstance());
+      currentNode.removeNode();
+      return true;
+    }
+  }
+
+  /**
+   * This peephole will try to remove toString nodes and replace by a constant string:
+   *
+   * <pre>
+   * newInstance -> init("foo") -> append("bar") -> toString() =>
+   * newInstance -> init("foo") -> append("bar")
+   * </pre>
+   *
+   * <p>If the node is an implicitToString, we update the append of another builder to have the new
+   * constant value directly. If not, we keep track of the outValue toString() had is replaced by a
+   * constant, by updating {@code MunchingState.optimizedStrings}
+   */
+  private static class MunchToString implements PeepholePattern {
+
+    @Override
+    public boolean optimize(
+        StringBuilderNode originalRoot,
+        StringBuilderNode currentNode,
+        MunchingState munchingState) {
+      if (!currentNode.isToStringNode() && !currentNode.isImplicitToStringNode()) {
+        return false;
+      }
+      StringBuilderNode root = findFirstNonSentinelRoot(originalRoot);
+      if (!root.isNewInstanceNode() || !root.hasSingleSuccessor()) {
+        return false;
+      }
+      StringBuilderNode init = root.getSingleSuccessor();
+      String rootConstantArgument = getConstantArgumentForNode(init, munchingState);
+      if (rootConstantArgument == null || !init.isInitNode()) {
+        return false;
+      }
+      // This is either <init>(str) -> toString() or <init>(str) -> append(str) -> toString()
+      // If the string builder dependency is directly given to another string builder, there is
+      // no toString() but an append with this string builder as argument.
+      if (!currentNode.hasSinglePredecessor() || !init.hasSingleSuccessor()) {
+        return false;
+      }
+      String constantArgument = null;
+      if (currentNode.getSinglePredecessor() == init) {
+        constantArgument = rootConstantArgument;
+      } else {
+        StringBuilderNode expectedAppend = init.getSingleSuccessor();
+        StringBuilderNode expectedSameAppend = currentNode.getSinglePredecessor();
+        String appendConstantArgument = getConstantArgumentForNode(expectedAppend, munchingState);
+        if (expectedAppend == expectedSameAppend && appendConstantArgument != null) {
+          // TODO(b/190489514): See if this larger pattern is necessary.
+          assert false : "See why this larger pattern is necessary";
+          constantArgument = rootConstantArgument + appendConstantArgument;
+        }
+      }
+      if (constantArgument == null) {
+        return false;
+      }
+      if (currentNode.isToStringNode()) {
+        ToStringNode toStringNode = currentNode.asToStringNode();
+        munchingState.actions.put(
+            toStringNode.getInstruction(), new ReplaceByConstantString(constantArgument));
+        munchingState.materializingInstructions.get(originalRoot).remove(currentNode);
+        String oldValue =
+            munchingState.optimizedStrings.put(
+                toStringNode.getInstruction().outValue(), constantArgument);
+        assert oldValue == null;
+      } else {
+        assert currentNode.isImplicitToStringNode();
+        ImplicitToStringNode implicitToStringNode = currentNode.asImplicitToStringNode();
+        InitOrAppend initOrAppend = implicitToStringNode.getInitOrAppend();
+        initOrAppend.setConstantArgument(constantArgument);
+        munchingState.actions.put(
+            initOrAppend.getInstruction(), new AppendWithNewConstantString(constantArgument));
+      }
+      currentNode.removeNode();
+      return true;
+    }
+  }
+
+  /**
+   * Find the first non split reference node or loop-node, which are nodes inserted to track
+   * control-flow.
+   */
+  private static StringBuilderNode findFirstNonSentinelRoot(StringBuilderNode root) {
+    WorkList<StringBuilderNode> workList = WorkList.newIdentityWorkList(root);
+    while (workList.hasNext()) {
+      StringBuilderNode node = workList.next();
+      if (!node.isSplitReferenceNode() && !node.isLoopNode()) {
+        return node;
+      }
+      if (node.hasSingleSuccessor()) {
+        workList.addIfNotSeen(node.getSingleSuccessor());
+      }
+    }
+    return root;
+  }
+
+  private static String getConstantArgumentForNode(
+      StringBuilderNode node, MunchingState munchingState) {
+    if (node.isAppendNode()) {
+      AppendNode appendNode = node.asAppendNode();
+      if (appendNode.hasConstantArgument()) {
+        return appendNode.getConstantArgument();
+      }
+      return getOptimizedConstantArgument(appendNode, munchingState);
+    } else if (node.isInitNode()) {
+      InitNode initNode = node.asInitNode();
+      if (initNode.hasConstantArgument()) {
+        return initNode.getConstantArgument();
+      }
+      return getOptimizedConstantArgument(initNode, munchingState);
+    }
+    return null;
+  }
+
+  private static String getOptimizedConstantArgument(
+      StringBuilderInstruction node, MunchingState munchingState) {
+    List<Value> inValues = node.getInstruction().inValues();
+    if (inValues.size() != 2) {
+      return null;
+    }
+    return munchingState.optimizedStrings.get(inValues.get(1).getAliasedValue());
+  }
+
+  /**
+   * This pattern tries to remove nodes that are no longer needed. An example is when a toString is
+   * materialized, and append may now not be observable and can be removed. This pattern tries to
+   * remove as many nodes as possibly by continuing to remove predecessor nodes. This is not
+   * necessary for correctness but since the overall munching algorithm runs over successors,
+   * removing predecessors directly here saves cycles.
+   */
+  private static class MunchNonMaterializing implements PeepholePattern {
+
+    @Override
+    public boolean optimize(
+        StringBuilderNode root, StringBuilderNode currentNode, MunchingState munchingState) {
+      boolean removedAnyNodes = false;
+      boolean removeNode;
+      boolean isEscaping = munchingState.escaping.contains(root);
+      while (currentNode != null) {
+        removeNode =
+            currentNode.isSplitReferenceNode()
+                && (currentNode.getSuccessors().isEmpty() || currentNode.hasSinglePredecessor());
+        // Remove appends if the string builder do not escape, is not inspected or materialized
+        if (removeNode) {
+          // TODO(b/190489514): See if this larger pattern is necessary.
+          assert false : "Check when this happens";
+        }
+        // and if it is not part of a loop.
+        if (currentNode.isAppendNode() && !isEscaping) {
+          AppendNode appendNode = currentNode.asAppendNode();
+          boolean canRemoveIfNoInspectionOrMaterializing =
+              !munchingState.inspectingCapacity.contains(root)
+                  && munchingState.materializingInstructions.get(root).isEmpty();
+          boolean canRemoveIfLastAndNoLoop =
+              !isLoopingOnPath(root, currentNode, munchingState)
+                  && currentNode.getSuccessors().isEmpty();
+          if (canRemoveIfNoInspectionOrMaterializing
+              && canRemoveIfLastAndNoLoop
+              && munchingState.oracle.canObserveStringBuilderCall(
+                  currentNode.asAppendNode().getInstruction())) {
+            munchingState.actions.put(
+                appendNode.getInstruction(), RemoveStringBuilderAction.getInstance());
+            removeNode = true;
+          }
+        }
+        if (currentNode.isInitNode()
+            && currentNode.asInitNode().hasConstantArgument()
+            && currentNode.hasSinglePredecessor()
+            && currentNode.getSinglePredecessor().isNewInstanceNode()
+            && currentNode.getSuccessors().isEmpty()
+            && !isEscaping
+            && !munchingState.oracle.canObserveStringBuilderCall(
+                currentNode.asInitNode().getInstruction())) {
+          removeNode = true;
+        }
+        if (!removeNode) {
+          return false;
+        } else {
+          removedAnyNodes = true;
+          currentNode.removeNode();
+          if (currentNode.isStringBuilderInstructionNode()) {
+            munchingState.actions.put(
+                currentNode.asStringBuilderInstructionNode().getInstruction(),
+                RemoveStringBuilderAction.getInstance());
+          }
+          currentNode =
+              currentNode.hasSinglePredecessor() ? currentNode.getSinglePredecessor() : null;
+        }
+      }
+      return removedAnyNodes;
+    }
+
+    private boolean isLoopingOnPath(
+        StringBuilderNode root, StringBuilderNode currentNode, MunchingState munchingState) {
+      if (!munchingState.looping.contains(root)) {
+        return false;
+      }
+      WorkList<StringBuilderNode> workList = WorkList.newIdentityWorkList(currentNode);
+      boolean seenNewInstance = false;
+      while (workList.hasNext()) {
+        StringBuilderNode next = workList.next();
+        if (next.isNewInstanceNode()) {
+          seenNewInstance = true;
+        }
+        if (next.isLoopNode()) {
+          // If we have seen a new instance and there is only a single successor for the loop
+          // the instance is always created inside the body.
+          return !seenNewInstance || !next.hasSingleSuccessor();
+        }
+        workList.addIfNotSeen(next.getPredecessors());
+      }
+      return false;
+    }
+  }
+
+  private static final PeepholePattern[] peepholePatterns =
+      new PeepholePattern[] {new MunchAppends(), new MunchToString(), new MunchNonMaterializing()};
+
+  static boolean optimize(
+      StringBuilderNode root, StringBuilderNode currentNode, MunchingState munchingState) {
+    if (currentNode.isDead()) {
+      return false;
+    }
+    for (PeepholePattern peepholePattern : peepholePatterns) {
+      if (peepholePattern.optimize(root, currentNode, munchingState)) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
index 3ad14fb..957ccfe 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -722,7 +722,7 @@
         return null;
       }
       if (StringBuilderAppendFlowAnalysis.hasAppendInstructionInLoop(
-          code, builder, optimizationConfiguration)) {
+          appView, code, builder, optimizationConfiguration)) {
         return null;
       }
       return StringUtils.join("", contents);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java
new file mode 100644
index 0000000..b6c0b4f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java
@@ -0,0 +1,186 @@
+// 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.string;
+
+import static com.android.tools.r8.ir.optimize.string.StringBuilderHelper.extractConstantArgument;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexItemFactory.StringBuildingMethods;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Value;
+import java.util.List;
+
+/**
+ * The {@link StringBuilderOracle} can answer if an instruction is of particular interest to the
+ * StringBuilderOptimization.
+ */
+interface StringBuilderOracle {
+
+  boolean isModeledStringBuilderInstruction(Instruction instruction);
+
+  boolean hasStringBuilderType(Value value);
+
+  boolean isStringBuilderType(DexType type);
+
+  boolean isToString(Instruction instruction);
+
+  String getConstantArgument(Instruction instruction);
+
+  boolean isInspecting(Instruction instruction);
+
+  boolean isAppend(Instruction instruction);
+
+  boolean canObserveStringBuilderCall(Instruction instruction);
+
+  boolean isInit(Instruction instruction);
+
+  class DefaultStringBuilderOracle implements StringBuilderOracle {
+
+    private final DexItemFactory factory;
+
+    DefaultStringBuilderOracle(DexItemFactory factory) {
+      this.factory = factory;
+    }
+
+    @Override
+    public boolean isModeledStringBuilderInstruction(Instruction instruction) {
+      if (instruction.isNewInstance()) {
+        return isStringBuilderType(instruction.asNewInstance().getType());
+      } else if (instruction.isInvokeMethod()) {
+        DexMethod invokedMethod = instruction.asInvokeMethod().getInvokedMethod();
+        return isStringBuildingMethod(factory.stringBuilderMethods, invokedMethod)
+            || isStringBuildingMethod(factory.stringBufferMethods, invokedMethod);
+      }
+      return false;
+    }
+
+    private boolean isStringBuildingMethod(StringBuildingMethods methods, DexMethod method) {
+      return methods.isAppendMethod(method)
+          || methods.isConstructorMethod(method)
+          || method == methods.toString
+          || method == methods.capacity;
+    }
+
+    @Override
+    public boolean hasStringBuilderType(Value value) {
+      return value.getType().isClassType()
+          && isStringBuilderType(value.getType().asClassType().getClassType());
+    }
+
+    @Override
+    public boolean isStringBuilderType(DexType type) {
+      return type == factory.stringBuilderType || type == factory.stringBufferType;
+    }
+
+    @Override
+    public boolean isToString(Instruction instruction) {
+      if (!instruction.isInvokeMethodWithReceiver()) {
+        return false;
+      }
+      InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
+      DexMethod invokedMethod = invoke.getInvokedMethod();
+      return factory.stringBuilderMethods.toString == invokedMethod
+          || factory.stringBufferMethods.toString == invokedMethod;
+    }
+
+    @Override
+    public String getConstantArgument(Instruction instruction) {
+      if (!instruction.isInvokeMethodWithReceiver()) {
+        return null;
+      }
+      if (isAppend(instruction)) {
+        return getConstantStringForAppend(instruction.asInvokeVirtual());
+      } else if (isInit(instruction)) {
+        return getConstantStringForInit(instruction.asInvokeDirect());
+      }
+      return null;
+    }
+
+    private DexType getAppendType(InvokeVirtual invokeMethodWithReceiver) {
+      DexMethod invokedMethod = invokeMethodWithReceiver.getInvokedMethod();
+      if (!factory.stringBuilderMethods.isAppendMethod(invokedMethod)
+          && !factory.stringBufferMethods.isAppendMethod(invokedMethod)) {
+        return null;
+      }
+      return invokedMethod.getParameter(0);
+    }
+
+    private String getConstantStringForAppend(InvokeVirtual invoke) {
+      DexType appendType = getAppendType(invoke);
+      Value arg = invoke.getFirstNonReceiverArgument().getAliasedValue();
+      return appendType != null
+          ? extractConstantArgument(factory, invoke.getInvokedMethod(), arg, appendType)
+          : null;
+    }
+
+    private String getConstantStringForInit(InvokeDirect invoke) {
+      assert invoke.isInvokeConstructor(factory);
+      List<Value> inValues = invoke.inValues();
+      if (inValues.size() == 1) {
+        return "";
+      } else if (inValues.size() == 2 && !invoke.getArgument(1).getType().isPrimitiveType()) {
+        Value arg = invoke.getArgument(1).getAliasedValue();
+        DexType argType = invoke.getInvokedMethod().getParameter(0);
+        return argType != null
+            ? extractConstantArgument(factory, invoke.getInvokedMethod(), arg, argType)
+            : null;
+      }
+      return null;
+    }
+
+    @Override
+    public boolean isInspecting(Instruction instruction) {
+      if (!instruction.isInvokeMethodWithReceiver()) {
+        return false;
+      }
+      DexMethod invokedMethod = instruction.asInvokeMethodWithReceiver().getInvokedMethod();
+      return factory.stringBuilderMethods.capacity == invokedMethod
+          || factory.stringBufferMethods.capacity == invokedMethod;
+    }
+
+    @Override
+    public boolean isAppend(Instruction instruction) {
+      if (!instruction.isInvokeMethod()) {
+        return false;
+      }
+      DexMethod invokedMethod = instruction.asInvokeMethod().getInvokedMethod();
+      return factory.stringBuilderMethods.isAppendMethod(invokedMethod)
+          || factory.stringBufferMethods.isAppendMethod(invokedMethod);
+    }
+
+    @Override
+    public boolean canObserveStringBuilderCall(Instruction instruction) {
+      assert isModeledStringBuilderInstruction(instruction);
+      if (!instruction.isInvokeMethod()) {
+        assert false : "Expecting a call to string builder";
+        return true;
+      }
+      DexMethod invokedMethod = instruction.asInvokeMethod().getInvokedMethod();
+      if (factory.stringBuilderMethods.isAppendObjectOrCharSequenceMethod(invokedMethod)
+          || factory.stringBufferMethods.isAppendObjectOrCharSequenceMethod(invokedMethod)
+          || factory.stringBuilderMethods.charSequenceConstructor == invokedMethod
+          || factory.stringBufferMethods.charSequenceConstructor == invokedMethod) {
+        assert instruction.inValues().size() == 2;
+        return instruction.inValues().get(1).getType().isStringType(factory);
+      }
+      return false;
+    }
+
+    @Override
+    public boolean isInit(Instruction instruction) {
+      if (!instruction.isInvokeDirect()) {
+        return false;
+      }
+      DexMethod invokedMethod = instruction.asInvokeMethod().getInvokedMethod();
+      return factory.stringBuilderMethods.isConstructorMethod(invokedMethod)
+          || factory.stringBufferMethods.isConstructorMethod(invokedMethod);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java
index 10e267b..fb04da0 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java
@@ -244,117 +244,4 @@
       return standardCfCodeFromInstructions(instructions);
     }
   }
-
-  public static class CollectionConversionCfCodeProvider extends NullableConversionCfCodeProvider {
-
-    private final DexType collectionType;
-    private final DexMethod conversion;
-
-    public CollectionConversionCfCodeProvider(
-        AppView<?> appView, DexType holder, DexType collectionType, DexMethod conversion) {
-      super(appView, holder);
-      this.collectionType = collectionType;
-      this.conversion = conversion;
-    }
-
-    @Override
-    public CfCode generateCfCode() {
-      DexItemFactory factory = appView.dexItemFactory();
-      List<CfInstruction> instructions = new ArrayList<>();
-
-      // if (arg == null) { return null; }
-      generateNullCheck(instructions);
-      instructions.add(
-          CfFrame.builder().appendLocal(FrameType.initialized(collectionType)).build());
-
-      CfFrame frame =
-          CfFrame.builder()
-              .appendLocal(FrameType.initialized(collectionType))
-              .appendLocal(FrameType.initialized(collectionType))
-              .appendLocal(FrameType.initialized(factory.iteratorType))
-              .build();
-
-      // Collection<E> t1 = new Collection<E>();
-      if (collectionType == factory.setType) {
-        DexType hashSetType = factory.createType("Ljava/util/HashSet;");
-        instructions.add(new CfNew(hashSetType));
-        instructions.add(
-            new CfInvoke(
-                Opcodes.INVOKESPECIAL,
-                factory.createMethod(
-                    hashSetType,
-                    factory.createProto(factory.voidType),
-                    factory.constructorMethodName),
-                false));
-      } else {
-        assert collectionType == factory.listType;
-        DexType arrayListType = factory.createType("Ljava/util/ArrayList;");
-        instructions.add(new CfNew(arrayListType));
-        instructions.add(
-            new CfInvoke(
-                Opcodes.INVOKESPECIAL,
-                factory.createMethod(
-                    arrayListType,
-                    factory.createProto(factory.voidType),
-                    factory.constructorMethodName),
-                false));
-      }
-      instructions.add(new CfStore(ValueType.OBJECT, 1));
-
-      // Iterator<E> t2 = receiver.iterator();
-      instructions.add(new CfLoad(ValueType.OBJECT, 0));
-      instructions.add(
-          new CfInvoke(
-              Opcodes.INVOKEINTERFACE,
-              factory.createMethod(
-                  factory.collectionType, factory.createProto(factory.iteratorType), "iterator"),
-              true));
-      instructions.add(new CfStore(ValueType.OBJECT, 2));
-
-      // while(t2.hasNext())
-      CfLabel returnLabel = new CfLabel();
-      CfLabel loopLabel = new CfLabel();
-      instructions.add(loopLabel);
-      instructions.add(frame);
-      instructions.add(new CfLoad(ValueType.fromDexType(factory.iteratorType), 2));
-      instructions.add(
-          new CfInvoke(
-              Opcodes.INVOKEINTERFACE,
-              factory.createMethod(
-                  factory.iteratorType, factory.createProto(factory.booleanType), "hasNext"),
-              true));
-      instructions.add(new CfConstNumber(0, ValueType.INT));
-      instructions.add(new CfIfCmp(If.Type.EQ, ValueType.INT, returnLabel));
-
-      // {t1.add(convert(t2.next());}
-      instructions.add(new CfLoad(ValueType.fromDexType(collectionType), 1));
-      instructions.add(new CfLoad(ValueType.fromDexType(factory.iteratorType), 2));
-      instructions.add(
-          new CfInvoke(
-              Opcodes.INVOKEINTERFACE,
-              factory.createMethod(
-                  factory.iteratorType,
-                  factory.createProto(conversion.getArgumentType(0, true)),
-                  "next"),
-              true));
-      instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, conversion, false));
-      instructions.add(
-          new CfInvoke(
-              Opcodes.INVOKEINTERFACE,
-              factory.createMethod(
-                  factory.collectionType,
-                  factory.createProto(factory.booleanType, factory.objectType),
-                  "add"),
-              true));
-      instructions.add(new CfGoto(loopLabel));
-
-      // return t1;
-      instructions.add(returnLabel);
-      instructions.add(frame.clone());
-      instructions.add(new CfLoad(ValueType.fromDexType(collectionType), 1));
-      instructions.add(new CfReturn(ValueType.fromDexType(collectionType)));
-
-      return standardCfCodeFromInstructions(instructions);
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index cdb9762..ab83fdb 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -364,13 +364,13 @@
     if (!method.hasClassFileVersion()) {
       // In this case bridges have been introduced for the Cf back-end,
       // which do not have class file version.
-      assert options.isDesugaredLibraryCompilation() || options.cfToCfDesugar
+      assert options.isDesugaredLibraryCompilation() || options.isDesugaring()
           : "Expected class file version for " + method.getReference().toSourceString();
       assert MIN_VERSION_FOR_COMPILER_GENERATED_CODE.isLessThan(
           options.classFileVersionAfterDesugaring(InternalOptions.SUPPORTED_CF_VERSION));
       // Any desugaring rewrites which cannot meet the default class file version after
       // desugaring must upgrade the class file version during desugaring.
-      return options.cfToCfDesugar
+      return options.isDesugaring()
           ? options.classFileVersionAfterDesugaring(InternalOptions.SUPPORTED_CF_VERSION)
           : MIN_VERSION_FOR_COMPILER_GENERATED_CODE;
     }
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 9f01beb..9591d26 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -65,7 +65,6 @@
 
     private ImmutableMap<String, ClassNamingForNameMapper> buildClassNameMappings() {
       ImmutableMap.Builder<String, ClassNamingForNameMapper> builder = ImmutableMap.builder();
-      builder.orderEntriesByValue(Comparator.comparing(x -> x.originalName));
       mapping.forEach(
           (renamedName, valueBuilder) -> builder.put(renamedName, valueBuilder.build()));
       return builder.build();
@@ -158,6 +157,7 @@
 
   public static ClassNameMapper mapperFromLineReaderWithFiltering(
       LineReader reader,
+      MapVersion mapVersion,
       DiagnosticsHandler diagnosticsHandler,
       boolean allowEmptyMappedRanges,
       boolean allowExperimentalMapping)
@@ -167,7 +167,8 @@
             reader,
             diagnosticsHandler != null ? diagnosticsHandler : new Reporter(),
             allowEmptyMappedRanges,
-            allowExperimentalMapping)) {
+            allowExperimentalMapping,
+            mapVersion)) {
       ClassNameMapper.Builder builder = ClassNameMapper.builder();
       proguardReader.parse(builder);
       return builder.build();
@@ -177,12 +178,12 @@
   private final ImmutableMap<String, ClassNamingForNameMapper> classNameMappings;
   private BiMapContainer<String, String> nameMapping;
   private final Map<Signature, Signature> signatureMap = new HashMap<>();
-  private final Set<MapVersionMappingInformation> mapVersions;
+  private final LinkedHashSet<MapVersionMappingInformation> mapVersions;
   private final Map<String, String> originalSourceFiles;
 
   private ClassNameMapper(
       ImmutableMap<String, ClassNamingForNameMapper> classNameMappings,
-      Set<MapVersionMappingInformation> mapVersions,
+      LinkedHashSet<MapVersionMappingInformation> mapVersions,
       Map<String, String> originalSourceFiles) {
     this.classNameMappings = classNameMappings;
     this.mapVersions = mapVersions;
@@ -242,6 +243,40 @@
     return originalSourceFiles.get(typeName);
   }
 
+  public ClassNameMapper combine(ClassNameMapper other) {
+    if (other == null || other.isEmpty()) {
+      return this;
+    }
+    if (this.isEmpty()) {
+      return other;
+    }
+    ImmutableMap.Builder<String, ClassNamingForNameMapper> builder = ImmutableMap.builder();
+    Map<String, ClassNamingForNameMapper> otherClassMappings = other.getClassNameMappings();
+    for (Entry<String, ClassNamingForNameMapper> mappingEntry : classNameMappings.entrySet()) {
+      ClassNamingForNameMapper otherMapping = otherClassMappings.get(mappingEntry.getKey());
+      if (otherMapping == null) {
+        builder.put(mappingEntry);
+      } else {
+        builder.put(mappingEntry.getKey(), mappingEntry.getValue().combine(otherMapping));
+      }
+    }
+    otherClassMappings.forEach(
+        (otherMappingClass, otherMapping) -> {
+          // Collisions are handled above so only take non-existing mappings.
+          if (!classNameMappings.containsKey(otherMappingClass)) {
+            builder.put(otherMappingClass, otherMapping);
+          }
+        });
+    LinkedHashSet<MapVersionMappingInformation> newMapVersions =
+        new LinkedHashSet<>(getMapVersions());
+    newMapVersions.addAll(other.getMapVersions());
+    Map<String, String> newSourcesFiles = new HashMap<>(originalSourceFiles);
+    // This will overwrite existing source files but the chance of that happening should be very
+    // slim.
+    newSourcesFiles.putAll(other.originalSourceFiles);
+    return new ClassNameMapper(builder.build(), newMapVersions, newSourcesFiles);
+  }
+
   @Override
   public boolean hasMapping(DexType type) {
     String decoded = descriptorToJavaType(type.descriptor.toString());
@@ -382,4 +417,8 @@
   public Set<MapVersionMappingInformation> getMapVersions() {
     return mapVersions;
   }
+
+  public MapVersionMappingInformation getFirstMappingInformation() {
+    return mapVersions.isEmpty() ? null : mapVersions.iterator().next();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index c1e5de7..352c555 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -265,6 +265,40 @@
     return mappedRangesByRenamedName.get(renamedName);
   }
 
+  public boolean isEmpty() {
+    return methodMembers.isEmpty() && fieldMembers.isEmpty();
+  }
+
+  public ClassNamingForNameMapper combine(ClassNamingForNameMapper otherMapping) {
+    if (!originalName.equals(otherMapping.originalName)) {
+      throw new RuntimeException(
+          "Cannot combine mapping for "
+              + renamedName
+              + " because it maps back to both "
+              + originalName
+              + " and "
+              + otherMapping.originalName
+              + ".");
+    }
+    if (!renamedName.equals(otherMapping.renamedName)) {
+      throw new RuntimeException(
+          "Cannot combine mapping for "
+              + originalName
+              + " because it maps forward to both "
+              + originalName
+              + " and "
+              + otherMapping.originalName
+              + ".");
+    }
+    if (this.isEmpty()) {
+      return otherMapping;
+    } else if (otherMapping.isEmpty()) {
+      return this;
+    } else {
+      throw new RuntimeException("R8 Retrace do not support merging of partial class mappings.");
+    }
+  }
+
   @Override
   public MemberNaming lookup(Signature renamedSignature) {
     if (renamedSignature.kind() == SignatureKind.METHOD) {
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 f28d4bc..3eb0ec1 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -77,10 +77,25 @@
       DiagnosticsHandler diagnosticsHandler,
       boolean allowEmptyMappedRanges,
       boolean allowExperimentalMapping) {
+    this(
+        reader,
+        diagnosticsHandler,
+        allowEmptyMappedRanges,
+        allowExperimentalMapping,
+        MapVersion.MAP_VERSION_NONE);
+  }
+
+  ProguardMapReader(
+      LineReader reader,
+      DiagnosticsHandler diagnosticsHandler,
+      boolean allowEmptyMappedRanges,
+      boolean allowExperimentalMapping,
+      MapVersion mapVersion) {
     this.reader = reader;
     this.diagnosticsHandler = diagnosticsHandler;
     this.allowEmptyMappedRanges = allowEmptyMappedRanges;
     this.allowExperimentalMapping = allowExperimentalMapping;
+    this.version = mapVersion;
     assert reader != null;
     assert diagnosticsHandler != null;
   }
@@ -89,7 +104,7 @@
   private int lineNo = 0;
   private int lineOffset = 0;
   private String line;
-  private MapVersion version = MapVersion.MAP_VERSION_NONE;
+  private MapVersion version;
 
   private int peekCodePoint() {
     return lineOffset < line.length() ? line.codePointAt(lineOffset) : '\n';
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 2efdb3c..2ac83b2 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BiForEachable;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.TriConsumer;
@@ -35,6 +36,7 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
@@ -354,15 +356,32 @@
         (bridgeHolder, targets) -> {
           // Sorting the list of bridges within a class maintains a deterministic order of entries
           // in the method collection.
-          targets.sort((p1, p2) -> p1.getFirst().compareTo(p2.getFirst()));
+          targets.sort(Comparator.comparing(Pair::getFirst));
           for (Pair<DexMethod, DexClassAndMethod> pair : targets) {
             DexMethod method = pair.getFirst();
             DexClassAndMethod target = pair.getSecond();
             DexMethod bridgeMethod =
                 method.withHolder(bridgeHolder.getType(), appView.dexItemFactory());
             if (bridgeHolder.getMethodCollection().getMethod(bridgeMethod) == null) {
+              DexEncodedMethod targetDefinition = target.getDefinition();
               DexEncodedMethod bridgeMethodDefinition =
-                  target.getDefinition().toForwardingMethod(bridgeHolder, appView);
+                  targetDefinition.toForwardingMethod(
+                      bridgeHolder,
+                      appView,
+                      builder -> {
+                        if (!targetDefinition.isAbstract()
+                            && targetDefinition.getApiLevelForCode().isNotSetApiLevel()) {
+                          assert target.isLibraryMethod();
+                          builder.setApiLevelForCode(
+                              appView
+                                  .apiLevelCompute()
+                                  .computeApiLevelForLibraryReference(
+                                      targetDefinition.getReference(),
+                                      appView.computedMinApiLevel()));
+                        }
+                        builder.setIsLibraryMethodOverrideIf(
+                            target.isLibraryMethod(), OptionalBool.TRUE);
+                      });
               bridgeHolder.addMethod(bridgeMethodDefinition);
             }
             assert resolver.apply(method).getResolvedMethod().getReference() == bridgeMethod;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
index bd3fee8..df0f324 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -40,6 +40,7 @@
 import com.android.tools.r8.shaking.KeepFieldInfo;
 import com.android.tools.r8.shaking.KeepMethodInfo;
 import com.android.tools.r8.utils.AccessUtils;
+import com.android.tools.r8.utils.AndroidApiLevelUtils;
 import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.InternalOptions;
@@ -642,8 +643,9 @@
 
     private DexType getNewFieldType(ProgramField field) {
       DynamicType dynamicType = field.getOptimizationInfo().getDynamicType();
+      DexType staticType = field.getType();
       if (dynamicType.isUnknown()) {
-        return field.getType();
+        return staticType;
       }
 
       KeepFieldInfo keepInfo = appView.getKeepInfo(field);
@@ -652,17 +654,17 @@
       assert !keepInfo.isPinned(options);
 
       if (!keepInfo.isFieldTypeStrengtheningAllowed(options)) {
-        return field.getType();
+        return staticType;
       }
 
       if (dynamicType.isNullType()) {
         // Don't optimize always null fields; these will be optimized anyway.
-        return field.getType();
+        return staticType;
       }
 
       if (dynamicType.isNotNullType()) {
         // We don't have a more specific type.
-        return field.getType();
+        return staticType;
       }
 
       DynamicTypeWithUpperBound dynamicTypeWithUpperBound =
@@ -670,15 +672,15 @@
       TypeElement dynamicUpperBoundType = dynamicTypeWithUpperBound.getDynamicUpperBoundType();
       assert dynamicUpperBoundType.isReferenceType();
 
-      ClassTypeElement staticFieldType = field.getType().toTypeElement(appView).asClassType();
+      ClassTypeElement staticFieldType = staticType.toTypeElement(appView).asClassType();
       if (dynamicUpperBoundType.equalUpToNullability(staticFieldType)) {
         // We don't have more precise type information.
-        return field.getType();
+        return staticType;
       }
 
       if (!dynamicUpperBoundType.strictlyLessThan(staticFieldType, appView)) {
         assert options.testing.allowTypeErrors;
-        return field.getType();
+        return staticType;
       }
 
       DexType newStaticFieldType;
@@ -689,7 +691,7 @@
             newStaticFieldType =
                 dynamicUpperBoundClassType.getInterfaces().getSingleKnownInterface();
           } else {
-            return field.getType();
+            return staticType;
           }
         } else {
           newStaticFieldType = dynamicUpperBoundClassType.getClassType();
@@ -698,8 +700,17 @@
         newStaticFieldType = dynamicUpperBoundType.asArrayType().toDexType(dexItemFactory);
       }
 
-      if (!AccessUtils.isAccessibleInSameContextsAs(newStaticFieldType, field.getType(), appView)) {
-        return field.getType();
+      if (newStaticFieldType == staticType) {
+        return staticType;
+      }
+
+      if (!AccessUtils.isAccessibleInSameContextsAs(newStaticFieldType, staticType, appView)) {
+        return staticType;
+      }
+
+      if (!AndroidApiLevelUtils.isApiSafeForTypeStrengthening(
+          newStaticFieldType, staticType, appView)) {
+        return staticType;
       }
 
       return newStaticFieldType;
@@ -965,10 +976,13 @@
       if (!appView.appInfo().isSubtype(newReturnType, staticType)) {
         return null;
       }
-      return AccessUtils.isAccessibleInSameContextsAs(
-              newReturnType, method.getReturnType(), appView)
-          ? newReturnType
-          : null;
+      if (!AccessUtils.isAccessibleInSameContextsAs(newReturnType, staticType, appView)) {
+        return null;
+      }
+      if (!AndroidApiLevelUtils.isApiSafeForTypeStrengthening(newReturnType, staticType, appView)) {
+        return null;
+      }
+      return newReturnType;
     }
 
     private SingleValue getReturnValue(ProgramMethod method) {
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
index b381e18..3684506 100644
--- 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
@@ -30,18 +30,18 @@
   }
 
   @Override
-  public CfFrameState check(AppView<?> appView, CfFrame frame) {
-    return new ConcreteCfFrameState().check(appView, frame);
+  public CfFrameState check(CfAnalysisConfig config, CfFrame frame) {
+    return this;
   }
 
   @Override
-  public CfFrameState checkLocals(AppView<?> appView, CfFrame frame) {
-    return new ConcreteCfFrameState().checkLocals(appView, frame);
+  public CfFrameState checkLocals(CfAnalysisConfig config, CfFrame frame) {
+    return this;
   }
 
   @Override
-  public CfFrameState checkStack(AppView<?> appView, CfFrame frame) {
-    return new ConcreteCfFrameState().checkStack(appView, frame);
+  public CfFrameState checkStack(CfAnalysisConfig config, CfFrame frame) {
+    return this;
   }
 
   @Override
@@ -52,66 +52,73 @@
   @Override
   public CfFrameState markInitialized(
       UninitializedFrameType uninitializedType, DexType initializedType) {
-    // Initializing an uninitialized type is a no-op when the frame is empty.
     return this;
   }
 
   @Override
-  public ErroneousCfFrameState pop() {
-    return error("Unexpected pop from empty stack");
+  public CfFrameState pop() {
+    return this;
   }
 
   @Override
-  public ErroneousCfFrameState pop(BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn) {
-    return pop();
+  public CfFrameState pop(BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn) {
+    return this;
   }
 
   @Override
-  public ErroneousCfFrameState popAndInitialize(
+  public CfFrameState popAndInitialize(
       AppView<?> appView, DexMethod constructor, CfAnalysisConfig config) {
-    return pop();
+    return this;
   }
 
   @Override
-  public ErroneousCfFrameState popArray(AppView<?> appView) {
-    return pop();
+  public CfFrameState popArray(AppView<?> appView) {
+    return this;
   }
 
   @Override
-  public ErroneousCfFrameState popInitialized(
+  public CfFrameState popInitialized(
       AppView<?> appView,
+      CfAnalysisConfig config,
       DexType expectedType,
       BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn) {
-    return pop();
+    return this;
   }
 
   @Override
-  public CfFrameState popInitialized(AppView<?> appView, DexType... expectedTypes) {
-    return expectedTypes.length == 0 ? this : pop();
+  public CfFrameState popInitialized(
+      AppView<?> appView, CfAnalysisConfig config, DexType... expectedTypes) {
+    return this;
   }
 
   @Override
   public CfFrameState push(CfAnalysisConfig config, DexType type) {
-    return new ConcreteCfFrameState().push(config, type);
+    return this;
   }
 
   @Override
   public CfFrameState push(CfAnalysisConfig config, PreciseFrameType frameType) {
-    return new ConcreteCfFrameState().push(config, frameType);
+    return this;
   }
 
   @Override
-  public ErroneousCfFrameState readLocal(
+  public CfFrameState pushException(CfAnalysisConfig config, DexType guard) {
+    return this;
+  }
+
+  @Override
+  public CfFrameState readLocal(
       AppView<?> appView,
+      CfAnalysisConfig config,
       int localIndex,
       ValueType expectedType,
       BiFunction<CfFrameState, FrameType, CfFrameState> fn) {
-    return error("Unexpected local read from empty frame");
+    return this;
   }
 
   @Override
   public CfFrameState storeLocal(int localIndex, FrameType frameType, CfAnalysisConfig config) {
-    return new ConcreteCfFrameState().storeLocal(localIndex, frameType, config);
+    return this;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfAnalysisConfig.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfAnalysisConfig.java
index bd8a7c9..0a2bec0 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfAnalysisConfig.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfAnalysisConfig.java
@@ -4,11 +4,14 @@
 
 package com.android.tools.r8.optimize.interfaces.analysis;
 
+import com.android.tools.r8.cf.code.CfAssignability;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 
 public interface CfAnalysisConfig {
 
+  CfAssignability getAssignability();
+
   DexMethod getCurrentContext();
 
   int getMaxLocals();
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
index dfa38f5..b136b9b 100644
--- 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
@@ -12,6 +12,7 @@
 import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.cf.code.frame.PreciseFrameType;
 import com.android.tools.r8.cf.code.frame.UninitializedFrameType;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -72,11 +73,13 @@
   }
 
   @Override
-  public boolean isGreaterThanOrEquals(CfFrameState state) {
+  public boolean isGreaterThanOrEquals(AppView<?> appView, CfFrameState state) {
     if (this == state) {
       return true;
     }
-    CfFrameState leastUpperBound = join(state, UnaryOperator.identity());
+    assert appView.hasClassHierarchy();
+    CfFrameState leastUpperBound =
+        join(appView.withClassHierarchy(), state, UnaryOperator.identity());
     return equals(leastUpperBound);
   }
 
@@ -100,11 +103,11 @@
     return null;
   }
 
-  public abstract CfFrameState check(AppView<?> appView, CfFrame frame);
+  public abstract CfFrameState check(CfAnalysisConfig config, CfFrame frame);
 
-  public abstract CfFrameState checkLocals(AppView<?> appView, CfFrame frame);
+  public abstract CfFrameState checkLocals(CfAnalysisConfig config, CfFrame frame);
 
-  public abstract CfFrameState checkStack(AppView<?> appView, CfFrame frame);
+  public abstract CfFrameState checkStack(CfAnalysisConfig config, CfFrame frame);
 
   public abstract CfFrameState clear();
 
@@ -120,38 +123,46 @@
 
   public abstract CfFrameState popArray(AppView<?> appView);
 
-  public final CfFrameState popInitialized(AppView<?> appView, DexType expectedType) {
-    return popInitialized(appView, expectedType, FunctionUtils::getFirst);
+  public final CfFrameState popInitialized(
+      AppView<?> appView, CfAnalysisConfig config, DexType expectedType) {
+    return popInitialized(appView, config, expectedType, FunctionUtils::getFirst);
   }
 
   public abstract CfFrameState popInitialized(
       AppView<?> appView,
+      CfAnalysisConfig config,
       DexType expectedType,
       BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn);
 
-  public abstract CfFrameState popInitialized(AppView<?> appView, DexType... expectedTypes);
+  public abstract CfFrameState popInitialized(
+      AppView<?> appView, CfAnalysisConfig config, DexType... expectedTypes);
 
-  public final CfFrameState popInitialized(AppView<?> appView, MemberType memberType) {
+  public final CfFrameState popInitialized(
+      AppView<?> appView, CfAnalysisConfig config, MemberType memberType) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     return popInitialized(
         appView,
+        config,
         FrameType.fromPreciseMemberType(memberType, dexItemFactory)
             .getInitializedType(dexItemFactory));
   }
 
-  public final CfFrameState popInitialized(AppView<?> appView, NumericType expectedType) {
-    return popInitialized(appView, expectedType.toDexType(appView.dexItemFactory()));
+  public final CfFrameState popInitialized(
+      AppView<?> appView, CfAnalysisConfig config, NumericType expectedType) {
+    return popInitialized(appView, config, expectedType.toDexType(appView.dexItemFactory()));
   }
 
-  public final CfFrameState popInitialized(AppView<?> appView, ValueType valueType) {
-    return popInitialized(appView, valueType, FunctionUtils::getFirst);
+  public final CfFrameState popInitialized(
+      AppView<?> appView, CfAnalysisConfig config, ValueType valueType) {
+    return popInitialized(appView, config, valueType, FunctionUtils::getFirst);
   }
 
   public final CfFrameState popInitialized(
       AppView<?> appView,
+      CfAnalysisConfig config,
       ValueType valueType,
       BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn) {
-    return popInitialized(appView, valueType.toDexType(appView.dexItemFactory()), fn);
+    return popInitialized(appView, config, valueType.toDexType(appView.dexItemFactory()), fn);
   }
 
   public final CfFrameState popObject(BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn) {
@@ -162,17 +173,16 @@
 
   @SuppressWarnings("InconsistentOverloads")
   public final CfFrameState popObject(
-      AppView<?> appView,
       DexType expectedType,
       CfAnalysisConfig config,
       BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn) {
+    CfAssignability assignability = config.getAssignability();
     return pop(
         (state, head) ->
             head.isObject()
-                    && CfAssignability.isAssignable(
+                    && assignability.isAssignable(
                         head.getObjectType(config.getCurrentContext().getHolderType()),
-                        expectedType,
-                        appView)
+                        expectedType)
                 ? fn.apply(state, head)
                 : errorUnexpectedStack(head, expectedType));
   }
@@ -288,8 +298,11 @@
     return push(config, valueType.toDexType(appView.dexItemFactory()));
   }
 
+  public abstract CfFrameState pushException(CfAnalysisConfig config, DexType guard);
+
   public abstract CfFrameState readLocal(
       AppView<?> appView,
+      CfAnalysisConfig config,
       int localIndex,
       ValueType expectedType,
       BiFunction<CfFrameState, FrameType, CfFrameState> consumer);
@@ -313,13 +326,18 @@
   }
 
   @Override
-  public final CfFrameState join(CfFrameState state) {
+  public final CfFrameState join(AppView<?> appView, CfFrameState state) {
+    assert appView.hasClassHierarchy();
     return join(
-        state, frameType -> frameType.isSingle() ? FrameType.oneWord() : FrameType.twoWord());
+        appView.withClassHierarchy(),
+        state,
+        frameType -> frameType.isSingle() ? FrameType.oneWord() : FrameType.twoWord());
   }
 
   public final CfFrameState join(
-      CfFrameState state, UnaryOperator<FrameType> joinWithMissingLocal) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      CfFrameState state,
+      UnaryOperator<FrameType> joinWithMissingLocal) {
     if (state.isBottom() || isError()) {
       return this;
     }
@@ -328,7 +346,7 @@
     }
     assert isConcrete();
     assert state.isConcrete();
-    return asConcrete().join(state.asConcrete(), joinWithMissingLocal);
+    return asConcrete().join(appView, state.asConcrete(), joinWithMissingLocal);
   }
 
   @Override
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
index 33e4cf2..ae2e594 100644
--- 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
@@ -4,10 +4,14 @@
 
 package com.android.tools.r8.optimize.interfaces.analysis;
 
+import com.android.tools.r8.cf.code.CfAssignability;
 import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfSubtypingAssignability;
+import com.android.tools.r8.cf.code.frame.FrameType;
 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.DexClassAndMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -20,51 +24,124 @@
 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;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic;
+import com.android.tools.r8.utils.collections.ProgramMethodMap;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 
 public class CfOpenClosedInterfacesAnalysis {
 
   private final AppView<AppInfoWithLiveness> appView;
+  private final CfAssignability assignability;
+  private final InternalOptions options;
+
+  private final ProgramMethodMap<UnverifiableCfCodeDiagnostic> unverifiableCodeDiagnostics =
+      ProgramMethodMap.createConcurrent();
 
   public CfOpenClosedInterfacesAnalysis(AppView<AppInfoWithLiveness> appView) {
+    InternalOptions options = appView.options();
     this.appView = appView;
+    this.assignability = new CfSubtypingAssignability(appView);
+    this.options = options;
   }
 
-  public void run() {
-    // TODO(b/214496607): Parallelize the analysis.
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      clazz.forEachProgramMethodMatching(DexEncodedMethod::hasCode, this::processMethod);
-    }
+  public boolean run(ExecutorService executorService) throws ExecutionException {
+    processClasses(executorService);
+    reportUnverifiableCodeDiagnostics();
+    return true;
+  }
+
+  private void processClasses(ExecutorService executorService) throws ExecutionException {
+    ThreadUtils.processItems(appView.appInfo().classes(), this::processClass, executorService);
+  }
+
+  private void processClass(DexProgramClass clazz) {
+    clazz.forEachProgramMethodMatching(DexEncodedMethod::hasCode, this::processMethod);
   }
 
   private void processMethod(ProgramMethod method) {
     Code code = method.getDefinition().getCode();
     if (!code.isCfCode()) {
-      assert code.isDefaultInstanceInitializerCode() || code.isThrowNullCode();
+      assert code.isDefaultInstanceInitializerCode() || code.isDexCode() || code.isThrowNullCode();
       return;
     }
 
     CfCode cfCode = code.asCfCode();
-    CfControlFlowGraph cfg = CfControlFlowGraph.create(cfCode);
+    CfControlFlowGraph cfg = CfControlFlowGraph.create(cfCode, options);
+    TransferFunction transfer = new TransferFunction(method);
     CfIntraproceduralDataflowAnalysis<CfFrameState> analysis =
-        new CfIntraproceduralDataflowAnalysis<>(
-            CfFrameState.bottom(), cfg, new TransferFunction(method));
+        new CfIntraproceduralDataflowAnalysis<>(appView, CfFrameState.bottom(), cfg, transfer);
     DataflowAnalysisResult result = analysis.run(cfg.getEntryBlock());
-    // TODO(b/214496607): Determine open interfaces.
+    assert result.isSuccessfulAnalysisResult();
+    for (CfBlock block : cfg.getBlocks()) {
+      if (analysis.isIntermediateBlock(block)) {
+        continue;
+      }
+      CfFrameState state = analysis.computeBlockEntryState(block);
+      do {
+        for (int instructionIndex = block.getFirstInstructionIndex();
+            instructionIndex <= block.getLastInstructionIndex();
+            instructionIndex++) {
+          // TODO(b/214496607): Determine open interfaces.
+          CfInstruction instruction = cfCode.getInstruction(instructionIndex);
+          state = transfer.apply(instruction, state).asAbstractState();
+          if (state.isError()) {
+            if (options.getCfCodeAnalysisOptions().isUnverifiableCodeReportingEnabled()) {
+              unverifiableCodeDiagnostics.put(
+                  method,
+                  new UnverifiableCfCodeDiagnostic(
+                      method.getMethodReference(),
+                      instructionIndex,
+                      state.asError().getMessage(),
+                      method.getOrigin()));
+            }
+            return;
+          }
+        }
+        if (analysis.isBlockWithIntermediateSuccessorBlock(block)) {
+          block = cfg.getUniqueSuccessor(block);
+        } else {
+          block = null;
+        }
+      } while (block != null);
+    }
+  }
+
+  private void reportUnverifiableCodeDiagnostics() {
+    Reporter reporter = appView.reporter();
+    List<ProgramMethod> methods = new ArrayList<>(unverifiableCodeDiagnostics.size());
+    unverifiableCodeDiagnostics.forEach((method, diagnostic) -> methods.add(method));
+    methods.sort(Comparator.comparing(DexClassAndMember::getReference));
+    methods.forEach(method -> reporter.warning(unverifiableCodeDiagnostics.get(method)));
   }
 
   private class TransferFunction
       implements AbstractTransferFunction<CfBlock, CfInstruction, CfFrameState> {
 
+    private final CfCode code;
     private final CfAnalysisConfig config;
+    private final ProgramMethod context;
 
     TransferFunction(ProgramMethod context) {
       CfCode code = context.getDefinition().getCode().asCfCode();
       int maxLocals = code.getMaxLocals();
       int maxStack = code.getMaxStack();
+      this.code = code;
       this.config =
           new CfAnalysisConfig() {
 
             @Override
+            public CfAssignability getAssignability() {
+              return assignability;
+            }
+
+            @Override
             public DexMethod getCurrentContext() {
               return context.getReference();
             }
@@ -84,12 +161,35 @@
               return type == context.getHolder().getSuperType();
             }
           };
+      this.context = context;
     }
 
     @Override
     public TransferFunctionResult<CfFrameState> apply(
         CfInstruction instruction, CfFrameState state) {
-      return instruction.evaluate(state, appView, config, appView.dexItemFactory());
+      return instruction.evaluate(state, appView, config);
+    }
+
+    @Override
+    public CfFrameState computeInitialState(CfBlock entryBlock, CfFrameState bottom) {
+      CfFrameState initialState = new ConcreteCfFrameState();
+      int localIndex = 0;
+      if (context.getDefinition().isInstance()) {
+        if (context.getDefinition().isInstanceInitializer()) {
+          initialState = initialState.storeLocal(localIndex, FrameType.uninitializedThis(), config);
+        } else {
+          initialState =
+              initialState.storeLocal(
+                  localIndex, FrameType.initialized(context.getHolderType()), config);
+        }
+        localIndex++;
+      }
+      for (DexType parameter : context.getParameters()) {
+        initialState =
+            initialState.storeLocal(localIndex, FrameType.initialized(parameter), config);
+        localIndex += parameter.getRequiredRegisters();
+      }
+      return initialState;
     }
 
     @Override
@@ -97,5 +197,15 @@
         CfBlock block, CfBlock predecessor, CfFrameState predecessorExitState) {
       return predecessorExitState;
     }
+
+    @Override
+    public CfFrameState computeExceptionalBlockEntryState(
+        CfBlock block,
+        DexType guard,
+        CfBlock throwBlock,
+        CfInstruction throwInstruction,
+        CfFrameState throwState) {
+      return throwState.pushException(config, guard);
+    }
   }
 }
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
index f05ab67..531db8b 100644
--- 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
@@ -10,11 +10,14 @@
 import com.android.tools.r8.cf.code.CfAssignability;
 import com.android.tools.r8.cf.code.CfAssignability.AssignabilityResult;
 import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrame.Builder;
+import com.android.tools.r8.cf.code.CfFrameUtils;
 import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.cf.code.frame.PreciseFrameType;
 import com.android.tools.r8.cf.code.frame.SingleFrameType;
 import com.android.tools.r8.cf.code.frame.UninitializedFrameType;
 import com.android.tools.r8.cf.code.frame.WideFrameType;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -40,12 +43,13 @@
   private final ArrayDeque<PreciseFrameType> stack;
   private int stackHeight;
 
-  ConcreteCfFrameState() {
+  public ConcreteCfFrameState() {
     this(new Int2ObjectAVLTreeMap<>(), new ArrayDeque<>(), 0);
   }
 
   public ConcreteCfFrameState(
       Int2ObjectAVLTreeMap<FrameType> locals, ArrayDeque<PreciseFrameType> stack, int stackHeight) {
+    assert CfFrameUtils.verifyLocals(locals);
     this.locals = locals;
     this.stack = stack;
     this.stackHeight = stackHeight;
@@ -67,10 +71,10 @@
   }
 
   @Override
-  public CfFrameState check(AppView<?> appView, CfFrame frame) {
+  public CfFrameState check(CfAnalysisConfig config, CfFrame frame) {
     CfFrame currentFrame = CfFrame.builder().setLocals(locals).setStack(stack).build();
     AssignabilityResult assignabilityResult =
-        CfAssignability.isFrameAssignable(currentFrame, frame, appView);
+        config.getAssignability().isFrameAssignable(currentFrame, frame);
     if (assignabilityResult.isFailed()) {
       return error(assignabilityResult.asFailed().getMessage());
     }
@@ -80,9 +84,9 @@
   }
 
   @Override
-  public CfFrameState checkLocals(AppView<?> appView, CfFrame frame) {
+  public CfFrameState checkLocals(CfAnalysisConfig config, CfFrame frame) {
     AssignabilityResult assignabilityResult =
-        CfAssignability.isLocalsAssignable(locals, frame.getLocals(), appView);
+        config.getAssignability().isLocalsAssignable(locals, frame.getLocals());
     if (assignabilityResult.isFailed()) {
       return error(assignabilityResult.asFailed().getMessage());
     }
@@ -90,9 +94,9 @@
   }
 
   @Override
-  public CfFrameState checkStack(AppView<?> appView, CfFrame frame) {
+  public CfFrameState checkStack(CfAnalysisConfig config, CfFrame frame) {
     AssignabilityResult assignabilityResult =
-        CfAssignability.isStackAssignable(stack, frame.getStack(), appView);
+        config.getAssignability().isStackAssignable(stack, frame.getStack());
     if (assignabilityResult.isFailed()) {
       return error(assignabilityResult.asFailed().getMessage());
     }
@@ -139,8 +143,7 @@
   @Override
   public CfFrameState pop(BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn) {
     if (stack.isEmpty()) {
-      // Return the same error as when popping from the bottom state.
-      return bottom().pop();
+      return error("Unexpected pop from empty stack");
     }
     PreciseFrameType frameType = stack.removeLast();
     stackHeight -= frameType.getWidth();
@@ -209,13 +212,15 @@
   @Override
   public CfFrameState popInitialized(
       AppView<?> appView,
+      CfAnalysisConfig config,
       DexType expectedType,
       BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn) {
+    CfAssignability assignability = config.getAssignability();
     return pop(
         (state, frameType) -> {
           if (frameType.isInitialized()) {
             DexType initializedType = frameType.getInitializedType(appView.dexItemFactory());
-            if (CfAssignability.isAssignable(initializedType, expectedType, appView)) {
+            if (assignability.isAssignable(initializedType, expectedType)) {
               return fn.apply(state, frameType);
             }
           }
@@ -224,10 +229,11 @@
   }
 
   @Override
-  public CfFrameState popInitialized(AppView<?> appView, DexType... expectedTypes) {
+  public CfFrameState popInitialized(
+      AppView<?> appView, CfAnalysisConfig config, DexType... expectedTypes) {
     CfFrameState state = this;
     for (int i = expectedTypes.length - 1; i >= 0; i--) {
-      state = state.popInitialized(appView, expectedTypes[i]);
+      state = state.popInitialized(appView, config, expectedTypes[i]);
     }
     return state;
   }
@@ -259,8 +265,18 @@
   }
 
   @Override
+  public CfFrameState pushException(CfAnalysisConfig config, DexType guard) {
+    Int2ObjectAVLTreeMap<FrameType> newLocals = new Int2ObjectAVLTreeMap<>(locals);
+    ArrayDeque<PreciseFrameType> newStack = new ArrayDeque<>();
+    int newStackHeight = 0;
+    return new ConcreteCfFrameState(newLocals, newStack, newStackHeight)
+        .push(config, FrameType.initialized(guard));
+  }
+
+  @Override
   public CfFrameState readLocal(
       AppView<?> appView,
+      CfAnalysisConfig config,
       int localIndex,
       ValueType expectedType,
       BiFunction<CfFrameState, FrameType, CfFrameState> fn) {
@@ -269,8 +285,9 @@
       return error("Unexpected read of missing local at index " + localIndex);
     }
     if (frameType.isInitialized()) {
-      if (CfAssignability.isAssignable(
-          frameType.getInitializedType(appView.dexItemFactory()), expectedType, appView)) {
+      CfAssignability assignability = config.getAssignability();
+      DexType actualType = frameType.getInitializedType(appView.dexItemFactory());
+      if (assignability.isAssignable(actualType, expectedType)) {
         return fn.apply(this, frameType);
       }
     } else if (frameType.isUninitialized() && expectedType.isObject()) {
@@ -285,10 +302,7 @@
     if (maxLocalIndex >= config.getMaxLocals()) {
       return storeLocalError(localIndex, frameType, config);
     }
-    locals.put(localIndex, frameType);
-    if (frameType.isWide()) {
-      locals.put(localIndex + 1, frameType);
-    }
+    CfFrameUtils.storeLocal(localIndex, frameType, locals);
     return this;
   }
 
@@ -308,10 +322,12 @@
   }
 
   public CfFrameState join(
-      ConcreteCfFrameState state, UnaryOperator<FrameType> joinWithMissingLocal) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      ConcreteCfFrameState state,
+      UnaryOperator<FrameType> joinWithMissingLocal) {
     CfFrame.Builder builder = CfFrame.builder();
-    joinLocals(state.locals, builder, joinWithMissingLocal);
-    ErroneousCfFrameState error = joinStack(state.stack, builder);
+    joinLocals(appView, state.locals, builder, joinWithMissingLocal);
+    ErroneousCfFrameState error = joinStack(appView, state.stack, builder);
     if (error != null) {
       return error;
     }
@@ -320,8 +336,9 @@
   }
 
   private void joinLocals(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       Int2ObjectSortedMap<FrameType> locals,
-      CfFrame.Builder builder,
+      Builder builder,
       UnaryOperator<FrameType> joinWithMissingLocal) {
     ObjectBidirectionalIterator<Entry<FrameType>> iterator =
         this.locals.int2ObjectEntrySet().iterator();
@@ -356,7 +373,7 @@
             builder);
       } else {
         joinLocalsWithSameIndex(
-            localIndex, frameType, otherFrameType, iterator, otherIterator, builder);
+            localIndex, frameType, otherFrameType, iterator, otherIterator, appView, builder);
       }
     }
     joinLocalsOnlyPresentInOne(iterator, builder, joinWithMissingLocal);
@@ -398,11 +415,12 @@
       FrameType otherFrameType,
       ObjectBidirectionalIterator<Entry<FrameType>> iterator,
       ObjectBidirectionalIterator<Entry<FrameType>> otherIterator,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       CfFrame.Builder builder) {
     if (frameType.isSingle()) {
       if (otherFrameType.isSingle()) {
         joinSingleLocalsWithSameIndex(
-            localIndex, frameType.asSingle(), otherFrameType.asSingle(), builder);
+            localIndex, frameType.asSingle(), otherFrameType.asSingle(), appView, builder);
       } else {
         setWideLocalToTop(localIndex, builder);
         handleOverlappingLocals(localIndex + 1, iterator, otherIterator, builder);
@@ -422,8 +440,9 @@
       int localIndex,
       SingleFrameType frameType,
       SingleFrameType otherFrameType,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       CfFrame.Builder builder) {
-    builder.store(localIndex, frameType.join(otherFrameType));
+    builder.store(localIndex, frameType.join(appView, otherFrameType));
   }
 
   private void joinWideLocalsWithSameIndex(
@@ -431,7 +450,13 @@
       WideFrameType frameType,
       WideFrameType otherFrameType,
       CfFrame.Builder builder) {
-    builder.store(localIndex, frameType.join(otherFrameType));
+    WideFrameType join = frameType.join(otherFrameType);
+    if (join.isPrecise()) {
+      builder.store(localIndex, join);
+    } else {
+      assert join.isTwoWord();
+      setWideLocalToTop(localIndex, builder);
+    }
   }
 
   // TODO(b/231521474): By splitting each wide type into single left/right types, the join of each
@@ -496,11 +521,13 @@
   private Entry<FrameType> nextLocal(ObjectBidirectionalIterator<Entry<FrameType>> iterator) {
     Entry<FrameType> entry = iterator.next();
     FrameType frameType = entry.getValue();
-    if (frameType.isWide()) {
-      assert frameType.isDouble() || frameType.isLong();
+    if (frameType.isWidePrimitive()) {
+      assert frameType.isDoubleLow() || frameType.isLongLow();
       Entry<FrameType> highEntry = iterator.next();
       assert highEntry.getIntKey() == entry.getIntKey() + 1;
-      assert highEntry.getValue() == frameType;
+      assert highEntry.getValue() == frameType.asWidePrimitive().getHighType();
+    } else {
+      assert !frameType.isWide();
     }
     return entry;
   }
@@ -508,11 +535,13 @@
   private void previousLocal(ObjectBidirectionalIterator<Entry<FrameType>> iterator) {
     Entry<FrameType> entry = iterator.previous();
     FrameType frameType = entry.getValue();
-    if (frameType.isWide()) {
-      assert frameType.isDouble() || frameType.isLong();
+    if (frameType.isWidePrimitive()) {
+      assert frameType.isDoubleHigh() || frameType.isLongHigh();
       Entry<FrameType> lowEntry = iterator.previous();
       assert lowEntry.getIntKey() == entry.getIntKey() - 1;
-      assert lowEntry.getValue() == frameType;
+      assert lowEntry.getValue() == frameType.asWidePrimitive().getLowType();
+    } else {
+      assert !frameType.isWide();
     }
   }
 
@@ -536,7 +565,10 @@
     setSingleLocalToTop(localIndex + 1, builder);
   }
 
-  private ErroneousCfFrameState joinStack(Deque<PreciseFrameType> stack, CfFrame.Builder builder) {
+  private ErroneousCfFrameState joinStack(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      Deque<PreciseFrameType> stack,
+      CfFrame.Builder builder) {
     Iterator<PreciseFrameType> iterator = this.stack.iterator();
     Iterator<PreciseFrameType> otherIterator = stack.iterator();
     int stackIndex = 0;
@@ -554,7 +586,7 @@
       }
       PreciseFrameType preciseJoin;
       if (frameType.isSingle()) {
-        SingleFrameType join = frameType.asSingle().join(otherFrameType.asSingle());
+        SingleFrameType join = frameType.asSingle().join(appView, otherFrameType.asSingle());
         if (join.isOneWord()) {
           return joinStackImpreciseJoinError(stackIndex, frameType, otherFrameType);
         }
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
index 83cd59d..1274958 100644
--- 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
@@ -25,6 +25,7 @@
   private final String message;
 
   ErroneousCfFrameState(String message) {
+    assert message != null;
     this.message = message;
   }
 
@@ -114,17 +115,17 @@
   }
 
   @Override
-  public CfFrameState check(AppView<?> appView, CfFrame frame) {
+  public CfFrameState check(CfAnalysisConfig config, CfFrame frame) {
     return this;
   }
 
   @Override
-  public CfFrameState checkLocals(AppView<?> appView, CfFrame frame) {
+  public CfFrameState checkLocals(CfAnalysisConfig config, CfFrame frame) {
     return this;
   }
 
   @Override
-  public CfFrameState checkStack(AppView<?> appView, CfFrame frame) {
+  public CfFrameState checkStack(CfAnalysisConfig config, CfFrame frame) {
     return this;
   }
 
@@ -163,13 +164,15 @@
   @Override
   public CfFrameState popInitialized(
       AppView<?> appView,
+      CfAnalysisConfig config,
       DexType expectedType,
       BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn) {
     return this;
   }
 
   @Override
-  public CfFrameState popInitialized(AppView<?> appView, DexType... expectedTypes) {
+  public CfFrameState popInitialized(
+      AppView<?> appView, CfAnalysisConfig config, DexType... expectedTypes) {
     return this;
   }
 
@@ -184,8 +187,14 @@
   }
 
   @Override
+  public CfFrameState pushException(CfAnalysisConfig config, DexType guard) {
+    return this;
+  }
+
+  @Override
   public CfFrameState readLocal(
       AppView<?> appView,
+      CfAnalysisConfig config,
       int localIndex,
       ValueType expectedType,
       BiFunction<CfFrameState, FrameType, CfFrameState> fn) {
@@ -199,11 +208,18 @@
 
   @Override
   public boolean equals(Object other) {
-    return this == other;
+    if (this == other) {
+      return true;
+    }
+    if (other == null || getClass() != other.getClass()) {
+      return false;
+    }
+    ErroneousCfFrameState error = (ErroneousCfFrameState) other;
+    return message.equals(error.message);
   }
 
   @Override
   public int hashCode() {
-    return System.identityHashCode(this);
+    return message.hashCode();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingSupplier.java b/src/main/java/com/android/tools/r8/retrace/MappingSupplier.java
new file mode 100644
index 0000000..d5a8f6a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/MappingSupplier.java
@@ -0,0 +1,24 @@
+// 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.retrace;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.retrace.internal.MappingSupplierInternal;
+
+@Keep
+public abstract class MappingSupplier<T extends MappingSupplier<T>>
+    extends MappingSupplierInternal {
+
+  /***
+   * Register an allowed mapping lookup to allow for prefetching of resources.
+   *
+   * @param classReference The minified class reference allowed to be lookup up.
+   */
+  public abstract T registerClassUse(ClassReference classReference);
+
+  public abstract void verifyMappingFileHash(DiagnosticsHandler diagnosticsHandler);
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingProviderBuilder.java b/src/main/java/com/android/tools/r8/retrace/MappingSupplierBuilder.java
similarity index 64%
rename from src/main/java/com/android/tools/r8/retrace/MappingProviderBuilder.java
rename to src/main/java/com/android/tools/r8/retrace/MappingSupplierBuilder.java
index f9dbd492..c57d88a 100644
--- a/src/main/java/com/android/tools/r8/retrace/MappingProviderBuilder.java
+++ b/src/main/java/com/android/tools/r8/retrace/MappingSupplierBuilder.java
@@ -4,18 +4,15 @@
 
 package com.android.tools.r8.retrace;
 
-import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
 
 @Keep
-public abstract class MappingProviderBuilder<
-    P extends MappingProvider, T extends MappingProviderBuilder<P, T>> {
+public abstract class MappingSupplierBuilder<
+    P extends MappingSupplier, T extends MappingSupplierBuilder<P, T>> {
 
   public abstract T self();
 
   public abstract T setAllowExperimental(boolean allowExperimental);
 
-  public abstract T setDiagnosticsHandler(DiagnosticsHandler diagnosticsHandler);
-
   public abstract P build();
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/ProguardMappingProvider.java b/src/main/java/com/android/tools/r8/retrace/ProguardMappingProvider.java
deleted file mode 100644
index e192003..0000000
--- a/src/main/java/com/android/tools/r8/retrace/ProguardMappingProvider.java
+++ /dev/null
@@ -1,28 +0,0 @@
-// 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.retrace;
-
-import com.android.tools.r8.Keep;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.retrace.internal.ProguardMappingProviderBuilderImpl;
-
-@Keep
-public abstract class ProguardMappingProvider extends MappingProvider {
-
-  public static Builder builder() {
-    return new ProguardMappingProviderBuilderImpl();
-  }
-
-  @Keep
-  public abstract static class Builder
-      extends MappingProviderBuilder<ProguardMappingProvider, Builder> {
-
-    public abstract Builder registerUse(ClassReference classReference);
-
-    public abstract Builder allowLookupAllClasses();
-
-    public abstract Builder setProguardMapProducer(ProguardMapProducer proguardMapProducer);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/retrace/ProguardMappingSupplier.java b/src/main/java/com/android/tools/r8/retrace/ProguardMappingSupplier.java
new file mode 100644
index 0000000..74cd98f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/ProguardMappingSupplier.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.retrace;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.retrace.internal.ProguardMappingSupplierBuilderImpl;
+
+@Keep
+public abstract class ProguardMappingSupplier extends MappingSupplier<ProguardMappingSupplier> {
+
+  public static Builder builder() {
+    return new ProguardMappingSupplierBuilderImpl();
+  }
+
+  @Keep
+  public abstract static class Builder
+      extends MappingSupplierBuilder<ProguardMappingSupplier, Builder> {
+
+    public abstract Builder setProguardMapProducer(ProguardMapProducer proguardMapProducer);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingProvider.java b/src/main/java/com/android/tools/r8/retrace/ResultWithContext.java
similarity index 67%
rename from src/main/java/com/android/tools/r8/retrace/MappingProvider.java
rename to src/main/java/com/android/tools/r8/retrace/ResultWithContext.java
index 18bf9ee..03988a9 100644
--- a/src/main/java/com/android/tools/r8/retrace/MappingProvider.java
+++ b/src/main/java/com/android/tools/r8/retrace/ResultWithContext.java
@@ -5,7 +5,11 @@
 package com.android.tools.r8.retrace;
 
 import com.android.tools.r8.Keep;
-import com.android.tools.r8.retrace.internal.MappingProviderInternal;
 
 @Keep
-public abstract class MappingProvider extends MappingProviderInternal {}
+public interface ResultWithContext<T> {
+
+  RetraceStackTraceContext getContext();
+
+  T getResult();
+}
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 890e4d3..bb1bd0e 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -10,11 +10,10 @@
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.Version;
-import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapChecker;
-import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapChecker.VerifyMappingFileHashResult;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.retrace.RetraceCommand.Builder;
+import com.android.tools.r8.retrace.internal.ResultWithContextImpl;
 import com.android.tools.r8.retrace.internal.RetraceAbortException;
 import com.android.tools.r8.retrace.internal.RetracerImpl;
 import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy;
@@ -31,9 +30,7 @@
 import com.google.common.base.Charsets;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.io.CharStreams;
 import java.io.IOException;
-import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.PrintStream;
 import java.io.UnsupportedEncodingException;
@@ -110,7 +107,7 @@
         continue;
       }
       if (!hasSetProguardMap) {
-        builder.setProguardMapProducer(getMappingSupplier(context.head(), diagnosticsHandler));
+        builder.setMappingSupplier(getMappingSupplier(context.head(), diagnosticsHandler));
         context.next();
         hasSetProguardMap = true;
       } else if (!hasSetStackTrace) {
@@ -135,7 +132,7 @@
     return builder;
   }
 
-  private static ProguardMapProducer getMappingSupplier(
+  private static MappingSupplier getMappingSupplier(
       String mappingPath, DiagnosticsHandler diagnosticsHandler) {
     Path path = Paths.get(mappingPath);
     if (!Files.exists(path)) {
@@ -143,7 +140,12 @@
           new StringDiagnostic(String.format("Could not find mapping file '%s'.", mappingPath)));
       throw new RetraceAbortException();
     }
-    return ProguardMapProducer.fromPath(Paths.get(mappingPath));
+    boolean allowExperimentalMapVersion =
+        System.getProperty("com.android.tools.r8.experimentalmapping") != null;
+    return ProguardMappingSupplier.builder()
+        .setProguardMapProducer(ProguardMapProducer.fromPath(Paths.get(mappingPath)))
+        .setAllowExperimental(allowExperimentalMapVersion)
+        .build();
   }
 
   private static List<String> getStackTraceFromFile(
@@ -176,9 +178,11 @@
    * Retraces a complete stack frame and returns a list of retraced stack traces.
    *
    * @param stackTrace the stack trace to be retrace
+   * @param context The context to retrace the stack trace in
    * @return list of potentially ambiguous stack traces.
    */
-  public List<List<List<T>>> retraceStackTrace(List<T> stackTrace) {
+  public ResultWithContext<List<List<List<T>>>> retraceStackTrace(
+      List<T> stackTrace, RetraceStackTraceContext context) {
     ListUtils.forEachWithIndex(
         stackTrace,
         (line, lineNumber) -> {
@@ -189,69 +193,75 @@
           }
         });
     List<ST> parsed = ListUtils.map(stackTrace, stackTraceLineParser::parse);
-    return retraceStackTraceParsed(parsed);
+    return retraceStackTraceParsed(parsed, context);
   }
 
   /**
    * Retraces a complete stack frame and returns a list of retraced stack traces.
    *
    * @param stackTrace the stack trace to be retrace
+   * @param context The context to retrace the stack trace in
    * @return list of potentially ambiguous stack traces.
    */
-  public List<List<List<T>>> retraceStackTraceParsed(List<ST> stackTrace) {
+  public ResultWithContext<List<List<List<T>>>> retraceStackTraceParsed(
+      List<ST> stackTrace, RetraceStackTraceContext context) {
     RetraceStackTraceElementProxyEquivalence<T, ST> equivalence =
         new RetraceStackTraceElementProxyEquivalence<>(isVerbose);
     List<List<List<T>>> finalResult = new ArrayList<>();
-    ListUtils.fold(
-        stackTrace,
-        RetraceStackTraceContext.empty(),
-        (context, stackTraceLine) -> {
-          List<Pair<RetraceStackTraceElementProxy<T, ST>, List<T>>> resultsForLine =
-              new ArrayList<>();
-          Box<List<T>> currentList = new Box<>();
-          Set<Wrapper<RetraceStackTraceElementProxy<T, ST>>> seen = new HashSet<>();
-          List<RetraceStackTraceContext> contexts = new ArrayList<>();
-          RetraceStackTraceElementProxyResult<T, ST> retraceResult =
-              proxyRetracer.retrace(stackTraceLine, context);
-          retraceResult.stream()
-              .forEach(
-                  retracedElement -> {
-                    if (retracedElement.isTopFrame() || !retracedElement.hasRetracedClass()) {
-                      if (seen.add(equivalence.wrap(retracedElement))) {
-                        currentList.set(new ArrayList<>());
-                        resultsForLine.add(Pair.create(retracedElement, currentList.get()));
-                        contexts.add(retracedElement.getContext());
-                      } else {
-                        currentList.clear();
-                      }
-                    }
-                    if (currentList.isSet()) {
-                      currentList
-                          .get()
-                          .add(stackTraceLine.toRetracedItem(retracedElement, isVerbose));
-                    }
-                  });
-          resultsForLine.sort(Comparator.comparing(Pair::getFirst));
-          finalResult.add(ListUtils.map(resultsForLine, Pair::getSecond));
-          if (contexts.isEmpty()) {
-            return retraceResult.getResultContext();
-          }
-          return contexts.size() == 1 ? contexts.get(0) : RetraceStackTraceContext.empty();
-        });
-    return finalResult;
+    RetraceStackTraceContext finalContext =
+        ListUtils.fold(
+            stackTrace,
+            context,
+            (newContext, stackTraceLine) -> {
+              List<Pair<RetraceStackTraceElementProxy<T, ST>, List<T>>> resultsForLine =
+                  new ArrayList<>();
+              Box<List<T>> currentList = new Box<>();
+              Set<Wrapper<RetraceStackTraceElementProxy<T, ST>>> seen = new HashSet<>();
+              List<RetraceStackTraceContext> contexts = new ArrayList<>();
+              RetraceStackTraceElementProxyResult<T, ST> retraceResult =
+                  proxyRetracer.retrace(stackTraceLine, newContext);
+              retraceResult.stream()
+                  .forEach(
+                      retracedElement -> {
+                        if (retracedElement.isTopFrame() || !retracedElement.hasRetracedClass()) {
+                          if (seen.add(equivalence.wrap(retracedElement))) {
+                            currentList.set(new ArrayList<>());
+                            resultsForLine.add(Pair.create(retracedElement, currentList.get()));
+                            contexts.add(retracedElement.getContext());
+                          } else {
+                            currentList.clear();
+                          }
+                        }
+                        if (currentList.isSet()) {
+                          currentList
+                              .get()
+                              .add(stackTraceLine.toRetracedItem(retracedElement, isVerbose));
+                        }
+                      });
+              resultsForLine.sort(Comparator.comparing(Pair::getFirst));
+              finalResult.add(ListUtils.map(resultsForLine, Pair::getSecond));
+              if (contexts.isEmpty()) {
+                return retraceResult.getResultContext();
+              }
+              return contexts.size() == 1 ? contexts.get(0) : RetraceStackTraceContext.empty();
+            });
+    return ResultWithContextImpl.create(finalResult, finalContext);
   }
 
   /**
    * Retraces a stack trace frame with support for splitting up ambiguous results.
    *
    * @param stackTraceFrame The frame to retrace that can give rise to ambiguous results
+   * @param context The context to retrace the stack trace in
    * @return A collection of retraced frame where each entry in the outer list is ambiguous
    */
-  public List<List<T>> retraceFrame(T stackTraceFrame) {
+  public ResultWithContext<List<List<T>>> retraceFrame(
+      T stackTraceFrame, RetraceStackTraceContext context) {
     Map<RetraceStackTraceElementProxy<T, ST>, List<T>> ambiguousBlocks = new HashMap<>();
     List<RetraceStackTraceElementProxy<T, ST>> ambiguousKeys = new ArrayList<>();
     ST parsedLine = stackTraceLineParser.parse(stackTraceFrame);
-    proxyRetracer.retrace(parsedLine, RetraceStackTraceContext.empty()).stream()
+    Box<RetraceStackTraceContext> contextBox = new Box<>(context);
+    proxyRetracer.retrace(parsedLine, context).stream()
         .forEach(
             retracedElement -> {
               if (retracedElement.isTopFrame() || !retracedElement.hasRetracedClass()) {
@@ -261,11 +271,12 @@
               ambiguousBlocks
                   .get(ListUtils.last(ambiguousKeys))
                   .add(parsedLine.toRetracedItem(retracedElement, isVerbose));
+              contextBox.set(retracedElement.getContext());
             });
     Collections.sort(ambiguousKeys);
     List<List<T>> retracedList = new ArrayList<>();
     ambiguousKeys.forEach(key -> retracedList.add(ambiguousBlocks.get(key)));
-    return retracedList;
+    return ResultWithContextImpl.create(retracedList, contextBox.get());
   }
 
   /**
@@ -273,17 +284,22 @@
    * to distinguish them. For retracing with ambiguous results separated, use {@link #retraceFrame}
    *
    * @param stackTraceLine the stack trace line to retrace
+   * @param context The context to retrace the stack trace in
    * @return the retraced stack trace line
    */
-  public List<T> retraceLine(T stackTraceLine) {
+  public ResultWithContext<List<T>> retraceLine(
+      T stackTraceLine, RetraceStackTraceContext context) {
     ST parsedLine = stackTraceLineParser.parse(stackTraceLine);
-    return proxyRetracer.retrace(parsedLine, RetraceStackTraceContext.empty()).stream()
-        .map(
-            retraceFrame -> {
-              retraceFrame.getOriginalItem().toRetracedItem(retraceFrame, isVerbose);
-              return parsedLine.toRetracedItem(retraceFrame, isVerbose);
-            })
-        .collect(Collectors.toList());
+    Box<RetraceStackTraceContext> contextBox = new Box<>(context);
+    List<T> result =
+        proxyRetracer.retrace(parsedLine, context).stream()
+            .map(
+                retraceFrame -> {
+                  contextBox.set(retraceFrame.getContext());
+                  return parsedLine.toRetracedItem(retraceFrame, isVerbose);
+                })
+            .collect(Collectors.toList());
+    return ResultWithContextImpl.create(result, contextBox.get());
   }
 
   /**
@@ -292,80 +308,21 @@
    * @param command The command that describes the desired behavior of this retrace invocation.
    */
   public static void run(RetraceCommand command) {
-    boolean allowExperimentalMapVersion =
-        System.getProperty("com.android.tools.r8.experimentalmapping") != null;
-    runForTesting(command, allowExperimentalMapVersion);
-  }
-
-  static void runForTesting(RetraceCommand command, boolean allowExperimentalMapping) {
     try {
       Timing timing = Timing.create("R8 retrace", command.printMemory());
       RetraceOptions options = command.getOptions();
+      MappingSupplier<?> mappingSupplier = options.getMappingSupplier();
       if (command.getOptions().isVerifyMappingFileHash()) {
-        try (InputStream reader = options.getProguardMapProducer().get()) {
-          VerifyMappingFileHashResult checkResult =
-              ProguardMapChecker.validateProguardMapHash(
-                  CharStreams.toString(new InputStreamReader(reader, Charsets.UTF_8)));
-          if (checkResult.isError()) {
-            command
-                .getOptions()
-                .getDiagnosticsHandler()
-                .error(new StringDiagnostic(checkResult.getMessage()));
-            throw new RuntimeException(checkResult.getMessage());
-          }
-          if (!checkResult.isOk()) {
-            command
-                .getOptions()
-                .getDiagnosticsHandler()
-                .warning(new StringDiagnostic(checkResult.getMessage()));
-          }
-        } catch (IOException e) {
-          command.getOptions().getDiagnosticsHandler().error(new ExceptionDiagnostic(e));
-          throw new RuntimeException(e);
-        }
+        mappingSupplier.verifyMappingFileHash(options.getDiagnosticsHandler());
         return;
       }
       DiagnosticsHandler diagnosticsHandler = options.getDiagnosticsHandler();
-      timing.begin("Parsing");
       StackTraceRegularExpressionParser stackTraceLineParser =
           new StackTraceRegularExpressionParser(options.getRegularExpression());
-      List<StackTraceElementStringProxy> parsedStackTrace = new ArrayList<>();
-      ListUtils.forEachWithIndex(
-          command.getStackTrace(),
-          (line, lineNumber) -> {
-            if (line == null) {
-              diagnosticsHandler.error(
-                  RetraceInvalidStackTraceLineDiagnostics.createNull(lineNumber));
-              throw new RetraceAbortException();
-            }
-            parsedStackTrace.add(stackTraceLineParser.parse(line));
-          });
-      timing.end();
       timing.begin("Read proguard map");
-      ProguardMappingProvider.Builder mappingBuilder =
-          ProguardMappingProvider.builder()
-              .setProguardMapProducer(options.getProguardMapProducer())
-              .setDiagnosticsHandler(diagnosticsHandler)
-              .setAllowExperimental(allowExperimentalMapping);
-      parsedStackTrace.forEach(
-          proxy -> {
-            if (proxy.hasClassName()) {
-              mappingBuilder.registerUse(proxy.getClassReference());
-            }
-            if (proxy.hasMethodArguments()) {
-              Arrays.stream(proxy.getMethodArguments().split(","))
-                  .forEach(typeName -> registerUseFromTypeReference(mappingBuilder, typeName));
-            }
-            if (proxy.hasFieldOrReturnType() && !proxy.getFieldOrReturnType().equals("void")) {
-              registerUseFromTypeReference(mappingBuilder, proxy.getFieldOrReturnType());
-            }
-          });
-      MappingProvider mappingProvider = mappingBuilder.build();
-      timing.end();
-      timing.begin("Retracing");
       RetracerImpl retracer =
           RetracerImpl.builder()
-              .setMappingProvider(mappingProvider)
+              .setMappingSupplier(mappingSupplier)
               .setDiagnosticsHandler(diagnosticsHandler)
               .build();
       retracer
@@ -377,17 +334,55 @@
                       RetraceUnknownMapVersionDiagnostic.create(mapVersionInfo.getValue()));
                 }
               });
-      StringRetrace stringRetrace =
+      StringRetrace stringRetracer =
           new StringRetrace(
               stackTraceLineParser,
               StackTraceElementProxyRetracer.createDefault(retracer),
               diagnosticsHandler,
               options.isVerbose());
-      List<String> retraced = stringRetrace.retraceParsed(parsedStackTrace);
       timing.end();
-      timing.begin("Report result");
-      command.getRetracedStackTraceConsumer().accept(retraced);
-      timing.end();
+      StackTraceSupplier stackTraceSupplier = command.getStacktraceSupplier();
+      int lineNumber = 0;
+      RetraceStackTraceContext context = RetraceStackTraceContext.empty();
+      List<String> currentStackTrace;
+      while ((currentStackTrace = stackTraceSupplier.get()) != null) {
+        timing.begin("Parsing");
+        List<StackTraceElementStringProxy> parsedStackTrace = new ArrayList<>();
+        for (String line : currentStackTrace) {
+          if (line == null) {
+            diagnosticsHandler.error(
+                RetraceInvalidStackTraceLineDiagnostics.createNull(lineNumber));
+            throw new RetraceAbortException();
+          }
+          parsedStackTrace.add(stackTraceLineParser.parse(line));
+          lineNumber += 1;
+        }
+        timing.end();
+        parsedStackTrace.forEach(
+            proxy -> {
+              if (proxy.hasClassName()) {
+                mappingSupplier.registerClassUse(proxy.getClassReference());
+              }
+              if (proxy.hasMethodArguments()) {
+                Arrays.stream(proxy.getMethodArguments().split(","))
+                    .forEach(typeName -> registerUseFromTypeReference(mappingSupplier, typeName));
+              }
+              if (proxy.hasFieldOrReturnType() && !proxy.getFieldOrReturnType().equals("void")) {
+                registerUseFromTypeReference(mappingSupplier, proxy.getFieldOrReturnType());
+              }
+            });
+        timing.begin("Retracing");
+        ResultWithContext<List<String>> listResultWithContext =
+            stringRetracer.retraceParsed(parsedStackTrace, context);
+        timing.end();
+        timing.begin("Report result");
+        context = listResultWithContext.getContext();
+        List<String> result = listResultWithContext.getResult();
+        if (!result.isEmpty() || currentStackTrace.isEmpty()) {
+          command.getRetracedStackTraceConsumer().accept(result);
+        }
+        timing.end();
+      }
       if (command.printTimes()) {
         timing.report();
       }
@@ -398,13 +393,13 @@
   }
 
   private static void registerUseFromTypeReference(
-      ProguardMappingProvider.Builder builder, String typeName) {
+      MappingSupplier<?> mappingSupplier, String typeName) {
     TypeReference typeReference = Reference.typeFromTypeName(typeName);
     if (typeReference.isArray()) {
       typeReference = typeReference.asArray().getBaseType();
     }
     if (typeReference.isClass()) {
-      builder.registerUse(typeReference.asClass());
+      mappingSupplier.registerClassUse(typeReference.asClass());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
index b6d70e9..10db02a 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
@@ -7,26 +7,27 @@
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.retrace.internal.StackTraceRegularExpressionParser;
+import com.android.tools.r8.utils.Box;
 import java.util.List;
 import java.util.function.Consumer;
 
 @Keep
 public class RetraceCommand {
 
-  private final List<String> stackTrace;
+  private final StackTraceSupplier stacktraceSupplier;
   private final Consumer<List<String>> retracedStackTraceConsumer;
   // Not inheriting to allow for static builder methods.
   private final RetraceOptions options;
 
   private RetraceCommand(
-      List<String> stackTrace,
+      StackTraceSupplier stackTraceSupplier,
       Consumer<List<String>> retracedStackTraceConsumer,
       RetraceOptions options) {
-    this.stackTrace = stackTrace;
+    this.stacktraceSupplier = stackTraceSupplier;
     this.retracedStackTraceConsumer = retracedStackTraceConsumer;
     this.options = options;
 
-    assert this.stackTrace != null || options.isVerifyMappingFileHash();
+    assert this.stacktraceSupplier != null || options.isVerifyMappingFileHash();
     assert this.retracedStackTraceConsumer != null;
   }
 
@@ -38,8 +39,8 @@
     return System.getProperty("com.android.tools.r8.printmemory") != null;
   }
 
-  public List<String> getStackTrace() {
-    return stackTrace;
+  public StackTraceSupplier getStacktraceSupplier() {
+    return stacktraceSupplier;
   }
 
   public Consumer<List<String>> getRetracedStackTraceConsumer() {
@@ -69,9 +70,9 @@
 
     private boolean isVerbose;
     private final DiagnosticsHandler diagnosticsHandler;
-    private ProguardMapProducer proguardMapProducer;
+    private MappingSupplier<?> mappingSupplier;
     private String regularExpression = StackTraceRegularExpressionParser.DEFAULT_REGULAR_EXPRESSION;
-    private List<String> stackTrace;
+    private StackTraceSupplier stackTrace;
     private Consumer<List<String>> retracedStackTraceConsumer;
     private boolean verifyMappingFileHash = false;
 
@@ -85,13 +86,9 @@
       return this;
     }
 
-    /**
-     * Set a producer for the proguard mapping contents.
-     *
-     * @param producer Producer for
-     */
-    public Builder setProguardMapProducer(ProguardMapProducer producer) {
-      this.proguardMapProducer = producer;
+    /** Set a mapping supplier for providing mapping contents. */
+    public Builder setMappingSupplier(MappingSupplier<?> mappingSupplier) {
+      this.mappingSupplier = mappingSupplier;
       return this;
     }
 
@@ -114,6 +111,17 @@
      *     first line.
      */
     public Builder setStackTrace(List<String> stackTrace) {
+      Box<List<String>> box = new Box<>(stackTrace);
+      return setStackTrace(() -> box.getAndSet(null));
+    }
+
+    /**
+     * Set a supplier of the obfuscated stack trace that is to be retraced.
+     *
+     * @param stackTrace Supplier where the first query should provide the top-most entry(the
+     *     closest stack to the error). Use null to specify no more lines.
+     */
+    public Builder setStackTrace(StackTraceSupplier stackTrace) {
       this.stackTrace = stackTrace;
       return this;
     }
@@ -138,7 +146,7 @@
       if (this.diagnosticsHandler == null) {
         throw new RuntimeException("DiagnosticsHandler not specified");
       }
-      if (this.proguardMapProducer == null) {
+      if (this.mappingSupplier == null) {
         throw new RuntimeException("ProguardMapSupplier not specified");
       }
       if (this.stackTrace == null && !verifyMappingFileHash) {
@@ -150,7 +158,7 @@
       RetraceOptions retraceOptions =
           RetraceOptions.builder(diagnosticsHandler)
               .setRegularExpression(regularExpression)
-              .setProguardMapProducer(proguardMapProducer)
+              .setMappingSupplier(mappingSupplier)
               .setVerbose(isVerbose)
               .setVerifyMappingFileHash(verifyMappingFileHash)
               .build();
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceHelper.java b/src/main/java/com/android/tools/r8/retrace/RetraceHelper.java
deleted file mode 100644
index c966897..0000000
--- a/src/main/java/com/android/tools/r8/retrace/RetraceHelper.java
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.retrace;
-
-/** Non-kept class for internal access from tests. */
-public class RetraceHelper {
-
-  public static void runForTesting(RetraceCommand command, boolean allowExperimentalMapping) {
-    Retrace.runForTesting(command, allowExperimentalMapping);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceOptions.java b/src/main/java/com/android/tools/r8/retrace/RetraceOptions.java
index 2543f35..7c00ed2 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceOptions.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceOptions.java
@@ -20,22 +20,22 @@
   private final boolean verifyMappingFileHash;
   private final String regularExpression;
   private final DiagnosticsHandler diagnosticsHandler;
-  private final ProguardMapProducer proguardMapProducer;
+  private final MappingSupplier mappingSupplier;
 
   private RetraceOptions(
       String regularExpression,
       DiagnosticsHandler diagnosticsHandler,
-      ProguardMapProducer proguardMapProducer,
+      MappingSupplier mappingSupplier,
       boolean isVerbose,
       boolean verifyMappingFileHash) {
     this.regularExpression = regularExpression;
     this.diagnosticsHandler = diagnosticsHandler;
-    this.proguardMapProducer = proguardMapProducer;
+    this.mappingSupplier = mappingSupplier;
     this.isVerbose = isVerbose;
     this.verifyMappingFileHash = verifyMappingFileHash;
 
     assert diagnosticsHandler != null;
-    assert proguardMapProducer != null;
+    assert mappingSupplier != null;
   }
 
   public boolean isVerbose() {
@@ -54,8 +54,8 @@
     return diagnosticsHandler;
   }
 
-  public ProguardMapProducer getProguardMapProducer() {
-    return proguardMapProducer;
+  public MappingSupplier getMappingSupplier() {
+    return mappingSupplier;
   }
 
   /** Utility method for obtaining a builder with a default diagnostics handler. */
@@ -78,7 +78,7 @@
     private boolean isVerbose;
     private boolean verifyMappingFileHash;
     private final DiagnosticsHandler diagnosticsHandler;
-    private ProguardMapProducer proguardMapProducer;
+    private MappingSupplier mappingSupplier;
     private String regularExpression = defaultRegularExpression();
 
     Builder(DiagnosticsHandler diagnosticsHandler) {
@@ -97,13 +97,9 @@
       return this;
     }
 
-    /**
-     * Set a producer for the proguard mapping contents.
-     *
-     * @param producer Producer for
-     */
-    public Builder setProguardMapProducer(ProguardMapProducer producer) {
-      this.proguardMapProducer = producer;
+    /** Set a mapping supplier for providing mapping contents. */
+    public Builder setMappingSupplier(MappingSupplier producer) {
+      this.mappingSupplier = producer;
       return this;
     }
 
@@ -123,18 +119,14 @@
       if (this.diagnosticsHandler == null) {
         throw new RuntimeException("DiagnosticsHandler not specified");
       }
-      if (this.proguardMapProducer == null) {
+      if (this.mappingSupplier == null) {
         throw new RuntimeException("ProguardMapSupplier not specified");
       }
       if (this.regularExpression == null) {
         throw new RuntimeException("Regular expression not specified");
       }
       return new RetraceOptions(
-          regularExpression,
-          diagnosticsHandler,
-          proguardMapProducer,
-          isVerbose,
-          verifyMappingFileHash);
+          regularExpression, diagnosticsHandler, mappingSupplier, isVerbose, verifyMappingFileHash);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/Retracer.java b/src/main/java/com/android/tools/r8/retrace/Retracer.java
index 15d3b48..650883b 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retracer.java
@@ -73,14 +73,10 @@
   static Retracer createDefault(
       ProguardMapProducer proguardMapProducer, DiagnosticsHandler diagnosticsHandler) {
     try {
-      ProguardMappingProvider mappingProvider =
-          ProguardMappingProvider.builder()
-              .setProguardMapProducer(proguardMapProducer)
-              .setDiagnosticsHandler(diagnosticsHandler)
-              .allowLookupAllClasses()
-              .build();
+      ProguardMappingSupplier mappingSupplier =
+          ProguardMappingSupplier.builder().setProguardMapProducer(proguardMapProducer).build();
       return Retracer.builder()
-          .setMappingProvider(mappingProvider)
+          .setMappingSupplier(mappingSupplier)
           .setDiagnosticsHandler(diagnosticsHandler)
           .build();
     } catch (Exception e) {
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracerBuilder.java b/src/main/java/com/android/tools/r8/retrace/RetracerBuilder.java
index efd56ce..21eb8e0 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetracerBuilder.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetracerBuilder.java
@@ -10,7 +10,7 @@
 @Keep
 public interface RetracerBuilder {
 
-  RetracerBuilder setMappingProvider(MappingProvider mappingProvider);
+  RetracerBuilder setMappingSupplier(MappingSupplier mappingSupplier);
 
   RetracerBuilder setDiagnosticsHandler(DiagnosticsHandler diagnosticsHandler);
 
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingProvider.java b/src/main/java/com/android/tools/r8/retrace/StackTraceSupplier.java
similarity index 67%
copy from src/main/java/com/android/tools/r8/retrace/MappingProvider.java
copy to src/main/java/com/android/tools/r8/retrace/StackTraceSupplier.java
index 18bf9ee..9940c5c 100644
--- a/src/main/java/com/android/tools/r8/retrace/MappingProvider.java
+++ b/src/main/java/com/android/tools/r8/retrace/StackTraceSupplier.java
@@ -5,7 +5,11 @@
 package com.android.tools.r8.retrace;
 
 import com.android.tools.r8.Keep;
-import com.android.tools.r8.retrace.internal.MappingProviderInternal;
+import java.util.List;
 
 @Keep
-public abstract class MappingProvider extends MappingProviderInternal {}
+@FunctionalInterface
+public interface StackTraceSupplier {
+
+  List<String> get();
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/StringRetrace.java b/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
index b5ed6ae..1c1a2ee 100644
--- a/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
@@ -8,6 +8,8 @@
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
+import com.android.tools.r8.retrace.internal.ResultWithContextImpl;
+import com.android.tools.r8.retrace.internal.RetracerImpl;
 import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -40,7 +42,10 @@
    */
   public static StringRetrace create(RetraceOptions command) {
     return create(
-        Retracer.createDefault(command.getProguardMapProducer(), command.getDiagnosticsHandler()),
+        RetracerImpl.builder()
+            .setMappingSupplier(command.getMappingSupplier())
+            .setDiagnosticsHandler(command.getDiagnosticsHandler())
+            .build(),
         command.getDiagnosticsHandler(),
         command.getRegularExpression(),
         command.isVerbose());
@@ -73,12 +78,15 @@
    * appended automatically to the retraced string.
    *
    * @param stackTrace the incoming stack trace
+   * @param context The context to retrace the stack trace in
    * @return the retraced stack trace
    */
-  public List<String> retrace(List<String> stackTrace) {
+  public ResultWithContext<List<String>> retrace(
+      List<String> stackTrace, RetraceStackTraceContext context) {
+    ResultWithContext<List<List<List<String>>>> listResultWithContext =
+        retraceStackTrace(stackTrace, context);
     List<String> retracedStrings = new ArrayList<>();
-    List<List<List<String>>> lists = retraceStackTrace(stackTrace);
-    for (List<List<String>> newLines : lists) {
+    for (List<List<String>> newLines : listResultWithContext.getResult()) {
       ListUtils.forEachWithIndex(
           newLines,
           (inlineFrames, ambiguousIndex) -> {
@@ -102,7 +110,7 @@
             }
           });
     }
-    return retracedStrings;
+    return ResultWithContextImpl.create(retracedStrings, listResultWithContext.getContext());
   }
 
   /**
@@ -110,12 +118,15 @@
    * will be appended automatically to the retraced string.
    *
    * @param stackTrace the incoming parsed stack trace
+   * @param context The context to retrace the stack trace in
    * @return the retraced stack trace
    */
-  public List<String> retraceParsed(List<StackTraceElementStringProxy> stackTrace) {
+  public ResultWithContext<List<String>> retraceParsed(
+      List<StackTraceElementStringProxy> stackTrace, RetraceStackTraceContext context) {
+    ResultWithContext<List<List<List<String>>>> listResultWithContext =
+        retraceStackTraceParsed(stackTrace, context);
     List<String> retracedStrings = new ArrayList<>();
-    List<List<List<String>>> lists = retraceStackTraceParsed(stackTrace);
-    for (List<List<String>> newLines : lists) {
+    for (List<List<String>> newLines : listResultWithContext.getResult()) {
       ListUtils.forEachWithIndex(
           newLines,
           (inlineFrames, ambiguousIndex) -> {
@@ -139,19 +150,23 @@
             }
           });
     }
-    return retracedStrings;
+    return ResultWithContextImpl.create(retracedStrings, listResultWithContext.getContext());
   }
 
   /**
    * Retraces a single stack trace line and returns the potential list of original frames
    *
    * @param stackTraceLine the stack trace line to retrace
+   * @param context The context to retrace the stack trace in
    * @return the retraced frames
    */
-  public List<String> retrace(String stackTraceLine) {
+  public ResultWithContext<List<String>> retrace(
+      String stackTraceLine, RetraceStackTraceContext context) {
+    ResultWithContext<List<List<String>>> listResultWithContext =
+        retraceFrame(stackTraceLine, context);
     List<String> result = new ArrayList<>();
-    joinAmbiguousLines(retraceFrame(stackTraceLine), result::add);
-    return result;
+    joinAmbiguousLines(listResultWithContext.getResult(), result::add);
+    return ResultWithContextImpl.create(result, listResultWithContext.getContext());
   }
 
   private void joinAmbiguousLines(
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MappingProviderInternal.java b/src/main/java/com/android/tools/r8/retrace/internal/MappingProviderInternal.java
deleted file mode 100644
index 13187cc..0000000
--- a/src/main/java/com/android/tools/r8/retrace/internal/MappingProviderInternal.java
+++ /dev/null
@@ -1,18 +0,0 @@
-// 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.retrace.internal;
-
-import com.android.tools.r8.naming.ClassNamingForNameMapper;
-import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
-import java.util.Set;
-
-public abstract class MappingProviderInternal {
-
-  abstract ClassNamingForNameMapper getClassNaming(String typeName);
-
-  abstract String getSourceFileForClass(String typeName);
-
-  abstract Set<MapVersionMappingInformation> getMapVersions();
-}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MappingSupplierInternal.java b/src/main/java/com/android/tools/r8/retrace/internal/MappingSupplierInternal.java
new file mode 100644
index 0000000..c47c467
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/MappingSupplierInternal.java
@@ -0,0 +1,20 @@
+// 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.retrace.internal;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.naming.ClassNamingForNameMapper;
+import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
+import java.util.Set;
+
+public abstract class MappingSupplierInternal {
+
+  abstract ClassNamingForNameMapper getClassNaming(
+      DiagnosticsHandler diagnosticsHandler, String typeName);
+
+  abstract String getSourceFileForClass(DiagnosticsHandler diagnosticsHandler, String typeName);
+
+  abstract Set<MapVersionMappingInformation> getMapVersions(DiagnosticsHandler diagnosticsHandler);
+}
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 0d764a5..e6589d5f 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
@@ -189,9 +189,12 @@
   private int endIndex = 0;
 
   private final Predicate<String> filter;
+  private final boolean readPreambleAndSourceFiles;
 
-  protected ProguardMapReaderWithFiltering(Predicate<String> filter) {
+  protected ProguardMapReaderWithFiltering(
+      Predicate<String> filter, boolean readPreambleAndSourceFiles) {
     this.filter = filter;
+    this.readPreambleAndSourceFiles = readPreambleAndSourceFiles;
   }
 
   public abstract byte[] read() throws IOException;
@@ -221,10 +224,12 @@
         String classMapping = getBufferAsString(bytes);
         String obfuscatedClassName = getObfuscatedClassName(classMapping);
         isInsideClassOfInterest = filter.test(obfuscatedClassName);
-        return classMapping;
-      } else if (lineParserState == IS_COMMENT_SOURCE_FILE) {
+        if (isInsideClassOfInterest || readPreambleAndSourceFiles) {
+          return classMapping;
+        }
+      } else if (lineParserState == IS_COMMENT_SOURCE_FILE && readPreambleAndSourceFiles) {
         return getBufferAsString(bytes);
-      } else if (isInsideClassOfInterest || !seenFirstClass) {
+      } else if (isInsideClassOfInterest || (!seenFirstClass && readPreambleAndSourceFiles)) {
         return getBufferAsString(bytes);
       }
     }
@@ -284,8 +289,11 @@
     private int temporaryBufferPosition = 0;
 
     public ProguardMapReaderWithFilteringMappedBuffer(
-        Path mappingFile, Predicate<String> classNamesOfInterest) throws IOException {
-      super(classNamesOfInterest);
+        Path mappingFile,
+        Predicate<String> classNamesOfInterest,
+        boolean readPreambleAndSourceFiles)
+        throws IOException {
+      super(classNamesOfInterest, readPreambleAndSourceFiles);
       fileChannel = FileChannel.open(mappingFile, StandardOpenOption.READ);
       channelSize = fileChannel.size();
       readFromChannel();
@@ -364,8 +372,10 @@
     private int endReadIndex = 0;
 
     public ProguardMapReaderWithFilteringInputBuffer(
-        InputStream inputStream, Predicate<String> classNamesOfInterest) {
-      super(classNamesOfInterest);
+        InputStream inputStream,
+        Predicate<String> classNamesOfInterest,
+        boolean readPreambleAndSourceFiles) {
+      super(classNamesOfInterest, readPreambleAndSourceFiles);
       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
deleted file mode 100644
index 8cf9ace..0000000
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
+++ /dev/null
@@ -1,82 +0,0 @@
-// 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.retrace.internal;
-
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.naming.LineReader;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.retrace.InvalidMappingFileException;
-import com.android.tools.r8.retrace.ProguardMapProducer;
-import com.android.tools.r8.retrace.ProguardMappingProvider;
-import com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.ProguardMapReaderWithFilteringInputBuffer;
-import com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.ProguardMapReaderWithFilteringMappedBuffer;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.function.Predicate;
-
-public class ProguardMappingProviderBuilderImpl extends ProguardMappingProvider.Builder {
-
-  private ProguardMapProducer proguardMapProducer;
-  private boolean allowExperimental = false;
-  private Set<String> allowedLookup = new HashSet<>();
-  private boolean allowLookupAllClasses = false;
-  private DiagnosticsHandler diagnosticsHandler = new DiagnosticsHandler() {};
-
-  @Override
-  public ProguardMappingProvider.Builder self() {
-    return this;
-  }
-
-  @Override
-  public ProguardMappingProvider.Builder setAllowExperimental(boolean allowExperimental) {
-    this.allowExperimental = allowExperimental;
-    return self();
-  }
-
-  @Override
-  public ProguardMappingProvider.Builder setDiagnosticsHandler(
-      DiagnosticsHandler diagnosticsHandler) {
-    this.diagnosticsHandler = diagnosticsHandler;
-    return self();
-  }
-
-  @Override
-  public ProguardMappingProvider.Builder registerUse(ClassReference classReference) {
-    allowedLookup.add(classReference.getTypeName());
-    return self();
-  }
-
-  @Override
-  public ProguardMappingProvider.Builder allowLookupAllClasses() {
-    allowLookupAllClasses = true;
-    return self();
-  }
-
-  @Override
-  public ProguardMappingProvider.Builder setProguardMapProducer(
-      ProguardMapProducer proguardMapProducer) {
-    this.proguardMapProducer = proguardMapProducer;
-    return self();
-  }
-
-  @Override
-  public ProguardMappingProvider build() {
-    try {
-      Predicate<String> buildForClass = allowLookupAllClasses ? null : allowedLookup::contains;
-      LineReader reader =
-          proguardMapProducer.isFileBacked()
-              ? new ProguardMapReaderWithFilteringMappedBuffer(
-                  proguardMapProducer.getPath(), buildForClass)
-              : new ProguardMapReaderWithFilteringInputBuffer(
-                  proguardMapProducer.get(), buildForClass);
-      return new ProguardMappingProviderImpl(
-          ClassNameMapper.mapperFromLineReaderWithFiltering(
-              reader, diagnosticsHandler, true, allowExperimental));
-    } catch (Exception e) {
-      throw new InvalidMappingFileException(e);
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderImpl.java
deleted file mode 100644
index c15efef..0000000
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderImpl.java
+++ /dev/null
@@ -1,51 +0,0 @@
-// 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.retrace.internal;
-
-import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.naming.ClassNamingForNameMapper;
-import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
-import com.android.tools.r8.retrace.IllegalClassNameLookupException;
-import com.android.tools.r8.retrace.ProguardMappingProvider;
-import java.util.Set;
-
-/**
- * IntelliJ highlights the class as being invalid because it cannot see getClassNameMapper is
- * defined on the class for some reason.
- */
-public class ProguardMappingProviderImpl extends ProguardMappingProvider {
-
-  private final ClassNameMapper classNameMapper;
-  private final Set<String> allowedLookupTypeNames;
-
-  public ProguardMappingProviderImpl(ClassNameMapper classNameMapper) {
-    this(classNameMapper, null);
-  }
-
-  public ProguardMappingProviderImpl(
-      ClassNameMapper classNameMapper, Set<String> allowedLookupTypeNames) {
-    this.classNameMapper = classNameMapper;
-    this.allowedLookupTypeNames = allowedLookupTypeNames;
-  }
-
-  @Override
-  Set<MapVersionMappingInformation> getMapVersions() {
-    return classNameMapper.getMapVersions();
-  }
-
-  @Override
-  ClassNamingForNameMapper getClassNaming(String typeName) {
-    // TODO(b/226885646): Enable lookup check when there are no additional lookups.
-    if (false && !allowedLookupTypeNames.contains(typeName)) {
-      throw new IllegalClassNameLookupException(typeName);
-    }
-    return classNameMapper.getClassNaming(typeName);
-  }
-
-  @Override
-  String getSourceFileForClass(String typeName) {
-    return classNameMapper.getSourceFile(typeName);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierBuilderImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierBuilderImpl.java
new file mode 100644
index 0000000..8cea599
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierBuilderImpl.java
@@ -0,0 +1,37 @@
+// 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.retrace.internal;
+
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.ProguardMappingSupplier;
+
+public class ProguardMappingSupplierBuilderImpl extends ProguardMappingSupplier.Builder {
+
+  private ProguardMapProducer proguardMapProducer;
+  private boolean allowExperimental = false;
+
+  @Override
+  public ProguardMappingSupplier.Builder self() {
+    return this;
+  }
+
+  @Override
+  public ProguardMappingSupplier.Builder setAllowExperimental(boolean allowExperimental) {
+    this.allowExperimental = allowExperimental;
+    return self();
+  }
+
+  @Override
+  public ProguardMappingSupplier.Builder setProguardMapProducer(
+      ProguardMapProducer proguardMapProducer) {
+    this.proguardMapProducer = proguardMapProducer;
+    return self();
+  }
+
+  @Override
+  public ProguardMappingSupplier build() {
+    return new ProguardMappingSupplierImpl(proguardMapProducer, allowExperimental);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierImpl.java
new file mode 100644
index 0000000..a6ae27b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierImpl.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.retrace.internal;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.ClassNamingForNameMapper;
+import com.android.tools.r8.naming.LineReader;
+import com.android.tools.r8.naming.MapVersion;
+import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapChecker;
+import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapChecker.VerifyMappingFileHashResult;
+import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.retrace.InvalidMappingFileException;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.ProguardMappingSupplier;
+import com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.ProguardMapReaderWithFilteringInputBuffer;
+import com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.ProguardMapReaderWithFilteringMappedBuffer;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.io.CharStreams;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/**
+ * IntelliJ highlights the class as being invalid because it cannot see getClassNameMapper is
+ * defined on the class for some reason.
+ */
+public class ProguardMappingSupplierImpl extends ProguardMappingSupplier {
+
+  private final ProguardMapProducer proguardMapProducer;
+  private final boolean allowExperimental;
+
+  private ClassNameMapper classNameMapper;
+  private final Set<String> pendingClassMappings = new HashSet<>();
+  private final Set<String> builtClassMappings;
+
+  public ProguardMappingSupplierImpl(ClassNameMapper classNameMapper) {
+    this.classNameMapper = classNameMapper;
+    this.proguardMapProducer = null;
+    this.allowExperimental = true;
+    builtClassMappings = null;
+  }
+
+  ProguardMappingSupplierImpl(ProguardMapProducer proguardMapProducer, boolean allowExperimental) {
+    this.proguardMapProducer = proguardMapProducer;
+    this.allowExperimental = allowExperimental;
+    builtClassMappings = proguardMapProducer.isFileBacked() ? new HashSet<>() : null;
+  }
+
+  private boolean hasClassMappingFor(String typeName) {
+    return builtClassMappings == null || builtClassMappings.contains(typeName);
+  }
+
+  @Override
+  Set<MapVersionMappingInformation> getMapVersions(DiagnosticsHandler diagnosticsHandler) {
+    return getClassNameMapper(diagnosticsHandler).getMapVersions();
+  }
+
+  @Override
+  ClassNamingForNameMapper getClassNaming(DiagnosticsHandler diagnosticsHandler, String typeName) {
+    if (!hasClassMappingFor(typeName)) {
+      pendingClassMappings.add(typeName);
+    }
+    return getClassNameMapper(diagnosticsHandler).getClassNaming(typeName);
+  }
+
+  @Override
+  String getSourceFileForClass(DiagnosticsHandler diagnosticsHandler, String typeName) {
+    return getClassNameMapper(diagnosticsHandler).getSourceFile(typeName);
+  }
+
+  private ClassNameMapper getClassNameMapper(DiagnosticsHandler diagnosticsHandler) {
+    if (classNameMapper != null && pendingClassMappings.isEmpty()) {
+      return classNameMapper;
+    }
+    if (classNameMapper != null && !proguardMapProducer.isFileBacked()) {
+      throw new RuntimeException("Cannot re-open a proguard map producer that is not file backed");
+    }
+    try {
+      Predicate<String> buildForClass =
+          builtClassMappings == null ? null : pendingClassMappings::contains;
+      boolean readPreambleAndSourceFile = classNameMapper == null;
+      LineReader reader =
+          proguardMapProducer.isFileBacked()
+              ? new ProguardMapReaderWithFilteringMappedBuffer(
+                  proguardMapProducer.getPath(), buildForClass, readPreambleAndSourceFile)
+              : new ProguardMapReaderWithFilteringInputBuffer(
+                  proguardMapProducer.get(), buildForClass, readPreambleAndSourceFile);
+      ClassNameMapper classNameMapper =
+          ClassNameMapper.mapperFromLineReaderWithFiltering(
+              reader, getMapVersion(), diagnosticsHandler, true, allowExperimental);
+      this.classNameMapper = classNameMapper.combine(this.classNameMapper);
+      if (builtClassMappings != null) {
+        builtClassMappings.addAll(pendingClassMappings);
+      }
+      pendingClassMappings.clear();
+    } catch (Exception e) {
+      throw new InvalidMappingFileException(e);
+    }
+    return classNameMapper;
+  }
+
+  private MapVersion getMapVersion() {
+    if (classNameMapper == null) {
+      return MapVersion.MAP_VERSION_NONE;
+    } else {
+      MapVersionMappingInformation mapVersion = classNameMapper.getFirstMappingInformation();
+      return mapVersion == null ? MapVersion.MAP_VERSION_UNKNOWN : mapVersion.getMapVersion();
+    }
+  }
+
+  @Override
+  public ProguardMappingSupplier registerClassUse(ClassReference classReference) {
+    if (!hasClassMappingFor(classReference.getTypeName())) {
+      pendingClassMappings.add(classReference.getTypeName());
+    }
+    return this;
+  }
+
+  @Override
+  public void verifyMappingFileHash(DiagnosticsHandler diagnosticsHandler) {
+    try (InputStream reader = proguardMapProducer.get()) {
+      VerifyMappingFileHashResult checkResult =
+          ProguardMapChecker.validateProguardMapHash(
+              CharStreams.toString(new InputStreamReader(reader, StandardCharsets.UTF_8)));
+      if (checkResult.isError()) {
+        diagnosticsHandler.error(new StringDiagnostic(checkResult.getMessage()));
+        throw new RuntimeException(checkResult.getMessage());
+      }
+      if (!checkResult.isOk()) {
+        diagnosticsHandler.warning(new StringDiagnostic(checkResult.getMessage()));
+      }
+    } catch (IOException e) {
+      diagnosticsHandler.error(new ExceptionDiagnostic(e));
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ResultWithContextImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ResultWithContextImpl.java
new file mode 100644
index 0000000..ab92a65
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ResultWithContextImpl.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.retrace.internal;
+
+import com.android.tools.r8.retrace.ResultWithContext;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+
+public class ResultWithContextImpl<T> implements ResultWithContext<T> {
+
+  private final T result;
+  private final RetraceStackTraceContext context;
+
+  private ResultWithContextImpl(T result, RetraceStackTraceContext context) {
+    this.result = result;
+    this.context = context;
+  }
+
+  public static <T> ResultWithContext<T> create(T result, RetraceStackTraceContext context) {
+    return new ResultWithContextImpl<>(result, context);
+  }
+
+  @Override
+  public RetraceStackTraceContext getContext() {
+    return context;
+  }
+
+  @Override
+  public T getResult() {
+    return result;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
index 346b417..daec745 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
-import com.android.tools.r8.retrace.MappingProvider;
+import com.android.tools.r8.retrace.MappingSupplier;
 import com.android.tools.r8.retrace.RetraceFrameResult;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.Retracer;
@@ -21,14 +21,14 @@
 /** A default implementation for the retrace api using the ClassNameMapper defined in R8. */
 public class RetracerImpl implements Retracer {
 
-  private final MappingProviderInternal classNameMapperProvider;
+  private final MappingSupplierInternal classNameMapperSupplier;
   private final DiagnosticsHandler diagnosticsHandler;
 
   private RetracerImpl(
-      MappingProviderInternal classNameMapperProvider, DiagnosticsHandler diagnosticsHandler) {
-    this.classNameMapperProvider = classNameMapperProvider;
+      MappingSupplierInternal classNameMapperSupplier, DiagnosticsHandler diagnosticsHandler) {
+    this.classNameMapperSupplier = classNameMapperSupplier;
     this.diagnosticsHandler = diagnosticsHandler;
-    assert classNameMapperProvider != null;
+    assert classNameMapperSupplier != null;
   }
 
   public DiagnosticsHandler getDiagnosticsHandler() {
@@ -70,7 +70,9 @@
   @Override
   public RetraceClassResultImpl retraceClass(ClassReference classReference) {
     return RetraceClassResultImpl.create(
-        classReference, classNameMapperProvider.getClassNaming(classReference.getTypeName()), this);
+        classReference,
+        classNameMapperSupplier.getClassNaming(diagnosticsHandler, classReference.getTypeName()),
+        this);
   }
 
   @Override
@@ -84,11 +86,12 @@
   }
 
   public Set<MapVersionMappingInformation> getMapVersions() {
-    return classNameMapperProvider.getMapVersions();
+    return classNameMapperSupplier.getMapVersions(diagnosticsHandler);
   }
 
   public String getSourceFile(ClassReference classReference) {
-    return classNameMapperProvider.getSourceFileForClass(classReference.getTypeName());
+    return classNameMapperSupplier.getSourceFileForClass(
+        diagnosticsHandler, classReference.getTypeName());
   }
 
   public static Builder builder() {
@@ -97,14 +100,14 @@
 
   public static class Builder implements RetracerBuilder {
 
-    private MappingProvider mappingProvider;
+    private MappingSupplier mappingSupplier;
     private DiagnosticsHandler diagnosticsHandler = new DiagnosticsHandler() {};
 
     private Builder() {}
 
     @Override
-    public Builder setMappingProvider(MappingProvider mappingProvider) {
-      this.mappingProvider = mappingProvider;
+    public Builder setMappingSupplier(MappingSupplier mappingSupplier) {
+      this.mappingSupplier = mappingSupplier;
       return this;
     }
 
@@ -116,7 +119,7 @@
 
     @Override
     public RetracerImpl build() {
-      return new RetracerImpl(mappingProvider, diagnosticsHandler);
+      return new RetracerImpl(mappingSupplier, diagnosticsHandler);
     }
   }
 }
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 c731f7d..689a3f0 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -16,6 +16,8 @@
 import static java.util.Collections.emptySet;
 
 import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.androidapi.CovariantReturnTypeMethods;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
@@ -72,6 +74,7 @@
 import com.android.tools.r8.graph.LookupMethodTarget;
 import com.android.tools.r8.graph.LookupResult;
 import com.android.tools.r8.graph.LookupTarget;
+import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodAccessInfoCollection;
 import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.MethodResolutionResult.FailedResolutionResult;
@@ -673,12 +676,23 @@
     return definitionFor(type, context, this::recordNonProgramClass, this::reportMissingClass);
   }
 
+  public DexLibraryClass definitionForLibraryClassOrIgnore(DexType type) {
+    assert type.isClassType();
+    ClassResolutionResult classResolutionResult =
+        appInfo().contextIndependentDefinitionForWithResolutionResult(type);
+    return classResolutionResult.hasClassResolutionResult()
+            && !classResolutionResult.isMultipleClassResolutionResult()
+        ? DexLibraryClass.asLibraryClassOrNull(
+            classResolutionResult.toSingleClassWithProgramOverLibrary())
+        : null;
+  }
+
   public boolean hasAlternativeLibraryDefinition(DexProgramClass programClass) {
     ClassResolutionResult classResolutionResult =
         internalDefinitionFor(
             programClass.type, programClass, this::recordNonProgramClass, this::reportMissingClass);
     assert classResolutionResult.hasClassResolutionResult();
-    DexClass alternativeClass = classResolutionResult.toAlternativeClassWithProgramOverLibrary();
+    DexClass alternativeClass = classResolutionResult.toAlternativeClass();
     assert alternativeClass == null || alternativeClass.isLibraryClass();
     return alternativeClass != null;
   }
@@ -772,6 +786,7 @@
         // rules.
         handleLibraryTypeInheritingFromProgramType(clazz.asLibraryClass());
       }
+      analyses.forEach(analysis -> analysis.processNewLiveNonProgramType(clazz));
       clazz.forEachClassField(
           field ->
               addNonProgramClassToWorklist(
@@ -2547,9 +2562,7 @@
       return;
     }
     DexClass alternativeResolutionResult =
-        appInfo()
-            .contextIndependentDefinitionForWithResolutionResult(type)
-            .toAlternativeClassWithProgramOverLibrary();
+        appInfo().contextIndependentDefinitionForWithResolutionResult(type).toAlternativeClass();
     if (alternativeResolutionResult != null && alternativeResolutionResult.isLibraryClass()) {
       // We are in a situation where a library class inherits from a library class, which has a
       // program class duplicated version for low API levels.
@@ -3554,9 +3567,8 @@
     includeMinimumKeepInfo(rootSet);
 
     if (mode.isInitialTreeShaking()) {
-      // This is simulating the effect of the "root set" applied rules.
-      // This is done only in the initial pass, in subsequent passes the "rules" are reapplied
-      // by iterating the instances.
+      // Amend library methods with covariant return types.
+      modelLibraryMethodsWithCovariantReturnTypes();
     } else if (appView.getKeepInfo() != null) {
       EnqueuerEvent preconditionEvent = UnconditionalKeepInfoEvent.get();
       appView
@@ -3598,6 +3610,30 @@
             this::recordDependentMinimumKeepInfo);
   }
 
+  private void modelLibraryMethodsWithCovariantReturnTypes() {
+    CovariantReturnTypeMethods.registerMethodsWithCovariantReturnType(
+        appView.dexItemFactory(),
+        method -> {
+          DexLibraryClass libraryClass =
+              DexLibraryClass.asLibraryClassOrNull(
+                  appView.appInfo().definitionForWithoutExistenceAssert(method.getHolderType()));
+          if (libraryClass == null) {
+            return;
+          }
+          // Check if the covariant method exists on the class.
+          DexEncodedMethod covariantMethod = libraryClass.lookupMethod(method);
+          if (covariantMethod != null) {
+            return;
+          }
+          libraryClass.addVirtualMethod(
+              DexEncodedMethod.builder()
+                  .setMethod(method)
+                  .setAccessFlags(MethodAccessFlags.builder().setPublic().build())
+                  .setApiLevelForDefinition(ComputedApiLevel.notSet())
+                  .build());
+        });
+  }
+
   private void applyMinimumKeepInfo(DexProgramClass clazz) {
     EnqueuerEvent preconditionEvent = UnconditionalKeepInfoEvent.get();
     KeepClassInfo.Joiner minimumKeepInfoForClass =
diff --git a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
index 85a1b2a..1932bd0 100644
--- a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.DESCRIPTOR_VIVIFIED_PREFIX;
 import static com.android.tools.r8.utils.collections.IdentityHashSetFromMap.newProgramDerivedContextSet;
 
+import com.android.tools.r8.androidapi.CovariantReturnTypeMethods;
 import com.android.tools.r8.diagnostic.MissingDefinitionsDiagnostic;
 import com.android.tools.r8.diagnostic.internal.DefinitionContextUtils;
 import com.android.tools.r8.diagnostic.internal.MissingClassInfoImpl;
@@ -292,6 +293,9 @@
                 addWithRewrittenType(
                     allowedMissingClasses, conversions.getTo().getHolderType(), appView);
               });
+      CovariantReturnTypeMethods.registerMethodsWithCovariantReturnType(
+          dexItemFactory,
+          method -> method.getReferencedTypes().forEach(allowedMissingClasses::add));
       return allowedMissingClasses.build();
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 8b4210b..cf357ed 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.shaking.ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS;
 import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptor;
 
 import com.android.tools.r8.InputDependencyGraphConsumer;
@@ -77,7 +78,7 @@
           "allowruntypeandignoreoptimizationpasses",
           "dontshrinkduringoptimization",
           "convert_proto_enum_to_string",
-          "keepkotlinmetadata");
+          "adaptkotlinmetadata");
 
   private static final List<String> IGNORED_CLASS_DESCRIPTOR_OPTIONS =
       ImmutableList.of("isclassnamestring", "whyarenotsimple");
@@ -286,12 +287,23 @@
           || parseTestingOption(optionStart)
           || parseUnsupportedOptionAndErr(optionStart)) {
         // Intentionally left empty.
-      } else if (acceptString("adaptkotlinmetadata")) {
-        reporter.info(
-            new StringDiagnostic(
-                "Ignoring -adaptkotlinmetadata because R8 always process kotlin.Metadata",
-                origin,
-                getPosition(optionStart)));
+      } else if (acceptString("keepkotlinmetadata")) {
+        configurationBuilder.addRule(
+            ProguardKeepRule.builder()
+                .setType(ProguardKeepRuleType.KEEP)
+                .setClassType(ProguardClassType.CLASS)
+                .setOrigin(origin)
+                .setStart(optionStart)
+                .setClassNames(
+                    ProguardClassNameList.builder()
+                        .addClassName(
+                            false, ProguardTypeMatcher.create(dexItemFactory.kotlinMetadataType))
+                        .build())
+                .setMemberRules(Collections.singletonList(ProguardMemberRule.defaultKeepAllRule()))
+                .setSource("-keepkotlinmetadata")
+                .build());
+        configurationBuilder.addKeepAttributePatterns(
+            Collections.singletonList(RUNTIME_VISIBLE_ANNOTATIONS));
       } else if (acceptString("renamesourcefileattribute")) {
         skipWhitespace();
         if (isOptionalArgumentGiven()) {
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 0cbbfed..97c4499 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
@@ -373,6 +373,7 @@
           DexMethod method,
           MethodResolutionResult resolutionResult,
           Function<SingleResolutionResult<?>, DexClassAndMethod> getResult) {
+        BooleanBox seenSingleResult = new BooleanBox();
         resolutionResult.forEachMethodResolutionResult(
             result -> {
               if (result.isFailedResolution()) {
@@ -382,13 +383,20 @@
                         type -> addType(type, referencedFrom),
                         methodCausingFailure ->
                             handleRewrittenMethodReference(method, methodCausingFailure));
-                if (!result.asFailedResolution().hasTypesOrMethodsCausingError()) {
-                  handleRewrittenMethodReference(method, (DexEncodedMethod) null);
-                }
                 return;
               }
+              seenSingleResult.set();
               handleRewrittenMethodReference(method, getResult.apply(result.asSingleResolution()));
             });
+        if (seenSingleResult.isFalse()) {
+          resolutionResult.forEachMethodResolutionResult(
+              failingResult -> {
+                assert failingResult.isFailedResolution();
+                if (!failingResult.asFailedResolution().hasMethodsCausingError()) {
+                  handleRewrittenMethodReference(method, (DexEncodedMethod) null);
+                }
+              });
+        }
       }
 
       private void handleRewrittenMethodReference(
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
index 374459a..189d9f9 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
@@ -4,12 +4,20 @@
 
 package com.android.tools.r8.utils;
 
+import static com.android.tools.r8.graph.DexLibraryClass.asLibraryClassOrNull;
+
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.LibraryClass;
+import com.android.tools.r8.graph.LibraryDefinition;
 import com.android.tools.r8.graph.LibraryMethod;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
@@ -95,11 +103,78 @@
     if (apiLevelOfOriginal.isUnknownApiLevel()) {
       return false;
     }
-    return apiLevelOfOriginal
-        .asKnownApiLevel()
-        .max(apiLevel)
-        .asKnownApiLevel()
-        .getApiLevel()
-        .isLessThanOrEqualTo(options.getMinApiLevel());
+    return apiLevelOfOriginal.max(apiLevel).isLessThanOrEqualTo(options.getMinApiLevel()).isTrue();
+  }
+
+  private static boolean isApiSafeForReference(LibraryDefinition definition, AppView<?> appView) {
+    return isApiSafeForReference(definition, appView.apiLevelCompute(), appView.options());
+  }
+
+  private static boolean isApiSafeForReference(
+      LibraryDefinition definition,
+      AndroidApiLevelCompute androidApiLevelCompute,
+      InternalOptions options) {
+    assert options.apiModelingOptions().enableApiCallerIdentification;
+    ComputedApiLevel apiLevel =
+        androidApiLevelCompute.computeApiLevelForLibraryReference(
+            definition.getReference(), ComputedApiLevel.unknown());
+    return apiLevel.isLessThanOrEqualTo(options.getMinApiLevel()).isTrue();
+  }
+
+  private static boolean isApiSafeForReference(
+      LibraryDefinition newDefinition, LibraryDefinition oldDefinition, AppView<?> appView) {
+    assert appView.options().apiModelingOptions().enableApiCallerIdentification;
+    assert !isApiSafeForReference(newDefinition, appView)
+        : "Clients should first check if the definition is present on all apis since the min api";
+    AndroidApiLevelCompute androidApiLevelCompute = appView.apiLevelCompute();
+    ComputedApiLevel apiLevel =
+        androidApiLevelCompute.computeApiLevelForLibraryReference(
+            newDefinition.getReference(), ComputedApiLevel.unknown());
+    if (apiLevel.isUnknownApiLevel()) {
+      return false;
+    }
+    ComputedApiLevel apiLevelOfOriginal =
+        androidApiLevelCompute.computeApiLevelForLibraryReference(
+            oldDefinition.getReference(), ComputedApiLevel.unknown());
+    return apiLevel.isLessThanOrEqualTo(apiLevelOfOriginal).isTrue();
+  }
+
+  public static boolean isApiSafeForTypeStrengthening(
+      DexType newType, DexType oldType, AppView<? extends AppInfoWithClassHierarchy> appView) {
+    // Type strengthening only applies to reference types.
+    assert newType.isReferenceType();
+    assert oldType.isReferenceType();
+    assert newType != oldType;
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    DexType newBaseType = newType.toBaseType(dexItemFactory);
+    if (newBaseType.isPrimitiveType()) {
+      // Array of primitives is available on all api levels.
+      return true;
+    }
+    assert newBaseType.isClassType();
+    DexClass newBaseClass = appView.definitionFor(newBaseType);
+    if (newBaseClass == null) {
+      // This could be a library class that is only available on newer api levels.
+      return false;
+    }
+    if (!newBaseClass.isLibraryClass()) {
+      // Program and classpath classes are not api level dependent.
+      return true;
+    }
+    if (!appView.options().apiModelingOptions().enableApiCallerIdentification) {
+      // Conservatively bail out if we don't have api modeling.
+      return false;
+    }
+    LibraryClass newBaseLibraryClass = newBaseClass.asLibraryClass();
+    if (isApiSafeForReference(newBaseLibraryClass, appView)) {
+      // Library class is present on all api levels since min api.
+      return true;
+    }
+    // Check if the new library class is present since the api level of the old type.
+    DexType oldBaseType = oldType.toBaseType(dexItemFactory);
+    assert oldBaseType.isClassType();
+    LibraryClass oldBaseLibraryClass = asLibraryClassOrNull(appView.definitionFor(oldBaseType));
+    return oldBaseLibraryClass != null
+        && isApiSafeForReference(newBaseLibraryClass, oldBaseLibraryClass, appView);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
index 292f103..17b3673 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.R8Command.Builder;
 import java.io.IOException;
+import java.lang.reflect.Method;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -22,6 +23,7 @@
 import java.util.Map;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.function.Consumer;
 
 /**
  * Wrapper to make it easy to call R8 in compat mode when compiling a dump file.
@@ -36,7 +38,13 @@
 public class CompileDumpCompatR8 {
 
   private static final List<String> VALID_OPTIONS =
-      Arrays.asList("--classfile", "--compat", "--debug", "--release");
+      Arrays.asList(
+          "--classfile",
+          "--compat",
+          "--debug",
+          "--release",
+          "--enable-missing-library-api-modeling",
+          "--android-platform-build");
 
   private static final List<String> VALID_OPTIONS_WITH_SINGLE_OPERAND =
       Arrays.asList(
@@ -78,6 +86,8 @@
     List<Path> mainDexRulesFiles = new ArrayList<>();
     int minApi = 1;
     int threads = -1;
+    boolean enableMissingLibraryApiModeling = false;
+    boolean androidPlatformBuild = false;
     for (int i = 0; i < args.length; i++) {
       String option = args[i];
       if (VALID_OPTIONS.contains(option)) {
@@ -102,6 +112,12 @@
               compilationMode = CompilationMode.RELEASE;
               break;
             }
+          case "--enable-missing-library-api-modeling":
+            enableMissingLibraryApiModeling = true;
+            break;
+          case "--android-platform-build":
+            androidPlatformBuild = true;
+            break;
           default:
             throw new IllegalArgumentException("Unimplemented option: " + option);
         }
@@ -187,6 +203,11 @@
             .addMainDexRulesFiles(mainDexRulesFiles)
             .setOutput(outputPath, outputMode)
             .setMode(compilationMode);
+    getReflectiveBuilderMethod(
+            commandBuilder, "setEnableExperimentalMissingLibraryApiModeling", boolean.class)
+        .accept(new Object[] {enableMissingLibraryApiModeling});
+    getReflectiveBuilderMethod(commandBuilder, "setAndroidPlatformBuild", boolean.class)
+        .accept(new Object[] {androidPlatformBuild});
     if (desugaredLibJson != null) {
       commandBuilder.addDesugaredLibraryConfiguration(readAllBytesJava7(desugaredLibJson));
     }
@@ -217,6 +238,24 @@
     }
   }
 
+  private static Consumer<Object[]> getReflectiveBuilderMethod(
+      Builder builder, String setter, Class<?>... parameters) {
+    try {
+      Method declaredMethod = CompatProguardCommandBuilder.class.getMethod(setter, parameters);
+      return args -> {
+        try {
+          declaredMethod.invoke(builder, args);
+        } catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      };
+    } catch (NoSuchMethodException e) {
+      e.printStackTrace();
+      // The option is not available so we just return an empty consumer
+      return args -> {};
+    }
+  }
+
   // We cannot use StringResource since this class is added to the class path and has access only
   // to the public APIs.
   private static String readAllBytesJava7(Path filePath) {
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 c16b39f..37a55bb 100644
--- a/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
+++ b/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
@@ -115,7 +115,7 @@
   /** The joining of state during backtracking of the algorithm. */
   abstract TraversalContinuation<TB, TC> internalOnJoin(T node);
 
-  abstract List<TC> getFinalStateForRoots(Collection<N> roots);
+  protected abstract List<TC> getFinalStateForRoots(Collection<N> roots);
 
   final T internalEnqueueNode(N value) {
     T dfsNode = nodeToNodeWithStateMap.computeIfAbsent(value, this::createDfsNode);
@@ -190,8 +190,18 @@
 
     @Override
     protected TraversalContinuation<TB, TC> internalOnJoin(DFSNodeImpl<N> node) {
+      return joiner(node);
+    }
+
+    public TraversalContinuation<TB, TC> joiner(DFSNode<N> node) {
+      // Override to be notified during callback.
       return TraversalContinuation.doContinue();
     }
+
+    @Override
+    protected List<TC> getFinalStateForRoots(Collection<N> roots) {
+      return null;
+    }
   }
 
   public abstract static class StatefulDepthFirstSearchWorkList<N, S, TB>
@@ -254,7 +264,7 @@
     }
 
     @Override
-    List<S> getFinalStateForRoots(Collection<N> roots) {
+    public 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/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index ee15be2..e74dfd9 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.utils;
 
 import static com.android.tools.r8.utils.AndroidApiLevel.ANDROID_PLATFORM;
+import static com.android.tools.r8.utils.AndroidApiLevel.B;
 import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault;
 
 import com.android.tools.r8.ClassFileConsumer;
@@ -336,7 +337,6 @@
   public boolean enableTreeShakingOfLibraryMethodOverrides = false;
   public boolean encodeChecksums = false;
   public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
-  public boolean cfToCfDesugar = false;
   public boolean forceAnnotateSynthetics = false;
   public boolean readDebugSetFileEvent = false;
   public boolean disableL8AnnotationRemoval =
@@ -518,12 +518,12 @@
   }
 
   public boolean shouldBackportMethods() {
-    return !hasConsumer() || isGeneratingDex() || cfToCfDesugar;
+    return !hasConsumer() || isGeneratingDex() || isCfDesugaring();
   }
 
   public boolean shouldKeepStackMapTable() {
-    assert cfToCfDesugar || isRelocatorCompilation() || getProguardConfiguration() != null;
-    return cfToCfDesugar
+    assert isCfDesugaring() || isRelocatorCompilation() || getProguardConfiguration() != null;
+    return isCfDesugaring()
         || isRelocatorCompilation()
         || getProguardConfiguration().getKeepAttributes().stackMapTable;
   }
@@ -549,7 +549,11 @@
   }
 
   public boolean isDesugaring() {
-    return !isGeneratingClassFiles() || cfToCfDesugar;
+    return desugarState.isOn();
+  }
+
+  public boolean isCfDesugaring() {
+    return isGeneratingClassFiles() && desugarState.isOn();
   }
 
   public DexIndexedConsumer getDexIndexedConsumer() {
@@ -646,6 +650,10 @@
   private final boolean enableMinification;
 
   public AndroidApiLevel getMinApiLevel() {
+    // If compiling to CF with no desugaring then we should not inspect the min-api.
+    // For now we assert the API level for non-desugared CF is B, but it would be better to never
+    // access the min-api in those cases.
+    assert desugarState.isOn() || isGeneratingDex() || minApiLevel.equals(AndroidApiLevel.B);
     return minApiLevel;
   }
 
@@ -800,6 +808,7 @@
 
   private final CallSiteOptimizationOptions callSiteOptimizationOptions =
       new CallSiteOptimizationOptions();
+  private final CfCodeAnalysisOptions cfCodeAnalysisOptions = new CfCodeAnalysisOptions();
   private final ClassInlinerOptions classInlinerOptions = new ClassInlinerOptions();
   private final InlinerOptions inlinerOptions = new InlinerOptions();
   private final HorizontalClassMergerOptions horizontalClassMergerOptions =
@@ -859,6 +868,10 @@
     return desugarSpecificOptions;
   }
 
+  public CfCodeAnalysisOptions getCfCodeAnalysisOptions() {
+    return cfCodeAnalysisOptions;
+  }
+
   public OpenClosedInterfacesOptions getOpenClosedInterfacesOptions() {
     return openClosedInterfacesOptions;
   }
@@ -937,7 +950,7 @@
   // If non null it must be and passed to the consumer.
   public StringConsumer mainDexListConsumer = null;
 
-  // If null, no proguad map needs to be computed.
+  // If null, no proguard map needs to be computed.
   // If non null it must be and passed to the consumer.
   public StringConsumer proguardMapConsumer = null;
 
@@ -1391,6 +1404,31 @@
     }
   }
 
+  public static class CfCodeAnalysisOptions {
+
+    private boolean allowUnreachableCfBlocks = true;
+    private boolean enableUnverifiableCodeReporting = false;
+
+    public boolean isUnverifiableCodeReportingEnabled() {
+      return enableUnverifiableCodeReporting;
+    }
+
+    public boolean isUnreachableCfBlocksAllowed() {
+      return allowUnreachableCfBlocks;
+    }
+
+    public CfCodeAnalysisOptions setAllowUnreachableCfBlocks(boolean allowUnreachableCfBlocks) {
+      this.allowUnreachableCfBlocks = allowUnreachableCfBlocks;
+      return this;
+    }
+
+    public CfCodeAnalysisOptions setEnableUnverifiableCodeReporting(
+        boolean enableUnverifiableCodeReporting) {
+      this.enableUnverifiableCodeReporting = enableUnverifiableCodeReporting;
+      return this;
+    }
+  }
+
   public class ClassInlinerOptions {
 
     public int classInliningInstructionAllowance = -1;
@@ -1973,6 +2011,36 @@
   }
 
   /**
+   * Predicate to guard on the support of a language feature.
+   *
+   * <p>Note that if not desugaring or compiling to DEX, then the output is a mapping of the input
+   * and thus all parts should be representable (assuming the compiler has support for them).
+   */
+  private boolean hasFeaturePresentFrom(AndroidApiLevel level) {
+    if (desugarState.isOn() || isGeneratingDex()) {
+      return level != null && hasMinApi(level);
+    }
+    // If not desugaring and not compiling to DEX, then the API level is effectively ignored and
+    // we assume that everything in the input is supported in the output.
+    assert minApiLevel.equals(B);
+    return true;
+  }
+
+  /**
+   * Predicate to guard against the possible presence of a VM bug.
+   *
+   * <p>Note that if the compilation is not desugaring to a min-api or targeting DEX at a min-api,
+   * then the bug is assumed to be present as the CF output could be futher compiled to any target.
+   */
+  private boolean canHaveBugPresentUntil(AndroidApiLevel level) {
+    if (desugarState.isOn() || isGeneratingDex()) {
+      return level == null || !hasMinApi(level);
+    }
+    assert minApiLevel.equals(B);
+    return true;
+  }
+
+  /**
    * Allow access modification of synthetic lambda implementation methods in D8 to avoid generating
    * an excessive amount of accessibility bridges. In R8, the lambda implementation methods are
    * inlined into the synthesized accessibility bridges, thus we don't allow access modification.
@@ -2008,47 +2076,52 @@
   }
 
   public boolean canUseInvokePolymorphicOnVarHandle() {
-    return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.P);
+    return hasFeaturePresentFrom(AndroidApiLevel.P);
   }
 
   public boolean canUseInvokePolymorphic() {
-    return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.O);
+    return hasFeaturePresentFrom(AndroidApiLevel.O);
   }
 
   public boolean canUseConstantMethodHandle() {
-    return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.P);
+    return hasFeaturePresentFrom(AndroidApiLevel.P);
   }
 
   public boolean canUseConstantMethodType() {
-    return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.P);
+    return hasFeaturePresentFrom(AndroidApiLevel.P);
   }
 
   public boolean canUseInvokeCustom() {
-    return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.O);
+    return hasFeaturePresentFrom(AndroidApiLevel.O);
   }
 
   public boolean canUseDefaultAndStaticInterfaceMethods() {
-    return !isDesugaring() || hasMinApi(AndroidApiLevel.N);
+    return hasFeaturePresentFrom(AndroidApiLevel.N);
   }
 
   public boolean canUseNestBasedAccess() {
-    return !isDesugaring() || emitNestAnnotationsInDex;
+    return hasFeaturePresentFrom(null) || emitNestAnnotationsInDex;
   }
 
   public boolean canUseRecords() {
-    return !isDesugaring();
+    return hasFeaturePresentFrom(null);
   }
 
   public boolean canUseSealedClasses() {
-    return !isDesugaring() || emitPermittedSubclassesAnnotationsInDex;
+    return hasFeaturePresentFrom(null) || emitPermittedSubclassesAnnotationsInDex;
   }
 
   public boolean canLeaveStaticInterfaceMethodInvokes() {
-    return !isDesugaring() || hasMinApi(AndroidApiLevel.L);
+    return hasFeaturePresentFrom(AndroidApiLevel.L);
   }
 
   public boolean canUseTwrCloseResourceMethod() {
-    return !isDesugaring() || hasMinApi(AndroidApiLevel.K);
+    return hasFeaturePresentFrom(AndroidApiLevel.K);
+  }
+
+  public boolean canUseSpacesInSimpleName() {
+    return itemFactory.getSkipNameValidationForTesting()
+        || hasFeaturePresentFrom(AndroidApiLevel.R);
   }
 
   public boolean enableBackportedMethodRewriting() {
@@ -2070,7 +2143,7 @@
   }
 
   public boolean canUsePrivateInterfaceMethods() {
-    return !isDesugaring() || hasMinApi(AndroidApiLevel.N);
+    return hasFeaturePresentFrom(AndroidApiLevel.N);
   }
 
   // Debug entries may be dropped only if the source file content allows being omitted from
@@ -2095,7 +2168,7 @@
     if (!hasConsumer()) {
       return false;
     }
-    return desugarState == DesugarState.ON
+    return desugarState.isOn()
         && interfaceMethodDesugaring == OffOrAuto.Auto
         && !canUseDefaultAndStaticInterfaceMethods();
   }
@@ -2114,23 +2187,24 @@
   }
 
   public boolean canUseJavaUtilObjects() {
-    return !isDesugaring() || hasMinApi(AndroidApiLevel.K);
+    return hasFeaturePresentFrom(AndroidApiLevel.K);
   }
 
   public boolean canUseJavaUtilObjectsIsNull() {
-    return isGeneratingDex() && hasMinApi(AndroidApiLevel.N);
+    return hasFeaturePresentFrom(AndroidApiLevel.N);
   }
 
   public boolean canUseSuppressedExceptions() {
     // TODO(b/214239152): Suppressed exceptions are @hide from at least 4.0.1 / Android I / API 14.
-    return !isDesugaring() || hasMinApi(AndroidApiLevel.K);
+    return hasFeaturePresentFrom(AndroidApiLevel.K);
   }
 
   public boolean canUseAssertionErrorTwoArgumentConstructor() {
-    return !isDesugaring() || hasMinApi(AndroidApiLevel.K);
+    return hasFeaturePresentFrom(AndroidApiLevel.K);
   }
 
   public CfVersion classFileVersionAfterDesugaring(CfVersion version) {
+    assert isGeneratingClassFiles();
     if (!isDesugaring()) {
       return version;
     }
@@ -2146,7 +2220,7 @@
   //
   // https://android.googlesource.com/platform/libcore/+/refs/heads/ics-mr1/luni/src/main/java/java/lang/AssertionError.java#56
   public boolean canInitCauseAfterAssertionErrorObjectConstructor() {
-    return !isDesugaring() || hasMinApi(AndroidApiLevel.J);
+    return hasFeaturePresentFrom(AndroidApiLevel.J);
   }
 
   // Dalvik x86-atom backend had a bug that made it crash on filled-new-array instructions for
@@ -2159,7 +2233,7 @@
   // https://android.googlesource.com/platform/dalvik/+/ics-mr0/vm/mterp/out/InterpAsm-x86-atom.S#25106
   public boolean canUseFilledNewArrayOfObjects() {
     assert isGeneratingDex();
-    return hasMinApi(AndroidApiLevel.K);
+    return hasFeaturePresentFrom(AndroidApiLevel.K);
   }
 
   // Art had a bug (b/68761724) for Android N and O in the arm32 interpreter
@@ -2167,7 +2241,7 @@
   // and the first register of the result could lead to the wrong exception
   // being thrown on out of bounds.
   public boolean canUseSameArrayAndResultRegisterInArrayGetWide() {
-    return isGeneratingClassFiles() || getMinApiLevel().isGreaterThan(AndroidApiLevel.O_MR1);
+    return hasFeaturePresentFrom(AndroidApiLevel.P);
   }
 
   // Some Lollipop versions of Art found in the wild perform invalid bounds
@@ -2184,7 +2258,7 @@
   //
   // See b/69364976 and b/77996377.
   public boolean canHaveBoundsCheckEliminationBug() {
-    return isGeneratingDex() && getMinApiLevel().isLessThan(AndroidApiLevel.M);
+    return canHaveBugPresentUntil(AndroidApiLevel.M);
   }
 
   // MediaTek JIT compilers for KitKat phones did not implement the not
@@ -2193,14 +2267,14 @@
   // we can only use not instructions if we are targeting Art-based
   // phones.
   public boolean canUseNotInstruction() {
-    return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.L);
+    return hasFeaturePresentFrom(AndroidApiLevel.L);
   }
 
   // Art before M has a verifier bug where the type of the contents of the receiver register is
   // assumed to not change. If the receiver register is reused for something else the verifier
   // will fail and the code will not run.
   public boolean canHaveThisTypeVerifierBug() {
-    return isGeneratingDex() && getMinApiLevel().isLessThan(AndroidApiLevel.M);
+    return canHaveBugPresentUntil(AndroidApiLevel.M);
   }
 
   // Art crashes if we do dead reference elimination of the receiver in release mode and Art
@@ -2209,13 +2283,13 @@
   //
   // See b/116683601 and b/116837585.
   public boolean canHaveThisJitCodeDebuggingBug() {
-    return getMinApiLevel().isLessThan(AndroidApiLevel.Q);
+    return canHaveBugPresentUntil(AndroidApiLevel.Q);
   }
 
   // The dalvik jit had a bug where the long operations add, sub, or, xor and and would write
   // the first part of the result long before reading the second part of the input longs.
   public boolean canHaveOverlappingLongRegisterBug() {
-    return isGeneratingDex() && getMinApiLevel().isLessThan(AndroidApiLevel.L);
+    return canHaveBugPresentUntil(AndroidApiLevel.L);
   }
 
   // Some dalvik versions found in the wild perform invalid JIT compilation of cmp-long
@@ -2248,7 +2322,7 @@
   //
   // See b/75408029.
   public boolean canHaveCmpLongBug() {
-    return isGeneratingDex() && getMinApiLevel().isLessThan(AndroidApiLevel.L);
+    return canHaveBugPresentUntil(AndroidApiLevel.L);
   }
 
   // Some Lollipop VMs crash if there is a const instruction between a cmp and an if instruction.
@@ -2276,7 +2350,7 @@
   //
   // See b/115552239.
   public boolean canHaveCmpIfFloatBug() {
-    return getMinApiLevel().isLessThan(AndroidApiLevel.M);
+    return canHaveBugPresentUntil(AndroidApiLevel.M);
   }
 
   // Some Lollipop VMs incorrectly optimize code with mul2addr instructions. In particular,
@@ -2298,7 +2372,7 @@
   //
   // This issue has only been observed on a Verizon Ellipsis 8 tablet. See b/76115465.
   public boolean canHaveMul2AddrBug() {
-    return isGeneratingDex() && getMinApiLevel().isLessThan(AndroidApiLevel.M);
+    return canHaveBugPresentUntil(AndroidApiLevel.M);
   }
 
   // Some Marshmallow VMs create an incorrect doubly-linked list of instructions. When the VM
@@ -2307,7 +2381,7 @@
   //
   // See b/77842465.
   public boolean canHaveDex2OatLinkedListBug() {
-    return isGeneratingDex() && getMinApiLevel().isLessThan(AndroidApiLevel.N);
+    return canHaveBugPresentUntil(AndroidApiLevel.N);
   }
 
   // dex2oat on Marshmallow VMs does aggressive inlining which can eat up all the memory on
@@ -2315,7 +2389,7 @@
   //
   // See b/111960171
   public boolean canHaveDex2OatInliningIssue() {
-    return isGeneratingDex() && getMinApiLevel().isLessThan(AndroidApiLevel.N);
+    return canHaveBugPresentUntil(AndroidApiLevel.N);
   }
 
   // Art 7.0.0 and later Art JIT may perform an invalid optimization if a string new-instance does
@@ -2323,7 +2397,7 @@
   //
   // See b/78493232 and b/80118070.
   public boolean canHaveArtStringNewInitBug() {
-    return isGeneratingDex() && getMinApiLevel().isLessThan(AndroidApiLevel.Q);
+    return canHaveBugPresentUntil(AndroidApiLevel.Q);
   }
 
   // Dalvik tracing JIT may perform invalid optimizations when int/float values are converted to
@@ -2331,7 +2405,7 @@
   //
   // See b/77496850.
   public boolean canHaveNumberConversionRegisterAllocationBug() {
-    return isGeneratingDex() && getMinApiLevel().isLessThan(AndroidApiLevel.L);
+    return canHaveBugPresentUntil(AndroidApiLevel.L);
   }
 
   // Some Lollipop mediatek VMs have a peculiar bug where the inliner crashes if there is a
@@ -2344,7 +2418,7 @@
   //
   // See b/68378480.
   public boolean canHaveForwardingInitInliningBug() {
-    return isGeneratingDex() && getMinApiLevel().isLessThan(AndroidApiLevel.M);
+    return canHaveBugPresentUntil(AndroidApiLevel.M);
   }
 
   // Some Lollipop x86_64 VMs have a bug causing a segfault if an exception handler directly targets
@@ -2356,7 +2430,8 @@
   //
   // See b/111337896.
   public boolean canHaveExceptionTargetingLoopHeaderBug() {
-    return isGeneratingDex() && !debug && getMinApiLevel().isLessThan(AndroidApiLevel.M);
+    assert isGeneratingDex();
+    return !debug && canHaveBugPresentUntil(AndroidApiLevel.M);
   }
 
   // The Dalvik tracing JIT can trace past the end of the instruction stream and end up
@@ -2371,7 +2446,7 @@
   // We also could not insert any dead code (e.g. a return) because that would make mediatek
   // dominator calculations on 7.0.0 crash. See b/128926846.
   public boolean canHaveTracingPastInstructionsStreamBug() {
-    return getMinApiLevel().isLessThan(AndroidApiLevel.L);
+    return canHaveBugPresentUntil(AndroidApiLevel.L);
   }
 
   // The art verifier incorrectly propagates type information for the following pattern:
@@ -2398,7 +2473,7 @@
   //
   // Fixed in Android Q, see b/120985556.
   public boolean canHaveArtInstanceOfVerifierBug() {
-    return isGeneratingDex() && getMinApiLevel().isLessThan(AndroidApiLevel.Q);
+    return canHaveBugPresentUntil(AndroidApiLevel.Q);
   }
 
   // Some Art Lollipop version do not deal correctly with long-to-int conversions.
@@ -2421,7 +2496,7 @@
   public boolean canHaveLongToIntBug() {
     // We have only seen this happening on Lollipop arm64 backends. We have tested on
     // Marshmallow and Nougat arm64 devices and they do not have the bug.
-    return getMinApiLevel().isLessThan(AndroidApiLevel.M);
+    return canHaveBugPresentUntil(AndroidApiLevel.M);
   }
 
   // The Art VM for Android N through P has a bug in the JIT that means that if the same
@@ -2434,7 +2509,7 @@
   //
   // See b/120164595.
   public boolean canHaveExceptionTypeBug() {
-    return getMinApiLevel().isLessThan(AndroidApiLevel.Q);
+    return canHaveBugPresentUntil(AndroidApiLevel.Q);
   }
 
   // Art 4.0.4 fails with a verification error when a null-literal is being passed directly to an
@@ -2442,7 +2517,7 @@
   // elimination of check-cast instructions where the value being cast is the constant null.
   // See b/123269162.
   public boolean canHaveArtCheckCastVerifierBug() {
-    return getMinApiLevel().isLessThan(AndroidApiLevel.J);
+    return canHaveBugPresentUntil(AndroidApiLevel.J);
   }
 
   // The verifier will merge A[] and B[] to Object[], even when both A and B implement an interface
@@ -2466,7 +2541,7 @@
   //
   // See b/131349148
   public boolean canHaveDalvikCatchHandlerVerificationBug() {
-    return isGeneratingClassFiles() || getMinApiLevel().isLessThan(AndroidApiLevel.L);
+    return canHaveBugPresentUntil(AndroidApiLevel.L);
   }
 
   // Having an invoke instruction that targets an abstract method on a non-abstract class will fail
@@ -2474,7 +2549,7 @@
   //
   // See b/132953944.
   public boolean canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug() {
-    return isGeneratingDex() && getMinApiLevel().isLessThan(AndroidApiLevel.L);
+    return canHaveBugPresentUntil(AndroidApiLevel.L);
   }
 
   // On dalvik we see issues when using an int value in places where a boolean, byte, char, or short
@@ -2488,14 +2563,14 @@
   //
   // See also b/134304597 and b/124152497.
   public boolean canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug() {
-    return isGeneratingClassFiles() || getMinApiLevel().isLessThan(AndroidApiLevel.L);
+    return canHaveBugPresentUntil(AndroidApiLevel.L);
   }
 
   // The standard library prior to API 19 did not contain a ZipFile that implemented Closable.
   //
   // See b/177532008.
   public boolean canHaveZipFileWithMissingCloseableBug() {
-    return isGeneratingClassFiles() || getMinApiLevel().isLessThan(AndroidApiLevel.K);
+    return canHaveBugPresentUntil(AndroidApiLevel.K);
   }
 
   // Some versions of Dalvik had a bug where a switch with a MAX_INT key would still go to
@@ -2503,7 +2578,7 @@
   //
   // See b/177790310.
   public boolean canHaveSwitchMaxIntBug() {
-    return isGeneratingDex() && getMinApiLevel().isLessThan(AndroidApiLevel.K);
+    return canHaveBugPresentUntil(AndroidApiLevel.K);
   }
 
   // On Dalvik the methods Integer.parseInt and Long.parseLong does not support strings with a '+'
@@ -2511,7 +2586,7 @@
   //
   // See b/182137865.
   public boolean canParseNumbersWithPlusPrefix() {
-    return getMinApiLevel().isGreaterThan(AndroidApiLevel.K);
+    return hasFeaturePresentFrom(AndroidApiLevel.L);
   }
 
   // Lollipop and Marshmallow devices do not correctly handle invoke-super when the static holder
@@ -2519,7 +2594,7 @@
   //
   // See b/215573892.
   public boolean canHaveSuperInvokeBug() {
-    return getMinApiLevel().isLessThan(AndroidApiLevel.N);
+    return canHaveBugPresentUntil(AndroidApiLevel.N);
   }
 
   // Some Dalvik and Art MVs does not support interface invokes to Object
@@ -2535,12 +2610,12 @@
   //
   // See b/218298666.
   public boolean canHaveInvokeInterfaceToObjectMethodBug() {
-    return isGeneratingDex() && getMinApiLevel().isLessThan(AndroidApiLevel.P);
+    return canHaveBugPresentUntil(AndroidApiLevel.P);
   }
 
   // Until we fully drop support for API levels < 16, we have to emit an empty annotation set to
   // work around a DALVIK bug. See b/36951668.
   public boolean canHaveDalvikEmptyAnnotationSetBug() {
-    return minApiLevel.isLessThan(AndroidApiLevel.J_MR1);
+    return canHaveBugPresentUntil(AndroidApiLevel.J_MR1);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/MapUtils.java b/src/main/java/com/android/tools/r8/utils/MapUtils.java
index bf80d7a..94b9bfe 100644
--- a/src/main/java/com/android/tools/r8/utils/MapUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapUtils.java
@@ -9,6 +9,7 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMaps;
 import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.function.BiFunction;
 import java.util.function.BiPredicate;
 import java.util.function.Function;
@@ -100,4 +101,19 @@
         });
     return result;
   }
+
+  public static <K, V> boolean equals(Map<K, V> one, Map<K, V> other) {
+    if (one == other) {
+      return true;
+    }
+    if (one.size() != other.size()) {
+      return false;
+    }
+    for (Entry<K, V> firstEntry : one.entrySet()) {
+      if (!firstEntry.getValue().equals(other.get(firstEntry.getKey()))) {
+        return false;
+      }
+    }
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/TraversalUtils.java b/src/main/java/com/android/tools/r8/utils/TraversalUtils.java
index 25e1be0..627ee79 100644
--- a/src/main/java/com/android/tools/r8/utils/TraversalUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/TraversalUtils.java
@@ -6,6 +6,8 @@
 
 import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
 
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -18,6 +20,16 @@
     return traversal.apply(TraversalContinuation::doBreak).asBreak().getValue();
   }
 
+  public static <BT, CT> boolean hasNext(
+      Consumer<Function<CT, TraversalContinuation<BT, CT>>> traversal) {
+    return !isEmpty(traversal);
+  }
+
+  public static <BT, CT> boolean isEmpty(
+      Consumer<Function<CT, TraversalContinuation<BT, CT>>> traversal) {
+    return isSizeExactly(traversal, 0);
+  }
+
   public static <BT, CT> boolean isSingleton(
       Consumer<Function<CT, TraversalContinuation<BT, CT>>> traversal) {
     return isSizeExactly(traversal, 1);
@@ -54,4 +66,23 @@
     }
     return traversalContinuation;
   }
+
+  public static <S, T, BT, CT> TraversalContinuation<BT, CT> traverseMap(
+      Map<S, T> map,
+      TriFunction<? super S, ? super T, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    TraversalContinuation<BT, CT> traversalContinuation =
+        TraversalContinuation.doContinue(initialValue);
+    for (Entry<S, T> entry : map.entrySet()) {
+      traversalContinuation =
+          fn.apply(
+              entry.getKey(),
+              entry.getValue(),
+              traversalContinuation.asContinue().getValueOrDefault(null));
+      if (traversalContinuation.isBreak()) {
+        break;
+      }
+    }
+    return traversalContinuation;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/UnverifiableCfCodeDiagnostic.java b/src/main/java/com/android/tools/r8/utils/UnverifiableCfCodeDiagnostic.java
new file mode 100644
index 0000000..e3aac9b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/UnverifiableCfCodeDiagnostic.java
@@ -0,0 +1,48 @@
+// 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 com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.references.MethodReference;
+
+@Keep
+public class UnverifiableCfCodeDiagnostic implements Diagnostic {
+
+  private final MethodReference methodReference;
+  private final int instructionIndex;
+  private final String message;
+  private final Origin origin;
+
+  public UnverifiableCfCodeDiagnostic(
+      MethodReference methodReference, int instructionIndex, String message, Origin origin) {
+    this.methodReference = methodReference;
+    this.instructionIndex = instructionIndex;
+    this.message = message;
+    this.origin = origin;
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  @Override
+  public Position getPosition() {
+    return Position.UNKNOWN;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return "Unverifiable code in `"
+        + MethodReferenceUtils.toSourceString(methodReference)
+        + "` at instruction "
+        + instructionIndex
+        + ": "
+        + message
+        + ".";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
index 8292f14..ebba98d 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
@@ -77,5 +77,9 @@
         .removeIf(entry -> predicate.test(entry.getKey().get(), entry.getValue()));
   }
 
+  public int size() {
+    return backing.size();
+  }
+
   abstract Wrapper<K> wrap(K member);
 }
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index c592df2..aebb5a6 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -3,9 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.R8CommandTest.getOutputPath;
 import static com.android.tools.r8.ToolHelper.EXAMPLES_BUILD_DIR;
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -699,6 +701,36 @@
   }
 
   @Test
+  public void desugaredLibraryWithOutputConf() throws CompilationFailedException, IOException {
+    Path pgout = temp.getRoot().toPath().resolve("pgout.conf");
+    D8Command d8Command =
+        parse(
+            "--desugared-lib",
+            "src/library_desugar/desugar_jdk_libs.json",
+            "--lib",
+            ToolHelper.getAndroidJar(AndroidApiLevel.R).toString(),
+            "--desugared-lib-pg-conf-output",
+            pgout.toString());
+    InternalOptions options = getOptionsWithLoadedDesugaredLibraryConfiguration(d8Command, false);
+    assertFalse(options.machineDesugaredLibrarySpecification.getRewriteType().isEmpty());
+  }
+
+  @Test
+  public void desugaredLibraryWithOutputConfMissingArg() {
+    TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
+    try {
+      parse(
+          diagnostics,
+          "--desugared-lib",
+          "src/library_desugar/desugar_jdk_libs.json",
+          "--desugared-lib-pg-conf-output");
+      fail("Expected parse error");
+    } catch (CompilationFailedException e) {
+      diagnostics.assertErrorsMatch(diagnosticMessage(containsString("Missing parameter")));
+    }
+  }
+
+  @Test
   public void pgInputMap() throws CompilationFailedException, IOException, ResourceException {
     Path mapFile = temp.newFile().toPath();
     FileUtils.writeTextFile(
diff --git a/src/test/java/com/android/tools/r8/L8TestBuilder.java b/src/test/java/com/android/tools/r8/L8TestBuilder.java
index c9f0ee8..3186dc1 100644
--- a/src/test/java/com/android/tools/r8/L8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/L8TestBuilder.java
@@ -89,6 +89,11 @@
     return this;
   }
 
+  public L8TestBuilder addKeepRules(String keepRule) throws IOException {
+    this.keepRules.add(keepRule);
+    return this;
+  }
+
   public L8TestBuilder addKeepRuleFile(Path keepRuleFile) throws IOException {
     this.keepRules.add(FileUtils.readTextFile(keepRuleFile, StandardCharsets.UTF_8));
     return this;
diff --git a/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
index 1d45cb1..79b8799 100644
--- a/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
@@ -58,11 +58,6 @@
 
     @Override
     void run() throws Throwable {
-      boolean expectedToThrow = minSdkErrorExpectedCf(testName);
-      if (expectedToThrow) {
-        thrown.expect(ApiLevelException.class);
-      }
-
       String qualifiedMainClass = packageName + "." + mainClass;
       Path inputFile = getInputJar();
       Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
@@ -82,10 +77,6 @@
       }
 
       execute(testName, qualifiedMainClass, new Path[] {inputFile}, new Path[] {out}, args);
-
-      if (expectedToThrow) {
-        System.out.println("Did not throw ApiLevelException as expected");
-      }
     }
 
     @Override
@@ -138,13 +129,4 @@
     System.out.println(testName + " " + expectedFailures.contains(testName));
     return expectedFailures.contains(testName);
   }
-
-  private static List<String> minSdkErrorExpected =
-      ImmutableList.of(
-      );
-
-  private boolean minSdkErrorExpectedCf(String testName) {
-    System.out.println(testName);
-    return minSdkErrorExpected.contains(testName);
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index d7a6cc2..8b4c482 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.benchmarks.BenchmarkResults;
 import com.android.tools.r8.debug.DebugTestConfig;
-import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorEventConsumer;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
 import com.android.tools.r8.testing.AndroidBuildVersion;
@@ -59,6 +58,10 @@
         options.testing.reportUnusedProguardConfigurationRules = true;
         options.horizontalClassMergerOptions().enable();
         options.horizontalClassMergerOptions().setEnableInterfaceMerging();
+        options
+            .getCfCodeAnalysisOptions()
+            .setAllowUnreachableCfBlocks(false)
+            .setEnableUnverifiableCodeReporting(true);
         options.getOpenClosedInterfacesOptions().disallowOpenInterfaces();
       };
 
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index 847f4ae..40a27ee 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -102,10 +102,18 @@
     return apiLevel;
   }
 
+  public Path getDefaultAndroidJar() {
+    assert isDexRuntime();
+    return ToolHelper.getFirstSupportedAndroidJar(getApiLevel());
+  }
+
+  public Path getDefaultAndroidJarAbove(AndroidApiLevel minimumCompileApiLevel) {
+    assert isDexRuntime();
+    return ToolHelper.getFirstSupportedAndroidJar(getApiLevel().max(minimumCompileApiLevel));
+  }
+
   public Path getDefaultRuntimeLibrary() {
-    return isCfRuntime()
-        ? ToolHelper.getJava8RuntimeJar()
-        : ToolHelper.getFirstSupportedAndroidJar(getApiLevel());
+    return isCfRuntime() ? ToolHelper.getJava8RuntimeJar() : getDefaultAndroidJar();
   }
 
   // Access to underlying runtime/wrapper.
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 58e5901..bf47b6e 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -176,7 +176,7 @@
   }
 
   public T addKeepKotlinMetadata() {
-    return addKeepRules("-keep class kotlin.Metadata { *; }");
+    return addKeepRules("-keepkotlinmetadata");
   }
 
   public T addKeepAllClassesRule() {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index b22d6d3..4330b97 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -15,7 +15,9 @@
 import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.ToolHelper.DexVm.Kind;
 import com.android.tools.r8.benchmarks.BenchmarkResults;
+import com.android.tools.r8.desugar.desugaredlibrary.jdk11.ConversionConverter;
 import com.android.tools.r8.desugar.desugaredlibrary.jdk11.DesugaredLibraryJDK11Undesugarer;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.CustomConversionVersion;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
@@ -187,10 +189,13 @@
   public static final Path DESUGARED_JDK_11_LIB_JAR =
       Paths.get(OPEN_JDK_DIR + "desugar_jdk_libs_11/desugar_jdk_libs.jar");
 
+  public static Path getConvertedDesugaredLibConversions(CustomConversionVersion legacy) {
+    return ConversionConverter.convertJar(DESUGAR_LIB_CONVERSIONS, legacy);
+  }
+
   public static Path getUndesugaredJdk11LibJarForTesting() {
     return DesugaredLibraryJDK11Undesugarer.undesugaredJarJDK11(
-        Paths.get("build/libs"),
-        Paths.get(OPEN_JDK_DIR + "desugar_jdk_libs_11/desugar_jdk_libs.jar"));
+        Paths.get("build/libs"), DESUGARED_JDK_11_LIB_JAR);
   }
 
   public static boolean isLocalDevelopment() {
diff --git a/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java b/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
index db640a2..cf867dd 100644
--- a/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
@@ -21,13 +21,16 @@
 import com.android.tools.r8.apimodel.JavaSourceCodePrinter.ParameterizedType;
 import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
 import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ClassReferenceUtils;
 import com.android.tools.r8.utils.EntryUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.MethodReferenceUtils;
@@ -40,10 +43,12 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -81,7 +86,9 @@
   public void testCanFindAnnotatedMethodsInJar() throws Exception {
     CovariantMethodsInJarResult covariantMethodsInJar = CovariantMethodsInJarResult.create();
     // These assertions are here to ensure we produce a sane result.
-    assertEquals(51, covariantMethodsInJar.methodReferenceMap.size());
+    assertEquals(9, covariantMethodsInJar.methodReferenceMap.keySet().size());
+    assertEquals(
+        51, covariantMethodsInJar.methodReferenceMap.values().stream().mapToLong(List::size).sum());
   }
 
   @Test
@@ -91,11 +98,9 @@
 
   public static String generateCode() throws Exception {
     CovariantMethodsInJarResult covariantMethodsInJar = CovariantMethodsInJarResult.create();
-    Map<MethodReference, List<MethodReference>> methodReferenceMap =
-        covariantMethodsInJar.methodReferenceMap;
-    List<Entry<MethodReference, List<MethodReference>>> entries =
-        new ArrayList<>(methodReferenceMap.entrySet());
-    entries.sort(Entry.comparingByKey(MethodReferenceUtils.getMethodReferenceComparator()));
+    List<Entry<ClassReference, List<MethodReferenceWithApiLevel>>> entries =
+        new ArrayList<>(covariantMethodsInJar.methodReferenceMap.entrySet());
+    entries.sort(Entry.comparingByKey(ClassReferenceUtils.getClassReferenceComparator()));
     JavaSourceCodePrinter printer =
         JavaSourceCodePrinter.builder()
             .setHeader(
@@ -119,10 +124,16 @@
                 methodPrinter ->
                     entries.forEach(
                         EntryUtils.accept(
-                            (ignored, covariations) ->
-                                covariations.forEach(
-                                    covariant ->
-                                        registerCovariantMethod(methodPrinter, covariant)))))
+                            (ignored, covariations) -> {
+                              covariations.sort(
+                                  Comparator.comparing(
+                                      MethodReferenceWithApiLevel::getMethodReference,
+                                      MethodReferenceUtils.getMethodReferenceComparator()));
+                              covariations.forEach(
+                                  covariant ->
+                                      registerCovariantMethod(
+                                          methodPrinter, covariant.methodReference));
+                            })))
             .toString();
     Path tempFile = Files.createTempFile("output-", ".java");
     Files.write(tempFile, javaSourceCode.getBytes(StandardCharsets.UTF_8));
@@ -175,15 +186,15 @@
   }
 
   public static class CovariantMethodsInJarResult {
-    private final Map<MethodReference, List<MethodReference>> methodReferenceMap;
+    private final Map<ClassReference, List<MethodReferenceWithApiLevel>> methodReferenceMap;
 
     private CovariantMethodsInJarResult(
-        Map<MethodReference, List<MethodReference>> methodReferenceMap) {
+        Map<ClassReference, List<MethodReferenceWithApiLevel>> methodReferenceMap) {
       this.methodReferenceMap = methodReferenceMap;
     }
 
     public static CovariantMethodsInJarResult create() throws Exception {
-      Map<MethodReference, List<MethodReference>> methodReferenceMap = new HashMap<>();
+      Map<ClassReference, List<MethodReferenceWithApiLevel>> methodReferenceMap = new HashMap<>();
       CodeInspector inspector = new CodeInspector(PATH_TO_CORE_JAR);
       DexItemFactory factory = inspector.getFactory();
       for (FoundClassSubject clazz : inspector.allClasses()) {
@@ -196,6 +207,7 @@
                           isCovariantReturnTypeAnnotation(annotation.annotation, factory));
               if (!covariantAnnotations.isEmpty()) {
                 MethodReference methodReference = method.asMethodReference();
+                ClassReference holder = clazz.getOriginalReference();
                 for (DexAnnotation covariantAnnotation : covariantAnnotations) {
                   if (covariantAnnotation.annotation.type
                       == factory.annotationCovariantReturnType) {
@@ -214,17 +226,55 @@
     private static void createCovariantMethodReference(
         MethodReference methodReference,
         DexAnnotation covariantAnnotation,
-        Map<MethodReference, List<MethodReference>> methodReferenceMap) {
+        Map<ClassReference, List<MethodReferenceWithApiLevel>> methodReferenceMap) {
       DexValueType newReturnType =
           covariantAnnotation.annotation.getElement(0).getValue().asDexValueType();
+      DexAnnotationElement element = covariantAnnotation.annotation.getElement(1);
+      assert element.name.toString().equals("presentAfter");
+      AndroidApiLevel apiLevel =
+          AndroidApiLevel.getAndroidApiLevel(element.getValue().asDexValueInt().value);
       methodReferenceMap
-          .computeIfAbsent(methodReference, ignoreKey(ArrayList::new))
+          .computeIfAbsent(methodReference.getHolderClass(), ignoreKey(ArrayList::new))
           .add(
-              Reference.method(
-                  methodReference.getHolderClass(),
-                  methodReference.getMethodName(),
-                  methodReference.getFormalTypes(),
-                  newReturnType.value.asClassReference()));
+              new MethodReferenceWithApiLevel(
+                  Reference.method(
+                      methodReference.getHolderClass(),
+                      methodReference.getMethodName(),
+                      methodReference.getFormalTypes(),
+                      newReturnType.value.asClassReference()),
+                  apiLevel));
+    }
+
+    public void visitCovariantMethodsForHolder(
+        ClassReference reference, Consumer<MethodReferenceWithApiLevel> consumer) {
+      List<MethodReferenceWithApiLevel> methodReferences = methodReferenceMap.get(reference);
+      if (methodReferences != null) {
+        methodReferences.stream()
+            .sorted(
+                Comparator.comparing(
+                    MethodReferenceWithApiLevel::getMethodReference,
+                    MethodReferenceUtils.getMethodReferenceComparator()))
+            .forEach(consumer);
+      }
+    }
+  }
+
+  public static class MethodReferenceWithApiLevel {
+
+    private final MethodReference methodReference;
+    private final AndroidApiLevel apiLevel;
+
+    private MethodReferenceWithApiLevel(MethodReference methodReference, AndroidApiLevel apiLevel) {
+      this.methodReference = methodReference;
+      this.apiLevel = apiLevel;
+    }
+
+    public MethodReference getMethodReference() {
+      return methodReference;
+    }
+
+    public AndroidApiLevel getApiLevel() {
+      return apiLevel;
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
index 708858e..423850e 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.androidapi.AndroidApiDataAccess;
+import com.android.tools.r8.androidapi.GenerateCovariantReturnTypeMethodsTest.CovariantMethodsInJarResult;
 import com.android.tools.r8.apimodel.AndroidApiVersionsXmlParser.ParsedApiClass;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -81,6 +82,8 @@
         computeAppViewWithClassHierarchy(AndroidApp.builder().addLibraryFile(androidJar).build());
     DexItemFactory factory = appView.dexItemFactory();
 
+    CovariantMethodsInJarResult covariantMethodsInJar = CovariantMethodsInJarResult.create();
+
     for (ParsedApiClass apiClass : apiClasses) {
       Map<DexMethod, AndroidApiLevel> methodsForApiClass = new HashMap<>();
       apiClass.visitMethodReferences(
@@ -88,6 +91,18 @@
             methods.forEach(
                 method -> methodsForApiClass.put(factory.createMethod(method), apiLevel));
           });
+      covariantMethodsInJar.visitCovariantMethodsForHolder(
+          apiClass.getClassReference(),
+          methodReferenceWithApiLevel -> {
+            DexMethod method =
+                factory.createMethod(methodReferenceWithApiLevel.getMethodReference());
+            if (!methodsForApiClass.containsKey(method)) {
+              apiClass.amendCovariantMethod(
+                  methodReferenceWithApiLevel.getMethodReference(),
+                  methodReferenceWithApiLevel.getApiLevel());
+              methodsForApiClass.put(method, methodReferenceWithApiLevel.getApiLevel());
+            }
+          });
       Map<DexField, AndroidApiLevel> fieldsForApiClass = new HashMap<>();
       apiClass.visitFieldReferences(
           (apiLevel, fields) -> {
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
index 53f5fda..7de78a2 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
@@ -6,6 +6,7 @@
 
 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
@@ -110,7 +111,7 @@
   @Test
   public void testDatabaseGenerationUpToDate() throws Exception {
     GenerateDatabaseResourceFilesResult result = generateResourcesFiles();
-    TestBase.filesAreEqual(result.apiLevels, API_DATABASE);
+    assertTrue(TestBase.filesAreEqual(result.apiLevels, API_DATABASE));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
index 4a3cfa2..8e8f4ee 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
@@ -247,6 +247,10 @@
       interfaces.forEach(consumer);
     }
 
+    public void amendCovariantMethod(MethodReference methodReference, AndroidApiLevel apiLevel) {
+      register(methodReference, apiLevel);
+    }
+
     public boolean isInterface() {
       return isInterface;
     }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelBridgeToLibraryMethodTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelBridgeToLibraryMethodTest.java
new file mode 100644
index 0000000..374b190
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelBridgeToLibraryMethodTest.java
@@ -0,0 +1,78 @@
+// 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.apimodel;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.function.Function;
+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;
+
+// This is a regression test for b/235184674.
+@RunWith(Parameterized.class)
+public class ApiModelBridgeToLibraryMethodTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("8");
+
+  @Test()
+  public void testR8WithApiLevelCheck() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClassWithApiLevelCheck.class)
+        .addAndroidBuildVersion()
+        .run(parameters.getRuntime(), TestClassWithApiLevelCheck.class)
+        .applyIf(
+            parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+            r -> r.assertSuccessWithOutputLines("No call"),
+            r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  static class TestClassWithApiLevelCheck {
+
+    private static void m(B b) {
+      System.out.println(b.compose(b).apply(2));
+    }
+
+    public static void main(String[] args) {
+      if (AndroidBuildVersion.VERSION >= 24) {
+        m(new B());
+      } else {
+        System.out.println("No call");
+      }
+    }
+  }
+
+  interface MyFunction<V, R> extends Function<V, R> {}
+
+  static class B implements MyFunction<Integer, Integer> {
+
+    @Override
+    public <V> Function<V, Integer> compose(Function<? super V, ? extends Integer> before) {
+      return MyFunction.super.compose(before);
+    }
+
+    @Override
+    public Integer apply(Integer integer) {
+      return integer * 2;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelCovariantReturnTypeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelCovariantReturnTypeTest.java
index 0726238..524147f 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelCovariantReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelCovariantReturnTypeTest.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.apimodel;
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.addTracedApiReferenceLevelCallBack;
-import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -40,17 +40,17 @@
     testForR8(parameters.getBackend())
         .addProgramClasses(Main.class)
         .setMinApi(parameters.getApiLevel())
-        .applyIf(
-            parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
-            b -> b.addDontWarn(KeySetView.class))
         .addKeepMainRule(Main.class)
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(
             addTracedApiReferenceLevelCallBack(
                 (method, apiLevel) -> {
                   if (Reference.methodFromMethod(main).equals(method)) {
-                    // TODO(b/232891189): Should be api level 28.
-                    assertNull(apiLevel);
+                    assertEquals(
+                        parameters.isCfRuntime()
+                            ? AndroidApiLevel.P
+                            : parameters.getApiLevel().max(AndroidApiLevel.P),
+                        apiLevel);
                   }
                 }))
         .compile();
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeStrengtheningAboveMinApiTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeStrengtheningAboveMinApiTest.java
new file mode 100644
index 0000000..d974c7a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeStrengtheningAboveMinApiTest.java
@@ -0,0 +1,98 @@
+// 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.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+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 ApiModelTypeStrengtheningAboveMinApiTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    int sdkInt = parameters.isCfRuntime() ? 0 : parameters.getApiLevel().getLevel();
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, Version.class)
+        .addLibraryClasses(ApiLevel22.class, ApiLevel23.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .addKeepMainRule(Main.class)
+        .addKeepRules(
+            "-assumevalues class " + Version.class.getTypeName() + " {",
+            "  public static int getSdkInt(int) return " + sdkInt + "..42;",
+            "}")
+        .apply(setMockApiLevelForClass(ApiLevel22.class, AndroidApiLevel.L_MR1))
+        .apply(setMockApiLevelForClass(ApiLevel23.class, AndroidApiLevel.M))
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              FieldSubject fieldSubject = inspector.clazz(Main.class).uniqueFieldWithName("FIELD");
+              assertThat(fieldSubject, isPresent());
+              assertEquals(
+                  ApiLevel22.class.getTypeName(), fieldSubject.getField().getType().getTypeName());
+            })
+        .addRunClasspathClasses(ApiLevel22.class, ApiLevel23.class)
+        .run(parameters.getRuntime(), Main.class, Integer.toString(sdkInt))
+        .applyIf(
+            parameters.isDexRuntime()
+                && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L_MR1),
+            runResult -> runResult.assertSuccessWithOutputLines("ApiLevel22"),
+            runResult -> runResult.assertSuccessWithOutputLines("null"));
+  }
+
+  public static class ApiLevel23 {}
+
+  public static class ApiLevel22 extends ApiLevel23 {
+
+    @Override
+    public String toString() {
+      return "ApiLevel22";
+    }
+  }
+
+  public static class Main {
+
+    public static ApiLevel23 FIELD;
+
+    public static void main(String[] args) {
+      int sdk = Integer.parseInt(args[0]);
+      if (Version.getSdkInt(sdk) >= 22) {
+        FIELD = new ApiLevel22();
+      }
+      System.out.println(FIELD);
+    }
+  }
+
+  public static class Version {
+
+    // -assumevalues ...
+    public static int getSdkInt(int sdk) {
+      return sdk;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeStrengtheningTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeStrengtheningTest.java
new file mode 100644
index 0000000..1d17e5c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeStrengtheningTest.java
@@ -0,0 +1,102 @@
+// 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.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+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 ApiModelTypeStrengtheningTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    boolean isTypeStrengtheningSafe =
+        parameters.isDexRuntime()
+            && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.M);
+    int sdkInt = parameters.isCfRuntime() ? 0 : parameters.getApiLevel().getLevel();
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, Version.class)
+        .addLibraryClasses(ApiLevel22.class, ApiLevel23.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .addKeepMainRule(Main.class)
+        .addKeepRules(
+            "-assumevalues class " + Version.class.getTypeName() + " {",
+            "  public static int getSdkInt(int) return " + sdkInt + "..42;",
+            "}")
+        .apply(setMockApiLevelForClass(ApiLevel22.class, AndroidApiLevel.L_MR1))
+        .apply(setMockApiLevelForClass(ApiLevel23.class, AndroidApiLevel.M))
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              Class<?> expectedFieldType =
+                  isTypeStrengtheningSafe ? ApiLevel23.class : ApiLevel22.class;
+              FieldSubject fieldSubject = inspector.clazz(Main.class).uniqueFieldWithName("FIELD");
+              assertThat(fieldSubject, isPresent());
+              assertEquals(
+                  expectedFieldType.getTypeName(), fieldSubject.getField().getType().getTypeName());
+            })
+        .addRunClasspathClasses(ApiLevel22.class, ApiLevel23.class)
+        .run(parameters.getRuntime(), Main.class, Integer.toString(sdkInt))
+        .applyIf(
+            isTypeStrengtheningSafe,
+            runResult -> runResult.assertSuccessWithOutputLines("ApiLevel23"),
+            runResult -> runResult.assertSuccessWithOutputLines("null"));
+  }
+
+  public static class ApiLevel22 {}
+
+  public static class ApiLevel23 extends ApiLevel22 {
+
+    @Override
+    public String toString() {
+      return "ApiLevel23";
+    }
+  }
+
+  public static class Main {
+
+    public static ApiLevel22 FIELD;
+
+    public static void main(String[] args) {
+      int sdk = Integer.parseInt(args[0]);
+      if (Version.getSdkInt(sdk) >= 23) {
+        FIELD = new ApiLevel23();
+      }
+      System.out.println(FIELD);
+    }
+  }
+
+  public static class Version {
+
+    // -assumevalues ...
+    public static int getSdkInt(int sdk) {
+      return sdk;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java b/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java
index e1f0d55..001c239 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.benchmarks.BenchmarkMethod;
 import com.android.tools.r8.benchmarks.BenchmarkTarget;
 import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.ProguardMappingSupplier;
 import com.android.tools.r8.retrace.Retrace;
 import com.android.tools.r8.retrace.RetraceCommand;
 import com.google.common.collect.ImmutableList;
@@ -69,8 +70,12 @@
                   long start = System.nanoTime();
                   Retrace.run(
                       RetraceCommand.builder()
-                          .setProguardMapProducer(
-                              ProguardMapProducer.fromPath(dependencyRoot.resolve("r8lib.jar.map")))
+                          .setMappingSupplier(
+                              ProguardMappingSupplier.builder()
+                                  .setProguardMapProducer(
+                                      ProguardMapProducer.fromPath(
+                                          dependencyRoot.resolve("r8lib.jar.map")))
+                                  .build())
                           .setStackTrace(stackTrace)
                           .setRetracedStackTraceConsumer(retraced::addAll)
                           .build());
diff --git a/src/test/java/com/android/tools/r8/cf/InconsistentLocalTypeOnExceptionEdgeTest.java b/src/test/java/com/android/tools/r8/cf/InconsistentLocalTypeOnExceptionEdgeTest.java
new file mode 100644
index 0000000..9c97a2e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/InconsistentLocalTypeOnExceptionEdgeTest.java
@@ -0,0 +1,112 @@
+// 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;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.jasmin.JasminTestBase;
+import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InconsistentLocalTypeOnExceptionEdgeTest extends JasminTestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    List<byte[]> classFileData = getProgramClassFileData();
+    String main = "Main";
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClassFileData(classFileData)
+          .run(parameters.getRuntime(), main)
+          .assertFailureWithErrorThatThrows(VerifyError.class);
+    } else {
+      try {
+        testForD8()
+            .addProgramClassFileData(classFileData)
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+      } catch (CompilationFailedException e) {
+        inspectCompilationFailedException(e);
+      }
+    }
+
+    try {
+      testForR8(parameters.getBackend())
+          .addProgramClassFileData(classFileData)
+          .addKeepAllClassesRule()
+          .allowDiagnosticWarningMessages()
+          .setMinApi(parameters.getApiLevel())
+          .compileWithExpectedDiagnostics(
+              diagnostics ->
+                  diagnostics.assertWarningsMatch(
+                      allOf(
+                          diagnosticType(UnverifiableCfCodeDiagnostic.class),
+                          diagnosticMessage(
+                              allOf(
+                                  containsString(
+                                      "Unverifiable code in `void Main.main(java.lang.String[])`"),
+                                  containsString(
+                                      "Expected object at local index 0, but was top"))))));
+      fail("Expected compilation to fail");
+    } catch (CompilationFailedException e) {
+      inspectCompilationFailedException(e);
+    }
+  }
+
+  private List<byte[]> getProgramClassFileData() throws Exception {
+    JasminBuilder appBuilder = new JasminBuilder();
+    ClassBuilder classBuilder = appBuilder.addClass("Main");
+    classBuilder.addStaticField("FIELD", "I");
+    classBuilder.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "LabelTryStart:",
+        // At this throwing instruction we have locals=[0: java.lang.String[]].
+        "  getstatic Main/FIELD I",
+        "  istore 0",
+        "  aconst_null",
+        // At this throwing instruction we have locals=[0: int].
+        "  athrow",
+        "LabelTryEnd:",
+        "LabelCatch:",
+        // Unsafe attempt to read an object at local index 0.
+        "  aload 0",
+        "  invokestatic java/util/Arrays/toString([Ljava/lang/Object;)Ljava/lang/String;",
+        "  pop",
+        "  return",
+        ".catch java/lang/Throwable from LabelTryStart to LabelTryEnd using LabelCatch");
+    return appBuilder.buildClasses();
+  }
+
+  private void inspectCompilationFailedException(CompilationFailedException e) {
+    assertThat(
+        e.getCause().getMessage(),
+        containsString("Cannot constrain type: INT for value: v1 by constraint: OBJECT"));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java b/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java
index 792f793..b4b25c7 100644
--- a/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java
+++ b/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java
@@ -3,6 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.R8TestRunResult;
@@ -11,6 +16,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic;
 import java.nio.file.Path;
 import java.util.List;
 import org.junit.Test;
@@ -53,9 +59,19 @@
             .addProgramClasses(TestClass.class, A.class)
             .addKeepMainRule(TestClass.class)
             .addDontWarn(B.class)
+            .allowDiagnosticWarningMessages()
             .enableNoMethodStaticizingAnnotations()
             .setMinApi(parameters.getApiLevel())
-            .compile()
+            .compileWithExpectedDiagnostics(
+                diagnostics ->
+                    diagnostics.assertWarningsMatch(
+                        allOf(
+                            diagnosticType(UnverifiableCfCodeDiagnostic.class),
+                            diagnosticMessage(
+                                containsString(
+                                    "Unverifiable code in `void "
+                                        + TestClass.class.getTypeName()
+                                        + ".main(java.lang.String[])`")))))
             .addRunClasspathFiles(getRuntimeClasspath())
             .run(parameters.getRuntime(), TestClass.class);
     if (parameters.isCfRuntime()) {
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
index 10ac394..f7ce233 100644
--- a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.ProguardMappingSupplier;
 import com.android.tools.r8.retrace.Retrace;
 import com.android.tools.r8.retrace.RetraceCommand;
 import com.android.tools.r8.utils.FileUtils;
@@ -138,7 +139,10 @@
     RetraceCommand retraceCommand =
         RetraceCommand.builder()
             .setStackTrace(StringUtils.splitLines(processResult.stderr))
-            .setProguardMapProducer(ProguardMapProducer.fromPath(r8R8Release.getSecond()))
+            .setMappingSupplier(
+                ProguardMappingSupplier.builder()
+                    .setProguardMapProducer(ProguardMapProducer.fromPath(r8R8Release.getSecond()))
+                    .build())
             .setRetracedStackTraceConsumer(
                 retraced -> {
                   int expectedIndex = -1;
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithFeatureSplitTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithFeatureSplitTest.java
index 3f0fac0..ad66194 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithFeatureSplitTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithFeatureSplitTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.dexsplitter.SplitterTestBase.RunInterface;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 import org.junit.runners.Parameterized;
@@ -32,6 +33,8 @@
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
             .addProgramClasses(Base.class)
+            // Link against android.jar that contains ReflectiveOperationException.
+            .addLibraryFiles(parameters.getDefaultAndroidJarAbove(AndroidApiLevel.K))
             .addFeatureSplitRuntime()
             .addFeatureSplit(Feature1Class1.class, Feature1Class2.class, Feature1Main.class)
             .addFeatureSplit(Feature2Class.class, Feature2Main.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/SyntheticLambdaWithMissingInterfaceMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/SyntheticLambdaWithMissingInterfaceMergingTest.java
index 6f9a788..3c92a8d 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/SyntheticLambdaWithMissingInterfaceMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/SyntheticLambdaWithMissingInterfaceMergingTest.java
@@ -4,11 +4,16 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic;
 import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import java.util.List;
 import org.junit.Test;
@@ -41,8 +46,18 @@
         .addHorizontallyMergedClassesInspector(
             HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .applyIf(!enableOptimization, TestShrinkerBuilder::addDontOptimize)
+        .allowDiagnosticWarningMessages()
         .setMinApi(parameters.getApiLevel())
-        .compile()
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics.assertWarningsMatch(
+                    allOf(
+                        diagnosticType(UnverifiableCfCodeDiagnostic.class),
+                        diagnosticMessage(
+                            containsString(
+                                "Unverifiable code in `void "
+                                    + Main.class.getTypeName()
+                                    + ".dead()`")))))
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("I");
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingWithAbsentMethodAndSuperClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingWithAbsentMethodAndSuperClassMergingTest.java
index 8ee3476..984f431 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingWithAbsentMethodAndSuperClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingWithAbsentMethodAndSuperClassMergingTest.java
@@ -20,10 +20,10 @@
 public class VirtualMethodMergingWithAbsentMethodAndSuperClassMergingTest extends TestBase {
 
   @Parameter(0)
-  public Class<?> upperMergeTarget;
+  public ClassWrapper upperMergeTarget;
 
   @Parameter(1)
-  public Class<?> lowerMergeTarget;
+  public ClassWrapper lowerMergeTarget;
 
   @Parameter(2)
   public TestParameters parameters;
@@ -31,11 +31,38 @@
   @Parameters(name = "{2}, upper merge target: {0}, lower merge target: {1}")
   public static List<Object[]> data() {
     return buildParameters(
-        ImmutableList.of(A.class, B.class),
-        ImmutableList.of(C.class, D.class),
+        ImmutableList.of(ClassWrapper.create(A.class), ClassWrapper.create(B.class)),
+        ImmutableList.of(ClassWrapper.create(C.class), ClassWrapper.create(D.class)),
         getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  // Use a ClassWrapper to wrap the classes such that the string representation of the test do not
+  // exceed to many characters.
+  public static class ClassWrapper {
+    public final Class<?> clazz;
+
+    private ClassWrapper(Class<?> clazz) {
+      this.clazz = clazz;
+    }
+
+    public static ClassWrapper create(Class<?> clazz) {
+      return new ClassWrapper(clazz);
+    }
+
+    public String getTypeName() {
+      return clazz.getTypeName();
+    }
+
+    public boolean is(Class<?> clazz) {
+      return this.clazz == clazz;
+    }
+
+    @Override
+    public String toString() {
+      return clazz.getSimpleName();
+    }
+  }
+
   @Test
   public void test() throws Exception {
     testForR8(parameters.getBackend())
@@ -69,11 +96,11 @@
             inspector ->
                 inspector
                     .applyIf(
-                        upperMergeTarget == A.class,
+                        upperMergeTarget.is(A.class),
                         i -> i.assertMergedInto(B.class, A.class),
                         i -> i.assertMergedInto(A.class, B.class))
                     .applyIf(
-                        lowerMergeTarget == C.class,
+                        lowerMergeTarget.is(C.class),
                         i -> i.assertMergedInto(D.class, C.class),
                         i -> i.assertMergedInto(C.class, D.class))
                     .assertNoOtherClassesMerged())
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index bfd9f97..cbbb43e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -858,7 +858,7 @@
     ClassBuilder classBuilder = jasminBuilder.addClass(main);
     classBuilder.addMainMethod(
         ".limit locals 1",
-        ".limit stack 2",
+        ".limit stack 3",
         // Instantiate B so that it is not merged into C.
         "new classmerging/B",
         "dup",
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
index 017c652..14a04dc 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.compilerapi.androidplatformbuild.AndroidPlatformBuildApiTest;
 import com.android.tools.r8.compilerapi.assertionconfiguration.AssertionConfigurationTest;
 import com.android.tools.r8.compilerapi.desugardependencies.DesugarDependenciesTest;
+import com.android.tools.r8.compilerapi.diagnostics.UnsupportedFeaturesDiagnosticApiTest;
 import com.android.tools.r8.compilerapi.globalsynthetics.GlobalSyntheticsTest;
 import com.android.tools.r8.compilerapi.inputdependencies.InputDependenciesTest;
 import com.android.tools.r8.compilerapi.mapid.CustomMapIdTest;
@@ -47,7 +48,8 @@
           GlobalSyntheticsTest.ApiTest.class,
           CommandLineParserTest.ApiTest.class,
           EnableMissingLibraryApiModelingTest.ApiTest.class,
-          AndroidPlatformBuildApiTest.ApiTest.class);
+          AndroidPlatformBuildApiTest.ApiTest.class,
+          UnsupportedFeaturesDiagnosticApiTest.ApiTest.class);
 
   private final TemporaryFolder temp;
 
diff --git a/src/test/java/com/android/tools/r8/compilerapi/diagnostics/UnsupportedFeaturesDiagnosticApiTest.java b/src/test/java/com/android/tools/r8/compilerapi/diagnostics/UnsupportedFeaturesDiagnosticApiTest.java
new file mode 100644
index 0000000..d6eeb25
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/diagnostics/UnsupportedFeaturesDiagnosticApiTest.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.compilerapi.diagnostics;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.errors.InvokeCustomDiagnostic;
+import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.function.Consumer;
+import org.junit.Test;
+
+public class UnsupportedFeaturesDiagnosticApiTest extends CompilerApiTestRunner {
+
+  public UnsupportedFeaturesDiagnosticApiTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<? extends CompilerApiTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  @Test
+  public void test() throws Exception {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    test.run(
+        new InvokeCustomDiagnostic(Origin.unknown(), Position.UNKNOWN),
+        result -> {
+          assertEquals("invoke-custom @ 26", result);
+        });
+  }
+
+  public static class ApiTest extends CompilerApiTest {
+
+    public ApiTest(Object parameters) {
+      super(parameters);
+    }
+
+    public void run(Diagnostic diagnostic, Consumer<String> consumer) {
+      DiagnosticsHandler myHandler =
+          new DiagnosticsHandler() {
+            @Override
+            public void warning(Diagnostic warning) {
+              if (warning instanceof UnsupportedFeatureDiagnostic) {
+                UnsupportedFeatureDiagnostic unsupportedFeature =
+                    (UnsupportedFeatureDiagnostic) warning;
+                String featureDescriptor = unsupportedFeature.getFeatureDescriptor();
+                int supportedApiLevel = unsupportedFeature.getSupportedApiLevel();
+                consumer.accept(featureDescriptor + " @ " + supportedApiLevel);
+              }
+            }
+          };
+      myHandler.warning(diagnostic);
+    }
+
+    @Test
+    public void test() throws Exception {
+      run(null, str -> {});
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java
index 2e9abfb..9860a15 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java
@@ -120,7 +120,6 @@
                 internalOptions -> {
                   if (parameters.isCfRuntime()) {
                     internalOptions.desugarState = DesugarState.ON;
-                    internalOptions.cfToCfDesugar = true;
                   }
                 });
     if (parameters.isDexRuntime()) {
diff --git a/src/test/java/com/android/tools/r8/desugar/LambdaMissingInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/LambdaMissingInterfaceTest.java
index 59853ee..6c9fe50 100644
--- a/src/test/java/com/android/tools/r8/desugar/LambdaMissingInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/LambdaMissingInterfaceTest.java
@@ -4,12 +4,16 @@
 
 package com.android.tools.r8.desugar;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.containsString;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -36,8 +40,18 @@
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
         .addDontWarn(MissingInterface.class)
+        .allowDiagnosticWarningMessages()
         .enableInliningAnnotations()
-        .compile()
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics.assertWarningsMatch(
+                    allOf(
+                        diagnosticType(UnverifiableCfCodeDiagnostic.class),
+                        diagnosticMessage(
+                            containsString(
+                                "Unverifiable code in `void "
+                                    + ClassWithLambda.class.getTypeName()
+                                    + ".callWithLambda()`")))))
         .addRunClasspathClasses(MissingInterface.class)
         .run(parameters.getRuntime(), Main.class)
         // We allow for renaming if the class is missing
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java
index 4022ff8..bcee062 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java
@@ -25,7 +25,8 @@
 public class ConcurrentHashMapSubclassTest extends DesugaredLibraryTestBase {
 
   private static final String EXPECTED_RESULT =
-      StringUtils.lines("1.0", "2.0", "10.0", "1.0", "2.0", "10.0", "1.0", "2.0", "10.0");
+      StringUtils.lines(
+          "1.0", "2.0", "10.0", "1.0", "2.0", "10.0", "1.0", "2.0", "10.0", "1.0", "2.0", "10.0");
 
   private final TestParameters parameters;
   private final CompilationSpecification compilationSpecification;
@@ -64,11 +65,28 @@
   @SuppressWarnings("unchecked")
   static class Executor {
     public static void main(String[] args) {
+      constructorTest();
       directType();
       classType();
       itfType();
     }
 
+    private static void constructorTest() {
+      Map map =
+          new ConcurrentHashMap<>(
+              5, // initial capacity
+              0.75f, // load factor (default)
+              1 // concurrency level - only one thread will modify, others read only
+              );
+      map.put(1, 1.0);
+      map.putIfAbsent(2, 2.0);
+      map.putIfAbsent(2, 3.0);
+      map.putAll(example());
+      System.out.println(map.get(1));
+      System.out.println(map.get(2));
+      System.out.println(map.get(10));
+    }
+
     static void itfType() {
       Map map = new NullableConcurrentHashMap<Integer, Double>();
       map.put(1, 1.0);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvalidTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvalidTest.java
index 6a169bf..fd59fc9 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvalidTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvalidTest.java
@@ -6,6 +6,8 @@
 
 import static com.android.tools.r8.ToolHelper.DESUGARED_JDK_8_LIB_JAR;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.CustomConversionVersion.LATEST;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.CustomConversionVersion.LEGACY;
 import static junit.framework.TestCase.assertTrue;
 import static org.junit.Assert.assertFalse;
 
@@ -40,14 +42,16 @@
             DESUGARED_JDK_8_LIB_JAR,
             "desugar_jdk_libs.json",
             AndroidApiLevel.L,
-            LibraryDesugaringSpecification.JDK8_DESCRIPTOR);
+            LibraryDesugaringSpecification.JDK8_DESCRIPTOR,
+            LEGACY);
     LibraryDesugaringSpecification jdk11InvalidLib =
         new LibraryDesugaringSpecification(
             "JDK11_INVALID_LIB",
             ToolHelper.getUndesugaredJdk11LibJarForTesting(),
             "jdk11/desugar_jdk_libs.json",
             AndroidApiLevel.L,
-            LibraryDesugaringSpecification.JDK11_DESCRIPTOR);
+            LibraryDesugaringSpecification.JDK11_DESCRIPTOR,
+            LATEST);
     return buildParameters(
         getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
         ImmutableList.of(jdk8InvalidLib, jdk11InvalidLib),
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
index b6be183..d90495c 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar.desugaredlibrary;
 
-import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -100,7 +100,8 @@
 
   @Parameters(name = "{0}, spec: {1}")
   public static List<Object[]> data() {
-    return buildParameters(getTestParameters().withNoneRuntime().build(), getJdk8Jdk11());
+    // TODO(b/236356665): Support JDK11 desugared lib.
+    return buildParameters(getTestParameters().withNoneRuntime().build(), ImmutableList.of(JDK8));
   }
 
   public ExtractWrapperTypesTest(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MaintainAndRewritePrefixTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MaintainAndRewritePrefixTest.java
new file mode 100644
index 0000000..51b3660
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MaintainAndRewritePrefixTest.java
@@ -0,0 +1,84 @@
+// 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;
+
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.errors.CompilationError;
+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;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class MaintainAndRewritePrefixTest extends DesugaredLibraryTestBase implements Opcodes {
+
+  private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+
+  @Parameters(name = "{0}, spec: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntime(Version.DEFAULT).withCfRuntime(CfVm.JDK11).build(),
+        LibraryDesugaringSpecification.getJdk8Jdk11());
+  }
+
+  public MaintainAndRewritePrefixTest(
+      TestParameters parameters, LibraryDesugaringSpecification libraryDesugaringSpecification) {
+    this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+  }
+
+  /**
+   * Add this library desugaring configuration:
+   * "library_flags": [
+   *  {
+   *   "rewrite_prefix": {"java.time.": "j$.time."},
+   *   "maintain_prefix": ["java.time."],
+   *  }
+   * ],
+   */
+  private static void specifyDesugaredLibrary(InternalOptions options) {
+    HumanRewritingFlags rewritingFlags =
+        HumanRewritingFlags.builder(options.reporter, Origin.unknown())
+            .putRewritePrefix("java.time.", "j$.time.")
+            .putMaintainPrefix("java.time.")
+            .build();
+    options.setDesugaredLibrarySpecification(
+        new HumanDesugaredLibrarySpecification(HumanTopLevelFlags.testing(), rewritingFlags, true));
+  }
+
+  @Test
+  public void test() throws Exception {
+    try {
+      testForL8(AndroidApiLevel.B, parameters.getBackend())
+          .apply(libraryDesugaringSpecification::configureL8TestBuilder)
+          .addOptionsModifier(MaintainAndRewritePrefixTest::specifyDesugaredLibrary)
+          .compile();
+      fail();
+    } catch (Exception e) {
+      Throwable cause = e.getCause();
+      org.junit.Assert.assertTrue(cause instanceof CompilationError);
+      CompilationError ce = (CompilationError) cause;
+      org.junit.Assert.assertTrue(
+          ce.getMessage()
+              .contains(
+                  "The compilation cannot proceed because the desugared library specification"
+                      + " contains ambiguous flags that the compiler cannot interpret"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
index 66438e9..9c300c7 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
@@ -6,6 +6,8 @@
 
 import static com.android.tools.r8.ToolHelper.DESUGARED_JDK_8_LIB_JAR;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.SPECIFICATIONS_WITH_CF2CF;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.CustomConversionVersion.LATEST;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.CustomConversionVersion.LEGACY;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
@@ -76,14 +78,16 @@
             DESUGARED_JDK_8_LIB_JAR,
             "desugar_jdk_libs.json",
             AndroidApiLevel.LATEST,
-            LibraryDesugaringSpecification.JDK8_DESCRIPTOR);
+            LibraryDesugaringSpecification.JDK8_DESCRIPTOR,
+            LEGACY);
     LibraryDesugaringSpecification jdk11MaxCompileSdk =
         new LibraryDesugaringSpecification(
             "JDK11_MAX",
             ToolHelper.getUndesugaredJdk11LibJarForTesting(),
             "jdk11/desugar_jdk_libs.json",
             AndroidApiLevel.LATEST,
-            LibraryDesugaringSpecification.JDK11_DESCRIPTOR);
+            LibraryDesugaringSpecification.JDK11_DESCRIPTOR,
+            LATEST);
     return buildParameters(
         getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
         ImmutableList.of(JDK8, JDK11, jdk8MaxCompileSdk, jdk11MaxCompileSdk),
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
index 566b0a0..efee088 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
@@ -6,6 +6,8 @@
 
 import static com.android.tools.r8.ToolHelper.DESUGARED_JDK_8_LIB_JAR;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.CustomConversionVersion.LATEST;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.CustomConversionVersion.LEGACY;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
@@ -52,7 +54,7 @@
             "JDK8_CL",
             ImmutableSet.of(
                 DESUGARED_JDK_8_LIB_JAR,
-                ToolHelper.DESUGAR_LIB_CONVERSIONS,
+                ToolHelper.getConvertedDesugaredLibConversions(LEGACY),
                 ToolHelper.getCoreLambdaStubs()),
             JDK8.getSpecification(),
             ImmutableSet.of(ToolHelper.getAndroidJar(AndroidApiLevel.O)),
@@ -63,7 +65,7 @@
             "JDK11_CL",
             ImmutableSet.of(
                 ToolHelper.getUndesugaredJdk11LibJarForTesting(),
-                ToolHelper.DESUGAR_LIB_CONVERSIONS,
+                ToolHelper.getConvertedDesugaredLibConversions(LATEST),
                 ToolHelper.getCoreLambdaStubs()),
             JDK11.getSpecification(),
             ImmutableSet.of(ToolHelper.getAndroidJar(AndroidApiLevel.R)),
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/ConcurrentHashMapFileSerializationTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/ConcurrentHashMapFileSerializationTest.java
index da9064b..3afc61c 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/ConcurrentHashMapFileSerializationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/ConcurrentHashMapFileSerializationTest.java
@@ -6,13 +6,19 @@
 
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -64,11 +70,23 @@
         .addKeepMainRule(Executor.class)
         .noMinification()
         .compile()
+        .inspectL8(this::assertVersionUID)
         .withArt6Plus64BitsLib()
         .run(parameters.getRuntime(), Executor.class)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
+  private void assertVersionUID(CodeInspector inspector) {
+    ClassSubject mapClass = inspector.clazz("j$.util.concurrent.ConcurrentHashMap");
+    if (parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) {
+      assertTrue(mapClass.isPresent());
+      FieldSubject serialVersionUID = mapClass.uniqueFieldWithName("serialVersionUID");
+      assertTrue(serialVersionUID.isPresent());
+    } else {
+      assertFalse(mapClass.isPresent());
+    }
+  }
+
   @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
   static class Executor {
     public static void main(String[] args) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/MyMapFileSerializationTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/MyMapFileSerializationTest.java
index 8f02521..3c85888 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/MyMapFileSerializationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/MyMapFileSerializationTest.java
@@ -64,10 +64,20 @@
         .noMinification()
         .compile()
         .withArt6Plus64BitsLib()
-        .run(parameters.getRuntime(), Executor.class)
+        .run(parameters.getRuntime(), Executor.class, uniqueName())
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
+  private String uniqueName() {
+    return libraryDesugaringSpecification.toString()
+        + "_"
+        + compilationSpecification.toString()
+        + "_"
+        + parameters.getRuntime()
+        + "_"
+        + parameters.getApiLevel();
+  }
+
   @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
   static class Executor {
 
@@ -76,7 +86,10 @@
       MyMap<String, String> map = new MyMap<>();
       map.put("k1", "v1");
       map.put("k2", "v2");
-      File file = new File("testTemp");
+      // It seems the FileSystem is shared across multiple VM runs at least on some VMs.
+      // There is no easy way to create a temp file that works on all VM/configurations.
+      // We pass a unique string as parameter that we use for the file name.
+      File file = new File("test_" + args[0]);
 
       FileOutputStream fos = new FileOutputStream(file);
       ObjectOutputStream oos = new ObjectOutputStream(fos);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/ChannelSetTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/ChannelSetTest.java
index 193507a..cc4bbc3 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/ChannelSetTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/ChannelSetTest.java
@@ -31,6 +31,7 @@
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ForkJoinPool;
 import java.util.concurrent.Future;
 import org.junit.Test;
@@ -45,7 +46,7 @@
   private final LibraryDesugaringSpecification libraryDesugaringSpecification;
   private final CompilationSpecification compilationSpecification;
 
-  private static final String EXPECTED_RESULT =
+  private static final String EXPECTED_RESULT_DESUGARING =
       StringUtils.lines(
           "bytes written: 11",
           "String written: Hello World",
@@ -54,7 +55,17 @@
           "bytes read: 11",
           "String read: Hello World",
           "unsupported");
-  private static final String EXPECTED_RESULT_26 =
+  private static final String EXPECTED_RESULT_DESUGARING_PLATFORM_FILE_SYSTEM =
+      StringUtils.lines(
+          "bytes written: 11",
+          "String written: Hello World",
+          "bytes read: 11",
+          "String read: Hello World",
+          "bytes read: 11",
+          "String read: Hello World",
+          "bytes read: 11",
+          "String read: Hello World");
+  private static final String EXPECTED_RESULT_NO_DESUGARING =
       StringUtils.lines(
           "bytes written: 11",
           "String written: Hello World",
@@ -97,7 +108,7 @@
   }
 
   @Test
-  public void test() throws Exception {
+  public void test() throws Throwable {
     testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
         .addProgramClasses(TestClass.class)
         .setCustomLibrarySpecification(
@@ -113,9 +124,12 @@
   }
 
   private String getExpectedResult() {
-    return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O)
-        ? EXPECTED_RESULT_26
-        : EXPECTED_RESULT;
+    if (!libraryDesugaringSpecification.hasNioFileDesugaring(parameters)) {
+      return EXPECTED_RESULT_NO_DESUGARING;
+    }
+    return libraryDesugaringSpecification.usesPlatformFileSystem(parameters)
+        ? EXPECTED_RESULT_DESUGARING_PLATFORM_FILE_SYSTEM
+        : EXPECTED_RESULT_DESUGARING;
   }
 
   public static class CustomLib {
@@ -163,12 +177,20 @@
           pathWrapper.getFileSystem().provider().newFileChannel(pathWrapper, openOptions)) {
         readFromChannel(channel, hello.length());
       }
+      ExecutorService executor;
+      try {
+        executor = ForkJoinPool.commonPool();
+      } catch (Throwable t) {
+        // ForkJoinPool is not entirely supported below Android 5.
+        System.out.println("unsupported");
+        return;
+      }
       try {
         try (AsynchronousFileChannel channel =
             pathWrapper
                 .getFileSystem()
                 .provider()
-                .newAsynchronousFileChannel(pathWrapper, openOptions, ForkJoinPool.commonPool())) {
+                .newAsynchronousFileChannel(pathWrapper, openOptions, executor)) {
           ByteBuffer byteBuffer = ByteBuffer.allocate(hello.length());
           Future<Integer> readFuture = channel.read(byteBuffer, 0);
           // We force the future to await here with get().
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/ConversionConverter.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/ConversionConverter.java
new file mode 100644
index 0000000..746a1d2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/ConversionConverter.java
@@ -0,0 +1,179 @@
+// 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.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.CustomConversionVersion.LEGACY;
+
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.CustomConversionVersion;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.StreamUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import org.objectweb.asm.Opcodes;
+
+public class ConversionConverter {
+
+  private static final Map<String, String> JAVA_WRAP_CONVERT_OWNER = new HashMap<>();
+  private static final Map<String, String> J$_WRAP_CONVERT_OWNER = new HashMap<>();
+
+  static {
+    JAVA_WRAP_CONVERT_OWNER.put(
+        "j$/nio/file/spi/FileSystemProvider",
+        "java/nio/file/spi/FileSystemProvider$VivifiedWrapper");
+    JAVA_WRAP_CONVERT_OWNER.put(
+        "j$/nio/file/spi/FileTypeDetector", "java/nio/file/spi/FileTypeDetector$VivifiedWrapper");
+    JAVA_WRAP_CONVERT_OWNER.put(
+        "j$/nio/file/StandardOpenOption", "java/nio/file/StandardOpenOption$EnumConversion");
+    JAVA_WRAP_CONVERT_OWNER.put(
+        "j$/nio/file/LinkOption", "java/nio/file/LinkOption$EnumConversion");
+    JAVA_WRAP_CONVERT_OWNER.put("j$/nio/file/Path", "java/nio/file/Path$Wrapper");
+    JAVA_WRAP_CONVERT_OWNER.put(
+        "j$/nio/file/WatchEvent", "java/nio/file/WatchEvent$VivifiedWrapper");
+    JAVA_WRAP_CONVERT_OWNER.put(
+        "j$/nio/file/attribute/BasicFileAttributes",
+        "java/nio/file/attribute/BasicFileAttributes$VivifiedWrapper");
+    JAVA_WRAP_CONVERT_OWNER.put(
+        "j$/nio/file/attribute/BasicFileAttributeView",
+        "java/nio/file/attribute/BasicFileAttributeView$VivifiedWrapper");
+    JAVA_WRAP_CONVERT_OWNER.put(
+        "j$/nio/file/attribute/FileOwnerAttributeView",
+        "java/nio/file/attribute/FileOwnerAttributeView$VivifiedWrapper");
+    JAVA_WRAP_CONVERT_OWNER.put(
+        "j$/nio/file/attribute/PosixFileAttributes",
+        "java/nio/file/attribute/PosixFileAttributes$VivifiedWrapper");
+    JAVA_WRAP_CONVERT_OWNER.put(
+        "j$/nio/file/attribute/PosixFileAttributeView",
+        "java/nio/file/attribute/PosixFileAttributeView$VivifiedWrapper");
+    JAVA_WRAP_CONVERT_OWNER.put(
+        "j$/nio/file/attribute/PosixFilePermission",
+        "java/nio/file/attribute/PosixFilePermission$EnumConversion");
+    JAVA_WRAP_CONVERT_OWNER.put(
+        "j$/util/stream/Collector$Characteristics",
+        "java/util/stream/Collector$Characteristics$EnumConversion");
+
+    J$_WRAP_CONVERT_OWNER.put(
+        "j$/nio/file/spi/FileSystemProvider", "java/nio/file/spi/FileSystemProvider$Wrapper");
+    J$_WRAP_CONVERT_OWNER.put(
+        "j$/nio/file/spi/FileTypeDetector", "java/nio/file/spi/FileTypeDetector$Wrapper");
+    J$_WRAP_CONVERT_OWNER.put(
+        "j$/nio/file/StandardOpenOption", "java/nio/file/StandardOpenOption$EnumConversion");
+    J$_WRAP_CONVERT_OWNER.put("j$/nio/file/LinkOption", "java/nio/file/LinkOption$EnumConversion");
+    J$_WRAP_CONVERT_OWNER.put("j$/nio/file/Path", "java/nio/file/Path$VivifiedWrapper");
+    J$_WRAP_CONVERT_OWNER.put("j$/nio/file/WatchEvent", "java/nio/file/WatchEvent$Wrapper");
+    J$_WRAP_CONVERT_OWNER.put(
+        "j$/nio/file/attribute/BasicFileAttributes",
+        "java/nio/file/attribute/BasicFileAttributes$Wrapper");
+    J$_WRAP_CONVERT_OWNER.put(
+        "j$/nio/file/attribute/BasicFileAttributeView",
+        "java/nio/file/attribute/BasicFileAttributeView$Wrapper");
+    J$_WRAP_CONVERT_OWNER.put(
+        "j$/nio/file/attribute/FileOwnerAttributeView",
+        "java/nio/file/attribute/FileOwnerAttributeView$Wrapper");
+    J$_WRAP_CONVERT_OWNER.put(
+        "j$/nio/file/attribute/PosixFileAttributes",
+        "java/nio/file/attribute/PosixFileAttributes$Wrapper");
+    J$_WRAP_CONVERT_OWNER.put(
+        "j$/nio/file/attribute/PosixFileAttributeView",
+        "java/nio/file/attribute/PosixFileAttributeView$Wrapper");
+    J$_WRAP_CONVERT_OWNER.put(
+        "j$/nio/file/attribute/PosixFilePermission",
+        "java/nio/file/attribute/PosixFilePermission$EnumConversion");
+    J$_WRAP_CONVERT_OWNER.put(
+        "j$/util/stream/Collector$Characteristics",
+        "java/util/stream/Collector$Characteristics$EnumConversion");
+  }
+
+  public static Path convertJar(Path jar, CustomConversionVersion legacy) {
+    String fileName = jar.getFileName().toString();
+    String newFileName =
+        fileName.substring(0, fileName.length() - ".jar".length())
+            + (legacy == LEGACY ? "_legacy" : "")
+            + "_converted.jar";
+    Path convertedJar = jar.getParent().resolve(newFileName);
+    return internalConvert(jar, convertedJar, legacy);
+  }
+
+  private static synchronized Path internalConvert(
+      Path jar, Path convertedJar, CustomConversionVersion legacy) {
+    if (Files.exists(convertedJar)) {
+      return convertedJar;
+    }
+
+    OpenOption[] options =
+        new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
+    try (ZipOutputStream out =
+        new ZipOutputStream(
+            new BufferedOutputStream(Files.newOutputStream(convertedJar, options)))) {
+      new ConversionConverter().convert(jar, out, legacy);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    return convertedJar;
+  }
+
+  private void convert(
+      Path desugaredLibraryFiles, ZipOutputStream out, CustomConversionVersion legacy)
+      throws IOException {
+    ZipUtils.iter(
+        desugaredLibraryFiles,
+        ((entry, input) -> {
+          if (!entry.getName().endsWith(".class")) {
+            return;
+          }
+          if (legacy == LEGACY
+              && (entry.getName().contains("nio.file") || entry.getName().contains("ApiFlips"))) {
+            return;
+          }
+          final byte[] bytes = StreamUtils.streamToByteArrayClose(input);
+          final byte[] rewrittenBytes =
+              transformInvoke(entry.getName().substring(0, entry.getName().length() - 6), bytes);
+          ZipUtils.writeToZipStream(out, entry.getName(), rewrittenBytes, ZipEntry.STORED);
+        }));
+  }
+
+  private byte[] transformInvoke(String descriptor, byte[] bytes) {
+    return ClassFileTransformer.create(bytes, Reference.classFromDescriptor(descriptor))
+        .addMethodTransformer(getMethodTransformer())
+        .transform();
+  }
+
+  private MethodTransformer getMethodTransformer() {
+    return new MethodTransformer() {
+      @Override
+      public void visitMethodInsn(
+          int opcode, String owner, String name, String descriptor, boolean isInterface) {
+        if (opcode == Opcodes.INVOKESTATIC && name.equals("wrap_convert")) {
+          if (!JAVA_WRAP_CONVERT_OWNER.containsKey(owner)
+              || !J$_WRAP_CONVERT_OWNER.containsKey(owner)) {
+            throw new RuntimeException("Cannot transform wrap_convert method for " + owner);
+          }
+          if (owner.startsWith("java")) {
+            String newOwner = J$_WRAP_CONVERT_OWNER.get(owner);
+            super.visitMethodInsn(opcode, newOwner, "convert", descriptor, isInterface);
+            return;
+          } else if (owner.startsWith("j$")) {
+            String newOwner = JAVA_WRAP_CONVERT_OWNER.get(owner);
+            super.visitMethodInsn(opcode, newOwner, "convert", descriptor, isInterface);
+            return;
+          } else {
+            throw new RuntimeException("Cannot transform wrap_convert method for " + owner);
+          }
+        }
+        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+      }
+    };
+  }
+}
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 e2ecec4..c9c9a31 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
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.transformers.MethodTransformer;
 import com.android.tools.r8.utils.StreamUtils;
 import com.android.tools.r8.utils.ZipUtils;
@@ -74,7 +75,7 @@
   }
 
   private byte[] transformInvoke(String descriptor, byte[] bytes) {
-    return transformer(bytes, Reference.classFromDescriptor(descriptor))
+    return ClassFileTransformer.create(bytes, Reference.classFromDescriptor(descriptor))
         .addMethodTransformer(getMethodTransformer())
         .transform();
   }
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
index 7848f02..c40b5c4 100644
--- 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
@@ -74,7 +74,7 @@
   }
 
   @Test
-  public void test() throws Exception {
+  public void test() throws Throwable {
     testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
         .addInnerClasses(getClass())
         .addProgramClasses(GoogleIcon.class)
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
index 1e4a04e..9508e8c 100644
--- 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
@@ -12,7 +12,6 @@
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
@@ -30,6 +29,7 @@
 import java.nio.file.attribute.PosixFilePermission;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -39,7 +39,7 @@
 @RunWith(Parameterized.class)
 public class FilesTest extends DesugaredLibraryTestBase {
 
-  private static final String EXPECTED_RESULT =
+  private static final String EXPECTED_RESULT_DESUGARING_FILE_SYSTEM =
       StringUtils.lines(
           "bytes written: 11",
           "String written: Hello World",
@@ -49,8 +49,9 @@
           "String read: Hello World",
           "null",
           "true",
-          "unsupported");
-  private static final String EXPECTED_RESULT_24_26 =
+          "unsupported",
+          "j$.nio.file.attribute");
+  private static final String EXPECTED_RESULT_DESUGARING_FILE_SYSTEM_PLATFORM_CHANNEL =
       StringUtils.lines(
           "bytes written: 11",
           "String written: Hello World",
@@ -60,8 +61,9 @@
           "unsupported",
           "null",
           "true",
-          "unsupported");
-  private static final String EXPECTED_RESULT_26 =
+          "unsupported",
+          "j$.nio.file.attribute");
+  private static final String EXPECTED_RESULT_PLATFORM_FILE_SYSTEM_DESUGARING =
       StringUtils.lines(
           "bytes written: 11",
           "String written: Hello World",
@@ -71,7 +73,20 @@
           "String read: Hello World",
           "true",
           "true",
-          "true");
+          "true",
+          "j$.nio.file.attribute");
+  private static final String EXPECTED_RESULT_PLATFORM_FILE_SYSTEM =
+      StringUtils.lines(
+          "bytes written: 11",
+          "String written: Hello World",
+          "bytes read: 11",
+          "String read: Hello World",
+          "bytes read: 11",
+          "String read: Hello World",
+          "true",
+          "true",
+          "true",
+          "java.nio.file.attribute");
 
   private final TestParameters parameters;
   private final LibraryDesugaringSpecification libraryDesugaringSpecification;
@@ -100,16 +115,18 @@
   }
 
   private String getExpectedResult() {
-    if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O)) {
-      return EXPECTED_RESULT_26;
+    if (libraryDesugaringSpecification.usesPlatformFileSystem(parameters)) {
+      return libraryDesugaringSpecification.hasNioFileDesugaring(parameters)
+          ? EXPECTED_RESULT_PLATFORM_FILE_SYSTEM_DESUGARING
+          : EXPECTED_RESULT_PLATFORM_FILE_SYSTEM;
     }
-    return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)
-        ? EXPECTED_RESULT_24_26
-        : EXPECTED_RESULT;
+    return libraryDesugaringSpecification.hasNioChannelDesugaring(parameters)
+        ? EXPECTED_RESULT_DESUGARING_FILE_SYSTEM
+        : EXPECTED_RESULT_DESUGARING_FILE_SYSTEM_PLATFORM_CHANNEL;
   }
 
   @Test
-  public void test() throws Exception {
+  public void test() throws Throwable {
     testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
@@ -126,6 +143,12 @@
       readWriteThroughFilesAPI(path);
       readThroughFileChannelAPI(path);
       attributeAccess(path);
+      fspMethodsWithGeneric(path);
+    }
+
+    private static void fspMethodsWithGeneric(Path path) throws IOException {
+      Map<String, Object> mapping = Files.readAttributes(path, "lastModifiedTime");
+      System.out.println(mapping.values().iterator().next().getClass().getPackage().getName());
     }
 
     private static void attributeAccess(Path path) throws IOException {
@@ -146,7 +169,7 @@
 
       try {
         PosixFileAttributes posixAttributes = Files.readAttributes(path, PosixFileAttributes.class);
-        if (attributes != null) {
+        if (posixAttributes != null) {
           System.out.println(
               posixAttributes.permissions().contains(PosixFilePermission.OWNER_READ));
         } else {
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
index 542ac06..26e16bc 100644
--- 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
@@ -28,7 +28,14 @@
   private final LibraryDesugaringSpecification libraryDesugaringSpecification;
   private final CompilationSpecification compilationSpecification;
 
-  private static final String EXPECTED_RESULT = StringUtils.lines("x.txt", "dir", "dir/x.txt", "/");
+  private static final String EXPECTED_RESULT_DESUGARING =
+      StringUtils.lines(
+          "x.txt", "dir", "dir/x.txt", "/", "class j$.desugar.sun.nio.fs.DesugarLinuxFileSystem");
+  private static final String EXPECTED_RESULT_DESUGARING_PLATFORM_FILE_SYSTEM =
+      StringUtils.lines(
+          "x.txt", "dir", "dir/x.txt", "/", "class j$.nio.file.FileSystem$VivifiedWrapper");
+  private static final String EXPECTED_RESULT_NO_DESUGARING =
+      StringUtils.lines("x.txt", "dir", "dir/x.txt", "/", "class sun.nio.fs.LinuxFileSystem");
 
   @Parameters(name = "{0}, spec: {1}, {2}")
   public static List<Object[]> data() {
@@ -47,13 +54,25 @@
     this.compilationSpecification = compilationSpecification;
   }
 
+  private String getExpectedResult() {
+    if (!libraryDesugaringSpecification.hasNioFileDesugaring(parameters)) {
+      return EXPECTED_RESULT_NO_DESUGARING;
+    }
+    return libraryDesugaringSpecification.usesPlatformFileSystem(parameters)
+        ? EXPECTED_RESULT_DESUGARING_PLATFORM_FILE_SYSTEM
+        : EXPECTED_RESULT_DESUGARING;
+  }
+
   @Test
-  public void test() throws Exception {
+  public void test() throws Throwable {
     testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addL8KeepRules("-keepnames class j$.desugar.sun.nio.fs.**")
+        .addL8KeepRules("-keepnames class j$.nio.file.FileSystem**")
         .addInnerClasses(PathTest.class)
         .addKeepMainRule(TestClass.class)
+        .compile()
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(EXPECTED_RESULT);
+        .assertSuccessWithOutput(getExpectedResult());
   }
 
   public static class TestClass {
@@ -67,6 +86,7 @@
       Path resolve = path2.resolve(path1);
       System.out.println(resolve);
       System.out.println(resolve.getFileSystem().getSeparator());
+      System.out.println(resolve.getFileSystem().getClass());
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StreamCollectorCharacteristicsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StreamCollectorCharacteristicsTest.java
new file mode 100644
index 0000000..56bfcd6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StreamCollectorCharacteristicsTest.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.desugar.desugaredlibrary.jdk11;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CustomLibrarySpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+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 StreamCollectorCharacteristicsTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
+
+  private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.O;
+  private static final String EXPECTED_RESULT = StringUtils.lines("IDENTITY_FINISH");
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getConversionParametersUpToExcluding(MIN_SUPPORTED),
+        ImmutableList.of(JDK11),
+        DEFAULT_SPECIFICATIONS);
+  }
+
+  public StreamCollectorCharacteristicsTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
+    this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addProgramClasses(TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .setCustomLibrarySpecification(
+            new CustomLibrarySpecification(CustomLib.class, MIN_SUPPORTED))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  public static class CustomLib {
+    public static Collector<?, ?, ?> getCollector() {
+      return Collectors.toList();
+    }
+  }
+
+  public static class TestClass {
+    public static void main(String[] args) {
+      System.out.println(CustomLib.getCollector().characteristics().iterator().next().toString());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11NioFileTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11NioFileTests.java
index 5cb8e57..8168e33 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11NioFileTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11NioFileTests.java
@@ -136,7 +136,6 @@
           "WatchServiceBasic",
           "WatchServiceFileTreeModifier",
           "WatchServiceDeleteInterference",
-          "WatchServiceMayFlies",
           "WatchServiceLotsOfCancels",
           "WatchServiceSensitivityModifier");
   private static final List<String> FAILING_MAIN_TESTS =
@@ -157,6 +156,7 @@
           "FilesTemporaryFiles",
           "FilesCheckPermissions",
           "FilesMisc",
+          "WatchServiceMayFlies", // Works but longest to run by far.
           "WatchServiceWithSecurityManager",
           "WatchServiceUpdateInterference",
           "WatchServiceLotsOfCloses");
@@ -272,10 +272,12 @@
             .compile()
             .withArt6Plus64BitsLib();
     int success = 0;
+    int failures = 0;
     for (String mainTestClass : SUCCESSFUL_MAIN_TESTS) {
       SingleTestRunResult<?> run = compileResult.run(parameters.getRuntime(), mainTestClass);
       if (run.getExitCode() != 0) {
         System.out.println("Main Fail " + mainTestClass);
+        failures++;
       } else {
         success++;
       }
@@ -286,6 +288,7 @@
               parameters.getRuntime(), "TestNGMainRunner", verbosity, testNGTestClass);
       if (!result.getStdOut().contains(StringUtils.lines(testNGTestClass + ": SUCCESS"))) {
         System.out.println("TestNG Fail " + testNGTestClass);
+        failures++;
       } else {
         success++;
       }
@@ -293,7 +296,9 @@
     // TODO(b/234689867): Understand and fix these issues.
     // Most issues seem to come from the missing secure.properties file. This file is not accessed
     // in all tests on all API levels, hence a different number of failures on each level.
-    assertTrue(success >= 15);
+    System.out.println("Successes :" + success + "; failures " + failures);
+    assertTrue(success >= 11);
+    assertTrue(failures <= 20);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
index b9ca3f1..5a2c8b0 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
@@ -44,6 +44,7 @@
   private final LibraryDesugaringSpecification libraryDesugaringSpecification;
   private final CompilationSpecification compilationSpecification;
   private final TestCompilerBuilder<?, ?, ?, ? extends SingleTestRunResult<?>, ?> builder;
+  private String l8ExtraKeepRules = "";
   private Consumer<InternalOptions> l8OptionModifier = ConsumerUtils.emptyConsumer();
   private boolean l8FinalPrefixVerification = true;
 
@@ -205,6 +206,13 @@
     return this;
   }
 
+  public DesugaredLibraryTestBuilder<T> addL8KeepRules(String keepRules) {
+    if (compilationSpecification.isL8Shrink()) {
+      l8ExtraKeepRules += keepRules + "\n";
+    }
+    return this;
+  }
+
   public DesugaredLibraryTestBuilder<T> addKeepClassAndMembersRules(Class<?>... clazz) {
     withR8TestBuilder(b -> b.addKeepClassAndMembersRules(clazz));
     return this;
@@ -348,7 +356,7 @@
     L8TestCompileResult nonShrunk =
         test.testForL8(parameters.getApiLevel(), Backend.CF)
             .apply(libraryDesugaringSpecification::configureL8TestBuilder)
-            .apply(this::configure)
+            .apply(b -> configure(b, Backend.CF))
             .compile();
     String keepRules =
         collectKeepRulesWithTraceReferences(compile.writeToZip(), nonShrunk.writeToZip());
@@ -362,13 +370,16 @@
             b ->
                 libraryDesugaringSpecification.configureL8TestBuilder(
                     b, compilationSpecification.isL8Shrink(), keepRule))
-        .apply(this::configure)
+        .apply(b -> configure(b, parameters.getBackend()))
         .compile();
   }
 
-  private void configure(L8TestBuilder l8Builder) {
+  private void configure(L8TestBuilder l8Builder, Backend backend) {
     l8Builder
         .applyIf(!l8FinalPrefixVerification, L8TestBuilder::ignoreFinalPrefixVerification)
+        .applyIf(
+            compilationSpecification.isL8Shrink() && !backend.isCf() && !l8ExtraKeepRules.isEmpty(),
+            b -> b.addKeepRules(l8ExtraKeepRules))
         .addOptionsModifier(l8OptionModifier);
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
index 539ad9a..143a52d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
@@ -7,10 +7,13 @@
 import static com.android.tools.r8.ToolHelper.DESUGARED_JDK_8_LIB_JAR;
 import static com.android.tools.r8.ToolHelper.DESUGARED_LIB_RELEASES_DIR;
 import static com.android.tools.r8.ToolHelper.getUndesugaredJdk11LibJarForTesting;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.CustomConversionVersion.LATEST;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.CustomConversionVersion.LEGACY;
 
 import com.android.tools.r8.L8TestBuilder;
 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.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -73,6 +76,11 @@
     }
   }
 
+  public enum CustomConversionVersion {
+    LEGACY,
+    LATEST
+  }
+
   // Main head specifications.
   public static LibraryDesugaringSpecification JDK8 =
       new LibraryDesugaringSpecification(
@@ -80,28 +88,32 @@
           DESUGARED_JDK_8_LIB_JAR,
           "desugar_jdk_libs.json",
           AndroidApiLevel.P,
-          JDK8_DESCRIPTOR);
+          JDK8_DESCRIPTOR,
+          LEGACY);
   public static LibraryDesugaringSpecification JDK11 =
       new LibraryDesugaringSpecification(
           "JDK11",
           getUndesugaredJdk11LibJarForTesting(),
           "jdk11/desugar_jdk_libs.json",
           AndroidApiLevel.R,
-          JDK11_DESCRIPTOR);
+          JDK11_DESCRIPTOR,
+          LATEST);
   public static LibraryDesugaringSpecification JDK11_MINIMAL =
       new LibraryDesugaringSpecification(
           "JDK11_MINIMAL",
           getUndesugaredJdk11LibJarForTesting(),
           "jdk11/desugar_jdk_libs_minimal.json",
           AndroidApiLevel.R,
-          EMPTY_DESCRIPTOR_24);
+          EMPTY_DESCRIPTOR_24,
+          LATEST);
   public static LibraryDesugaringSpecification JDK11_PATH =
       new LibraryDesugaringSpecification(
           "JDK11_PATH",
           getUndesugaredJdk11LibJarForTesting(),
           "jdk11/desugar_jdk_libs_path.json",
           AndroidApiLevel.R,
-          JDK11_PATH_DESCRIPTOR);
+          JDK11_PATH_DESCRIPTOR,
+          LATEST);
 
   // Legacy specifications.
   public static LibraryDesugaringSpecification JDK11_PATH_ALTERNATIVE_3 =
@@ -110,14 +122,16 @@
           getUndesugaredJdk11LibJarForTesting(),
           "jdk11/desugar_jdk_libs_path_alternative_3.json",
           AndroidApiLevel.R,
-          JDK11_PATH_DESCRIPTOR);
+          JDK11_PATH_DESCRIPTOR,
+          LATEST);
   public static LibraryDesugaringSpecification JDK11_CHM_ONLY =
       new LibraryDesugaringSpecification(
           "JDK11_CHM_ONLY",
           getUndesugaredJdk11LibJarForTesting(),
           "jdk11/chm_only_desugar_jdk_libs.json",
           AndroidApiLevel.R,
-          EMPTY_DESCRIPTOR_24);
+          EMPTY_DESCRIPTOR_24,
+          LATEST);
   public static LibraryDesugaringSpecification JDK11_LEGACY =
       new LibraryDesugaringSpecification(
           "JDK11_LEGACY",
@@ -125,7 +139,8 @@
           DESUGARED_JDK_11_LIB_JAR,
           "jdk11/desugar_jdk_libs_legacy.json",
           AndroidApiLevel.R,
-          JDK11_LEGACY_DESCRIPTOR);
+          JDK11_LEGACY_DESCRIPTOR,
+          LEGACY);
   public static final LibraryDesugaringSpecification RELEASED_1_0_9 =
       new LibraryDesugaringSpecification("1.0.9", AndroidApiLevel.P);
   public static final LibraryDesugaringSpecification RELEASED_1_0_10 =
@@ -149,10 +164,11 @@
       Path desugarJdkLibs,
       String specificationPath,
       AndroidApiLevel androidJarLevel,
-      Descriptor descriptor) {
+      Descriptor descriptor,
+      CustomConversionVersion legacy) {
     this(
         name,
-        ImmutableSet.of(desugarJdkLibs, ToolHelper.DESUGAR_LIB_CONVERSIONS),
+        ImmutableSet.of(desugarJdkLibs, ToolHelper.getConvertedDesugaredLibConversions(legacy)),
         Paths.get("src/library_desugar/" + specificationPath),
         ImmutableSet.of(ToolHelper.getAndroidJar(androidJarLevel)),
         descriptor,
@@ -254,6 +270,14 @@
     return parameters.getApiLevel().getLevel() < descriptor.getNioFileDesugaring();
   }
 
+  public boolean hasNioChannelDesugaring(TestParameters parameters) {
+    return hasNioFileDesugaring(parameters) && parameters.getApiLevel().getLevel() < 24;
+  }
+
+  public boolean usesPlatformFileSystem(TestParameters parameters) {
+    return parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V8_1_0);
+  }
+
   public boolean hasAnyDesugaring(TestParameters parameters) {
     return hasAnyDesugaring(parameters.getApiLevel());
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
index 6fa1514..04e2842 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
@@ -74,7 +74,6 @@
                 builder.addOptionsModification(
                     options -> {
                       options.desugarState = DesugarState.ON;
-                      options.cfToCfDesugar = true;
                     }))
         .compile()
         .inspect(inspector -> assertNests(inspector, desugar))
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultMethodResolvingToLibraryTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultMethodResolvingToLibraryTest.java
index 6e19e01..46bfd05 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultMethodResolvingToLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultMethodResolvingToLibraryTest.java
@@ -7,14 +7,12 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import java.util.function.Function;
@@ -34,7 +32,7 @@
     return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
   }
 
-  private static final String EXPECTED_OUTPUT = StringUtils.lines("8");
+  private static final String EXPECTED_OUTPUT = "8";
 
   private void inspect(CodeInspector inspector) {
     assertTrue(
@@ -47,6 +45,10 @@
             .noneMatch(name -> name.endsWith("$-CC")));
   }
 
+  private String getExpectedOutputForApiCheck() {
+    return parameters.getApiLevel().isLessThan(AndroidApiLevel.N) ? "No call" : EXPECTED_OUTPUT;
+  }
+
   @Test
   public void testDesugaring() throws Exception {
     testForD8(parameters.getBackend())
@@ -64,7 +66,7 @@
                     .maxSupportedApiLevel()
                     .isLessThan(AndroidApiLevel.N),
             r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
-            r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+            r -> r.assertSuccessWithOutputLines(EXPECTED_OUTPUT));
   }
 
   @Test
@@ -73,59 +75,22 @@
     testForD8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
-        .addAndroidBuildVersion(parameters.getRuntime().asDex().maxSupportedApiLevel())
+        .addAndroidBuildVersion(parameters.getApiLevel())
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClassWithApiLevelCheck.class)
-        .applyIf(
-            parameters.isDexRuntime()
-                && parameters
-                    .getRuntime()
-                    .asDex()
-                    .maxSupportedApiLevel()
-                    .isLessThan(AndroidApiLevel.N),
-            r -> r.assertSuccessWithOutputLines("No call"),
-            r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+        .assertSuccessWithOutputLines(getExpectedOutputForApiCheck());
   }
 
   @Test
   public void testR8() throws Exception {
-    try {
-      testForR8(parameters.getBackend())
-          .addInnerClasses(getClass())
-          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
-          .setMinApi(parameters.getApiLevel())
-          .addKeepMainRule(TestClass.class)
-          .compile()
-          // .inspect(this::inspect)
-          .run(parameters.getRuntime(), TestClass.class)
-          .applyIf(
-              parameters.isDexRuntime()
-                  && parameters
-                      .getRuntime()
-                      .asDex()
-                      .maxSupportedApiLevel()
-                      .isLessThan(AndroidApiLevel.N),
-              r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
-              r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
-    } catch (CompilationFailedException e) {
-      // TODO(b/235184674): Fix this.
-      assertTrue(parameters.isCfRuntime());
-    }
-  }
-
-  // TODO(b/235184674): Fix this.
-  @Test(expected = CompilationFailedException.class)
-  public void testR8WithApiLevelCheck() throws Exception {
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
         .setMinApi(parameters.getApiLevel())
-        .addKeepMainRule(TestClassWithApiLevelCheck.class)
-        .compile()
-        .inspect(this::inspect)
-        .run(parameters.getRuntime(), TestClassWithApiLevelCheck.class)
+        .addKeepMainRule(TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
         .applyIf(
             parameters.isDexRuntime()
                 && parameters
@@ -134,7 +99,19 @@
                     .maxSupportedApiLevel()
                     .isLessThan(AndroidApiLevel.N),
             r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
-            r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+            r -> r.assertSuccessWithOutputLines(EXPECTED_OUTPUT));
+  }
+
+  @Test
+  public void testR8WithApiLevelCheck() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClassWithApiLevelCheck.class)
+        .addAndroidBuildVersion(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClassWithApiLevelCheck.class)
+        .assertSuccessWithOutputLines(getExpectedOutputForApiCheck());
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DevirtualizationAcrossFeatureSplitTest.java b/src/test/java/com/android/tools/r8/dexsplitter/DevirtualizationAcrossFeatureSplitTest.java
index 538f764..0553efe 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DevirtualizationAcrossFeatureSplitTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DevirtualizationAcrossFeatureSplitTest.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -29,6 +30,8 @@
   public void test() throws Exception {
     testForR8(parameters.getBackend())
         .addProgramClasses(BaseClass.class, BaseInterface.class)
+        // Link against android.jar that contains ReflectiveOperationException.
+        .addLibraryFiles(parameters.getDefaultAndroidJarAbove(AndroidApiLevel.K))
         .addFeatureSplitRuntime()
         .addFeatureSplit(FeatureMain.class, BaseInterfaceImpl.class)
         .addKeepFeatureMainRules(FeatureMain.class)
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 71e7df5..f56464c 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java
@@ -5,22 +5,16 @@
 package com.android.tools.r8.dexsplitter;
 
 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.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestShrinkerBuilder;
-import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
 import com.google.common.collect.ImmutableSet;
@@ -67,7 +61,10 @@
                           Object.class.getTypeName(),
                           fieldSubject.getField().getType().getTypeName());
                     }),
-            ThrowableConsumer.empty());
+            // Link against android.jar that contains ReflectiveOperationException.
+            testBuilder ->
+                testBuilder.addLibraryFiles(
+                    parameters.getDefaultAndroidJarAbove(AndroidApiLevel.K)));
     assertEquals(processResult.exitCode, 0);
     assertEquals(processResult.stdout, EXPECTED);
   }
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 53af2b6..dfc4327 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
@@ -4,28 +4,20 @@
 
 package com.android.tools.r8.dexsplitter;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static junit.framework.TestCase.assertEquals;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.R8FullTestBuilder;
-import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.ThrowingConsumer;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
-import java.util.function.Consumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -52,7 +44,11 @@
     assumeTrue(parameters.isDexRuntime());
     ThrowableConsumer<R8FullTestBuilder> configurator =
         r8FullTestBuilder ->
-            r8FullTestBuilder.enableNoVerticalClassMergingAnnotations().noMinification();
+            r8FullTestBuilder
+                // Link against android.jar that contains ReflectiveOperationException.
+                .addLibraryFiles(parameters.getDefaultAndroidJarAbove(AndroidApiLevel.K))
+                .enableNoVerticalClassMergingAnnotations()
+                .noMinification();
     ProcessResult processResult =
         testR8Splitter(
             parameters,
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 a2e1b1d..ad8ab38 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
@@ -4,24 +4,17 @@
 
 package com.android.tools.r8.dexsplitter;
 
-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.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.ThrowingConsumer;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
 import org.junit.Test;
@@ -56,7 +49,11 @@
             FeatureClass.class,
             ThrowableConsumer.empty(),
             testBuilder ->
-                testBuilder.enableInliningAnnotations().addDontObfuscate(FeatureEnum.class));
+                testBuilder
+                    // Link against android.jar that contains ReflectiveOperationException.
+                    .addLibraryFiles(parameters.getDefaultAndroidJarAbove(AndroidApiLevel.K))
+                    .addDontObfuscate(FeatureEnum.class)
+                    .enableInliningAnnotations());
     assertEquals(processResult.exitCode, 0);
     assertEquals(processResult.stdout, EXPECTED);
   }
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 a7d137f..c83e290 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
@@ -4,10 +4,7 @@
 
 package com.android.tools.r8.dexsplitter;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
@@ -15,17 +12,14 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.R8FullTestBuilder;
-import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.ThrowingConsumer;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
-import java.util.function.Consumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -52,7 +46,11 @@
     assumeTrue(parameters.isDexRuntime());
     ThrowableConsumer<R8FullTestBuilder> configurator =
         r8FullTestBuilder ->
-            r8FullTestBuilder.enableNoVerticalClassMergingAnnotations().noMinification();
+            r8FullTestBuilder
+                // Link against android.jar that contains ReflectiveOperationException.
+                .addLibraryFiles(parameters.getDefaultAndroidJarAbove(AndroidApiLevel.K))
+                .enableNoVerticalClassMergingAnnotations()
+                .noMinification();
     ProcessResult processResult =
         testR8Splitter(
             parameters,
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java
index b5940d4..b2d8fe8 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -38,7 +39,7 @@
 @RunWith(Parameterized.class)
 public class R8FeatureSplitTest extends SplitterTestBase {
 
-  private static String EXPECTED = "Hello world";
+  private static final String EXPECTED = "Hello world";
 
   @Parameters(name = "{0}")
   public static TestParametersCollection params() {
@@ -62,7 +63,7 @@
   public void simpleApiTest() throws CompilationFailedException, IOException, ExecutionException {
     testForR8(parameters.getBackend())
         .addProgramClasses(HelloWorld.class)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .addFeatureSplit(R8FeatureSplitTest::emptySplitProvider)
         .addKeepMainRule(HelloWorld.class)
         .compile()
@@ -129,6 +130,8 @@
 
     testForR8(parameters.getBackend())
         .addProgramClasses(BaseClass.class, RunInterface.class, SplitRunner.class)
+        // Link against android.jar that contains ReflectiveOperationException.
+        .addLibraryFiles(parameters.getDefaultAndroidJarAbove(AndroidApiLevel.K))
         .setMinApi(parameters.getApiLevel())
         .addFeatureSplit(
             builder ->
@@ -253,6 +256,8 @@
       R8TestCompileResult compileResult =
           testForR8(parameters.getBackend())
               .addProgramClasses(BaseClass.class, RunInterface.class, SplitRunner.class)
+              // Link against android.jar that contains ReflectiveOperationException.
+              .addLibraryFiles(parameters.getDefaultAndroidJarAbove(AndroidApiLevel.K))
               .setMinApi(parameters.getApiLevel())
               .addFeatureSplit(FeatureClass.class)
               .addFeatureSplit(FeatureClass2.class)
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java b/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
index 496c3d3..6576720 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.google.common.collect.ImmutableSet;
@@ -50,7 +51,11 @@
     assumeTrue(parameters.isDexRuntime());
     ThrowableConsumer<R8FullTestBuilder> configurator =
         r8FullTestBuilder ->
-            r8FullTestBuilder.enableNoVerticalClassMergingAnnotations().noMinification();
+            r8FullTestBuilder
+                // Link against android.jar that contains ReflectiveOperationException.
+                .addLibraryFiles(parameters.getDefaultAndroidJarAbove(AndroidApiLevel.K))
+                .enableNoVerticalClassMergingAnnotations()
+                .noMinification();
     ThrowableConsumer<R8TestCompileResult> ensureInlined =
         r8TestCompileResult -> {
           // Ensure that isEarly from BaseUtilClass is inlined into the feature
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/StaticClassMergingInFeatureSplitTest.java b/src/test/java/com/android/tools/r8/dexsplitter/StaticClassMergingInFeatureSplitTest.java
index a1c961e..78e7738 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/StaticClassMergingInFeatureSplitTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/StaticClassMergingInFeatureSplitTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -37,6 +38,8 @@
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
             .addProgramClasses(BaseClassA.class, BaseClassB.class)
+            // Link against android.jar that contains ReflectiveOperationException.
+            .addLibraryFiles(parameters.getDefaultAndroidJarAbove(AndroidApiLevel.K))
             .addFeatureSplitRuntime()
             .addFeatureSplit(Feature1Main.class, Feature1ClassA.class, Feature1ClassB.class)
             .addFeatureSplit(Feature2Main.class, Feature2ClassA.class, Feature2ClassB.class)
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java b/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java
index 3e86cc9..3dd0858 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.apimodel.ApiModelingTestHelper;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.google.common.collect.ImmutableSet;
@@ -93,6 +94,8 @@
 
   private void configure(R8FullTestBuilder testBuilder) throws NoSuchMethodException {
     testBuilder
+        // Link against android.jar that contains ReflectiveOperationException.
+        .addLibraryFiles(parameters.getDefaultAndroidJarAbove(AndroidApiLevel.K))
         .addKeepMethodRules(
             Reference.methodFromMethod(
                 BaseSuperClass.class.getDeclaredMethod(
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/VerticalClassMergingAcrossFeatureSplitTest.java b/src/test/java/com/android/tools/r8/dexsplitter/VerticalClassMergingAcrossFeatureSplitTest.java
index 4ff8f4b..933c3c7 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/VerticalClassMergingAcrossFeatureSplitTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/VerticalClassMergingAcrossFeatureSplitTest.java
@@ -12,9 +12,7 @@
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.dexsplitter.VerticalClassMergingInFeatureSplitTest.BaseClass;
-import com.android.tools.r8.dexsplitter.VerticalClassMergingInFeatureSplitTest.Feature1Class;
-import com.android.tools.r8.dexsplitter.VerticalClassMergingInFeatureSplitTest.Feature2Class;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -39,6 +37,8 @@
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
             .addProgramClasses(BaseClass.class)
+            // Link against android.jar that contains ReflectiveOperationException.
+            .addLibraryFiles(parameters.getDefaultAndroidJarAbove(AndroidApiLevel.K))
             .addFeatureSplitRuntime()
             .addFeatureSplit(Feature1Class.class)
             .addFeatureSplit(Feature2Main.class, Feature2Class.class)
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/VerticalClassMergingInFeatureSplitTest.java b/src/test/java/com/android/tools/r8/dexsplitter/VerticalClassMergingInFeatureSplitTest.java
index 13234ef..4e7e2d0 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/VerticalClassMergingInFeatureSplitTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/VerticalClassMergingInFeatureSplitTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -43,6 +44,8 @@
             .addFeatureSplit(
                 Feature2Main.class, Feature2Class.class, Feature2ClassWithSameFeatureSubclass.class)
             .addKeepFeatureMainRules(Feature1Main.class, Feature2Main.class)
+            // Link against android.jar that contains ReflectiveOperationException.
+            .addLibraryFiles(parameters.getDefaultAndroidJarAbove(AndroidApiLevel.K))
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
             .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/diagnostics/ApiLevelDiagnosticTest.java b/src/test/java/com/android/tools/r8/diagnostics/UnsupportedFeaturesDiagnosticsTest.java
similarity index 66%
rename from src/test/java/com/android/tools/r8/diagnostics/ApiLevelDiagnosticTest.java
rename to src/test/java/com/android/tools/r8/diagnostics/UnsupportedFeaturesDiagnosticsTest.java
index 66f976b..8133dc2 100644
--- a/src/test/java/com/android/tools/r8/diagnostics/ApiLevelDiagnosticTest.java
+++ b/src/test/java/com/android/tools/r8/diagnostics/UnsupportedFeaturesDiagnosticsTest.java
@@ -4,20 +4,26 @@
 package com.android.tools.r8.diagnostics;
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.startsWith;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.DiagnosticsMatcher;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
-// TODO(b/154778581): Extend these tests with typed diagnostics and improved information.
 @RunWith(Parameterized.class)
-public class ApiLevelDiagnosticTest extends TestBase {
+public class UnsupportedFeaturesDiagnosticsTest extends TestBase {
 
   // Hard coded messages in AGP. See D8DexArchiveBuilder.
 
@@ -35,7 +41,30 @@
     return getTestParameters().withNoneRuntime().build();
   }
 
-  public ApiLevelDiagnosticTest(TestParameters parameters) {
+  private static class FeatureMatcher extends DiagnosticsMatcher {
+    private final String descriptor;
+
+    public FeatureMatcher(String descriptor) {
+      this.descriptor = descriptor;
+    }
+
+    @Override
+    protected boolean eval(Diagnostic diagnostic) {
+      return ((UnsupportedFeatureDiagnostic) diagnostic).getFeatureDescriptor().equals(descriptor);
+    }
+
+    @Override
+    protected void explain(Description description) {
+      description.appendText("feature ").appendText(descriptor);
+    }
+  }
+
+  public static Matcher<Diagnostic> matches(String descriptor) {
+    return allOf(
+        diagnosticType(UnsupportedFeatureDiagnostic.class), new FeatureMatcher(descriptor));
+  }
+
+  public UnsupportedFeaturesDiagnosticsTest(TestParameters parameters) {
     parameters.assertNoneRuntime();
   }
 
@@ -49,7 +78,10 @@
             diagnostics -> {
               diagnostics
                   .assertOnlyErrors()
-                  .assertErrorsMatch(diagnosticMessage(startsWith(AGP_INVOKE_CUSTOM)));
+                  .assertErrorsMatch(
+                      allOf(
+                          matches("invoke-custom"),
+                          diagnosticMessage(startsWith(AGP_INVOKE_CUSTOM))));
             });
   }
 
@@ -63,7 +95,10 @@
             diagnostics -> {
               diagnostics
                   .assertOnlyErrors()
-                  .assertErrorsMatch(diagnosticMessage(startsWith(AGP_DEFAULT_INTERFACE_METHOD)));
+                  .assertErrorsMatch(
+                      allOf(
+                          matches("default-interface-method"),
+                          diagnosticMessage(startsWith(AGP_DEFAULT_INTERFACE_METHOD))));
             });
   }
 
@@ -77,7 +112,10 @@
             diagnostics -> {
               diagnostics
                   .assertOnlyErrors()
-                  .assertErrorsMatch(diagnosticMessage(startsWith(AGP_STATIC_INTERFACE_METHOD)));
+                  .assertErrorsMatch(
+                      allOf(
+                          matches("static-interface-method"),
+                          diagnosticMessage(startsWith(AGP_STATIC_INTERFACE_METHOD))));
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
index cb43c01..5b2431d 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertTrue;
 
@@ -15,6 +18,7 @@
 import com.android.tools.r8.transformers.ClassTransformer;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -147,7 +151,18 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(MainClassFailing.class)
         .addOptionsModification(o -> o.testing.allowTypeErrors = true)
+        .allowDiagnosticWarningMessages()
         .enableNoMethodStaticizingAnnotations()
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics.assertWarningsMatch(
+                    allOf(
+                        diagnosticType(UnverifiableCfCodeDiagnostic.class),
+                        diagnosticMessage(
+                            containsString(
+                                "Unverifiable code in `void "
+                                    + InvokerClass.class.getTypeName()
+                                    + ".invokeSubLevel2MethodOnSubClassOfInvokerClass()`")))))
         .run(parameters.getRuntime(), MainClassFailing.class)
         .apply(r -> checkNonVerifyingResult(r, true));
   }
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToEnumUnboxedMethodTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToEnumUnboxedMethodTest.java
new file mode 100644
index 0000000..c40df02
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToEnumUnboxedMethodTest.java
@@ -0,0 +1,72 @@
+// 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.graph.invokespecial;
+
+import static com.android.tools.r8.graph.invokespecial.InvokeSpecialToEnumUnboxedMethodTest.MyEnum.TEST_1;
+import static com.android.tools.r8.graph.invokespecial.InvokeSpecialToEnumUnboxedMethodTest.MyEnum.TEST_2;
+import static org.junit.Assert.assertThrows;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DiagnosticsMatcher;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvokeSpecialToEnumUnboxedMethodTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForR8Compat(parameters.getBackend())
+                .addInnerClasses(getClass())
+                .setMinApi(parameters.getApiLevel())
+                .addKeepMainRule(Main.class)
+                .enableInliningAnnotations()
+                // TODO(b/235817866): Should not have invalid assert.
+                .compileWithExpectedDiagnostics(
+                    diagnostics ->
+                        diagnostics.assertErrorThatMatches(
+                            DiagnosticsMatcher.diagnosticException(AssertionError.class))));
+  }
+
+  public enum MyEnum {
+    TEST_1("Foo"),
+    TEST_2("Bar");
+
+    private final String str;
+
+    MyEnum(String str) {
+      this.str = str;
+    }
+
+    @NeverInline
+    private String getStr() {
+      return str;
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      System.out.println((args.length == 0 ? TEST_1 : TEST_2).getStr());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java
index b8dd4fc..043dcb7 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java
@@ -4,12 +4,17 @@
 
 package com.android.tools.r8.graph.invokespecial;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
 import java.util.List;
@@ -49,9 +54,12 @@
         .assertFailureWithErrorThatThrowsIf(!isExpectedToSucceedWithD8(), VerifyError.class);
   }
 
+  private boolean isExpectedToSucceedWithJvm() {
+    return holder == C.class;
+  }
+
   private boolean isExpectedToSucceedWithD8() {
-    if (holder == C.class) {
-      // Should always succeed.
+    if (isExpectedToSucceedWithJvm()) {
       return true;
     }
     // TODO(b/144410139): Consider making this a compilation failure instead.
@@ -72,7 +80,22 @@
         .addProgramClasses(EmptySubC.class, C.class, D.class, Main.class)
         .addProgramClassFileData(getClassBWithTransformedInvoked(holder))
         .addKeepMainRule(Main.class)
+        .allowDiagnosticWarningMessages(!isExpectedToSucceedWithJvm())
         .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              if (!isExpectedToSucceedWithJvm()) {
+                diagnostics.assertWarningsMatch(
+                    allOf(
+                        diagnosticType(UnverifiableCfCodeDiagnostic.class),
+                        diagnosticMessage(
+                            containsString(
+                                "Unverifiable code in `"
+                                    + "void "
+                                    + B.class.getTypeName()
+                                    + ".callPrint()`"))));
+              }
+            })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("D");
   }
diff --git a/src/test/java/com/android/tools/r8/internal/ClankDepsTest.java b/src/test/java/com/android/tools/r8/internal/ClankDepsTest.java
index 183031e..ddd9bd4 100644
--- a/src/test/java/com/android/tools/r8/internal/ClankDepsTest.java
+++ b/src/test/java/com/android/tools/r8/internal/ClankDepsTest.java
@@ -3,14 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.internal;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static com.android.tools.r8.utils.codeinspector.Matchers.proguardConfigurationRuleDoesNotMatch;
 import static com.android.tools.r8.utils.codeinspector.Matchers.typeVariableNotInScope;
+import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import org.junit.Test;
@@ -48,10 +53,20 @@
         .allowUnusedProguardConfigurationRules()
         .allowUnnecessaryDontWarnWildcards()
         .setMinApi(AndroidApiLevel.N)
-        .allowDiagnosticInfoMessages()
+        .allowDiagnosticMessages()
         .compileWithExpectedDiagnostics(
             diagnostics ->
-                diagnostics.assertAllInfosMatch(
-                    anyOf(typeVariableNotInScope(), proguardConfigurationRuleDoesNotMatch())));
+                diagnostics
+                    .assertAllInfosMatch(
+                        anyOf(typeVariableNotInScope(), proguardConfigurationRuleDoesNotMatch()))
+                    .assertWarningsMatch(
+                        allOf(
+                            diagnosticType(UnverifiableCfCodeDiagnostic.class),
+                            diagnosticMessage(
+                                containsString(
+                                    "Unverifiable code in `"
+                                        + "void zzz.com.facebook.litho.ComponentHost"
+                                        + ".refreshAccessibilityDelegatesIfNeeded(boolean)`"))))
+                    .assertNoErrors());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java b/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
index 4ad8b97..60f6fa7 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
@@ -127,6 +127,23 @@
             anyOf(
                 containsString("Expected stack map table for method with non-linear control flow."),
                 containsString("Ignoring option: -outjars"),
+                containsString(
+                    "Unverifiable code in `"
+                        + "java.net.Socket com.google.android.gms.org.conscrypt."
+                        + "KitKatPlatformOpenSSLSocketAdapterFactory.wrap("
+                        + "com.google.android.gms.org.conscrypt.OpenSSLSocketImpl)`"),
+                containsString(
+                    "Unverifiable code in `"
+                        + "java.net.Socket com.google.android.gms.org.conscrypt."
+                        + "PreKitKatPlatformOpenSSLSocketAdapterFactory.wrap("
+                        + "com.google.android.gms.org.conscrypt.OpenSSLSocketImpl)`"),
+                containsString(
+                    "Unverifiable code in `"
+                        + "android.content.pm.PackageStats com.google.android.libraries.performance"
+                        + ".primes.metriccapture.PackageStatsCapture"
+                        + ".getPackageStatsUsingInternalAPI(android.content.Context, long, "
+                        + "com.google.android.libraries.performance.primes.metriccapture."
+                        + "PackageStatsCapture$PackageStatsInvocation[])`"),
                 allOf(
                     startsWith(
                         "Rule matches the static final field "
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreV10ProguardMapReaderTest.java b/src/test/java/com/android/tools/r8/internal/GMSCoreV10ProguardMapReaderTest.java
index 2c2742a..58bef78 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreV10ProguardMapReaderTest.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreV10ProguardMapReaderTest.java
@@ -17,8 +17,9 @@
   @Test
   public void roundTripTestGmsCoreV10() throws IOException {
     Path map = Paths.get(APP_DIR).resolve(PG_MAP);
-    ClassNameMapper firstMapper = ClassNameMapper.mapperFromFile(map);
-    ClassNameMapper secondMapper = ClassNameMapper.mapperFromString(firstMapper.toString());
+    ClassNameMapper firstMapper = ClassNameMapper.mapperFromFile(map).sorted();
+    ClassNameMapper secondMapper =
+        ClassNameMapper.mapperFromString(firstMapper.toString()).sorted();
     Assert.assertEquals(firstMapper, secondMapper);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java b/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
index a6277d6..b09cb00 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
@@ -205,6 +205,25 @@
             anyOf(
                 containsString("Ignoring option: -optimizations"),
                 containsString("Proguard configuration rule does not match anything")))
-        .assertAllWarningMessagesMatch(containsString("Ignoring option: -outjars"));
+        .assertAllWarningMessagesMatch(
+            anyOf(
+                containsString("Ignoring option: -outjars"),
+                containsString(
+                    "Unverifiable code in `"
+                        + "java.net.Socket com.google.android.gms.org.conscrypt."
+                        + "KitKatPlatformOpenSSLSocketAdapterFactory.wrap("
+                        + "com.google.android.gms.org.conscrypt.OpenSSLSocketImpl)`"),
+                containsString(
+                    "Unverifiable code in `"
+                        + "java.net.Socket com.google.android.gms.org.conscrypt."
+                        + "PreKitKatPlatformOpenSSLSocketAdapterFactory.wrap("
+                        + "com.google.android.gms.org.conscrypt.OpenSSLSocketImpl)`"),
+                containsString(
+                    "Unverifiable code in `"
+                        + "android.content.pm.PackageStats com.google.android.libraries.performance"
+                        + ".primes.metriccapture.PackageStatsCapture"
+                        + ".getPackageStatsUsingInternalAPI(android.content.Context, long, "
+                        + "com.google.android.libraries.performance.primes.metriccapture."
+                        + "PackageStatsCapture$PackageStatsInvocation[])`")));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/Regression127524985.java b/src/test/java/com/android/tools/r8/internal/Regression127524985.java
index a39814c..009c587 100644
--- a/src/test/java/com/android/tools/r8/internal/Regression127524985.java
+++ b/src/test/java/com/android/tools/r8/internal/Regression127524985.java
@@ -3,10 +3,17 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.internal;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import org.junit.Test;
@@ -36,25 +43,56 @@
   }
 
   @Test
-  public void test() throws Throwable {
-    if (parameters.isCfRuntime()) {
-      testForJvm()
-          .addClasspath(JAR)
-          .run(parameters.getRuntime(), MAIN)
-          .assertSuccessWithOutput(EXPECTED);
-    }
-    (parameters.isDexRuntime()
-            ? testForD8()
-            : testForR8(parameters.getBackend())
-                .debug()
-                .noTreeShaking()
-                .noMinification()
-                .addKeepAllAttributes()
-                .addKeepRules("-dontwarn"))
+  public void testJvm() throws Throwable {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addClasspath(JAR)
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testD8() throws Throwable {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
         .addProgramFiles(JAR)
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutput(EXPECTED);
   }
+
+  @Test
+  public void testR8Cf() throws Throwable {
+    assumeTrue(parameters.isCfRuntime());
+    testForR8(parameters.getBackend())
+        .debug()
+        .noTreeShaking()
+        .noMinification()
+        .addKeepAllAttributes()
+        .addKeepRules("-dontwarn")
+        .allowDiagnosticWarningMessages()
+        .addProgramFiles(JAR)
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics.assertWarningsMatch(
+                    allOf(
+                        diagnosticType(UnverifiableCfCodeDiagnostic.class),
+                        diagnosticMessage(
+                            containsString(
+                                "Unverifiable code in `"
+                                    + "void com.google.protobuf.contrib.android."
+                                    + "ProtoParsers$InternalDontUse.<clinit>()`"))),
+                    allOf(
+                        diagnosticType(UnverifiableCfCodeDiagnostic.class),
+                        diagnosticMessage(
+                            containsString(
+                                "Unverifiable code in `"
+                                    + "void com.google.protobuf.contrib.android.ProtoParsers"
+                                    + ".put(android.os.Bundle, java.lang.String, "
+                                    + "com.google.protobuf.MessageLite)`")))))
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutput(EXPECTED);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/internal/retrace/RetraceTests.java
index d283ee6..0e5ce56 100644
--- a/src/test/java/com/android/tools/r8/internal/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/internal/retrace/RetraceTests.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.internal.retrace.stacktraces.FinskyStackTrace;
 import com.android.tools.r8.internal.retrace.stacktraces.VelvetStackTrace;
 import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.ProguardMappingSupplier;
 import com.android.tools.r8.retrace.Retrace;
 import com.android.tools.r8.retrace.RetraceCommand;
 import com.android.tools.r8.retrace.stacktraces.StackTraceForTest;
@@ -199,7 +200,11 @@
     TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
     RetraceCommand retraceCommand =
         RetraceCommand.builder(diagnosticsHandler)
-            .setProguardMapProducer(ProguardMapProducer.fromString(stackTraceForTest.mapping()))
+            .setMappingSupplier(
+                ProguardMappingSupplier.builder()
+                    .setProguardMapProducer(
+                        ProguardMapProducer.fromString(stackTraceForTest.mapping()))
+                    .build())
             .setStackTrace(stackTraceForTest.obfuscatedStackTrace())
             .setRegularExpression(regularExpression)
             .setRetracedStackTraceConsumer(
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index 16e570a..81f92c3 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -126,7 +126,7 @@
     if (instruction.isFrame()) {
       return instruction
           .asFrame()
-          .map(
+          .mapReferenceTypes(
               type ->
                   (type.getTypeName().endsWith("$UnsafeStub"))
                       ? itemFactory.createType("Lsun/misc/Unsafe;")
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationMonitorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationMonitorTest.java
new file mode 100644
index 0000000..7d99b55
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationMonitorTest.java
@@ -0,0 +1,80 @@
+// 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.canonicalization;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Optional;
+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;
+
+/** This is a regression test for b/235319568 */
+@RunWith(Parameterized.class)
+public class ConstClassCanonicalizationMonitorTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepClassAndMembersRules(Main.class)
+        .enableInliningAnnotations()
+        .addKeepAttributeLineNumberTable()
+        .addKeepAttributeSourceFile()
+        .addOptionsModification(
+            options -> {
+              options.proguardMapConsumer = null;
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World!")
+        .inspect(
+            inspector -> {
+              ClassSubject clazz = inspector.clazz(Main.class);
+              assertThat(clazz, isPresent());
+              MethodSubject testSubject = clazz.uniqueMethodWithName("test");
+              assertThat(testSubject, isPresent());
+              Optional<InstructionSubject> insertedMonitor =
+                  testSubject
+                      .streamInstructions()
+                      .filter(InstructionSubject::isMonitorEnter)
+                      .findFirst();
+              assertTrue(insertedMonitor.isPresent());
+              assertTrue(testSubject.getLineNumberForInstruction(insertedMonitor.get()) > 0);
+            });
+  }
+
+  public static class Main {
+
+    @NeverInline
+    public static synchronized void test() {
+      synchronized (Main.class) {
+        System.out.println("Hello World!");
+      }
+    }
+
+    public static void main(String[] args) {
+      test();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithLibraryTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithLibraryTypeTest.java
index e5635d0..7387f3a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithLibraryTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithLibraryTypeTest.java
@@ -33,7 +33,7 @@
 @RunWith(Parameterized.class)
 public class InlineCatchHandlerWithLibraryTypeTest extends TestBase {
 
-  private static final String TEMPLATE_CODE_EXCEPTION_BINARY_NAME = "java/lang/RuntimeException";
+  private static final String TEMPLATE_CODE_EXCEPTION_BINARY_NAME = "java/lang/Exception";
 
   // A subset of exception types introduced in API levels between 16 to 24.
   private static final Map<String, Integer> EXCEPTIONS =
@@ -156,7 +156,7 @@
     public static void methodWithCatch() {
       try {
         maybeThrow();
-      } catch (RuntimeException e) {
+      } catch (Exception e) {
         // We must use the exception, otherwise there is no move-exception that triggers the
         // verification error.
         System.out.println(e.getClass().getName());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java
index 84b6917..6037e95 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java
@@ -4,6 +4,10 @@
 
 package com.android.tools.r8.ir.optimize.inliner;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
@@ -12,6 +16,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.google.common.collect.Streams;
 import org.junit.Test;
@@ -65,8 +70,18 @@
                 ExistingException.class)
             .addKeepMainRule(TestClassCallingMethodWithNonExisting.class)
             .addDontWarn(NonExistingException.class)
+            .allowDiagnosticWarningMessages()
             .setMinApi(parameters.getApiLevel())
-            .compile()
+            .compileWithExpectedDiagnostics(
+                diagnostics ->
+                    diagnostics.assertWarningsMatch(
+                        allOf(
+                            diagnosticType(UnverifiableCfCodeDiagnostic.class),
+                            diagnosticMessage(
+                                containsString(
+                                    "Unverifiable code in `void "
+                                        + ClassWithCatchNonExisting.class.getTypeName()
+                                        + ".methodWithCatch()`")))))
             .run(parameters.getRuntime(), TestClassCallingMethodWithNonExisting.class)
             .assertSuccess();
     ClassSubject classSubject =
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/SdkIntMemberValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/SdkIntMemberValuePropagationTest.java
index 0d6c727..59aca75 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/SdkIntMemberValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/SdkIntMemberValuePropagationTest.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.util.List;
 import org.junit.Test;
@@ -35,40 +34,56 @@
     R8
   }
 
+  public enum Rule {
+    EMPTY(""),
+    ASSUME_NO_SIDE_EFFECTS_24(
+        StringUtils.lines(
+            "-assumenosideeffects class android.os.Build$VERSION {",
+            "  public static int SDK_INT return 24;",
+            "}")),
+    ASSUME_NO_SIDE_EFFECTS_24_29(
+        StringUtils.lines(
+            "-assumenosideeffects class android.os.Build$VERSION {",
+            "  public static int SDK_INT return 24..25;",
+            "}")),
+    ASSUME_VALUES_24(
+        StringUtils.lines(
+            "-assumevalues class android.os.Build$VERSION {",
+            "  public static int SDK_INT return 24;",
+            "}")),
+    ASSUME_VALUES_24_29(
+        StringUtils.lines(
+            "-assumevalues class android.os.Build$VERSION {",
+            "  public static int SDK_INT return 24..29;",
+            "}"));
+
+    private final String rule;
+
+    Rule(String rule) {
+      this.rule = rule;
+    }
+
+    String getRule() {
+      return rule;
+    }
+  }
+
   @Parameter(0)
   public TestParameters parameters;
 
   @Parameter(1)
-  public String rule;
+  public Rule rule;
 
   @Parameterized.Parameters(name = "{0}, rule: {1}")
   public static List<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(),
-        ImmutableList.of(
-            "",
-            StringUtils.lines(
-                "-assumenosideeffects class android.os.Build$VERSION {",
-                "  public static int SDK_INT return 24;",
-                "}"),
-            StringUtils.lines(
-                "-assumenosideeffects class android.os.Build$VERSION {",
-                "  public static int SDK_INT return 24..25;",
-                "}"),
-            StringUtils.lines(
-                "-assumevalues class android.os.Build$VERSION {",
-                "  public static int SDK_INT return 24;",
-                "}"),
-            StringUtils.lines(
-                "-assumevalues class android.os.Build$VERSION {",
-                "  public static int SDK_INT return 24..29;",
-                "}")));
+        getTestParameters().withAllRuntimesAndApiLevels().build(), Rule.values());
   }
 
   @Test
   public void testD8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
-    assumeTrue(rule.equals(""));
+    assumeTrue(rule.getRule().equals(""));
     testForD8()
         .addProgramClassFileData(getTransformedMainClass())
         .addLibraryClassFileData(getTransformedBuildVERSIONClass())
@@ -86,7 +101,7 @@
     testForR8(parameters.getBackend())
         .addProgramClassFileData(getTransformedMainClass())
         .addKeepMainRule(Main.class)
-        .addKeepRules(rule)
+        .addKeepRules(rule.getRule())
         .addLibraryClassFileData(getTransformedBuildVERSIONClass())
         .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
         .setMinApi(parameters.getApiLevel())
@@ -101,7 +116,7 @@
     if (compiler == Compiler.D8) {
       return parameters.getApiLevel().isLessThan(AndroidApiLevel.N) ? "<N" : ">=N";
     } else {
-      if (rule.equals("")) {
+      if (rule.getRule().equals("")) {
         if (parameters.isDexRuntime()) {
           return getExpectedOutput(Compiler.D8);
         }
@@ -137,7 +152,7 @@
         mainSubject
             .streamInstructions()
             .anyMatch(x -> x.isStaticGet() && x.getField().getName().toString().equals("SDK_INT"));
-    if (compiler == Compiler.D8 || rule.equals("")) {
+    if (compiler == Compiler.D8 || rule.getRule().equals("")) {
       assertEquals(
           parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
           hasIf);
@@ -146,7 +161,7 @@
           readsMinSdkField);
     } else {
       assertFalse(hasIf);
-      assertEquals(rule.startsWith("-assumevalues"), readsMinSdkField);
+      assertEquals(rule.getRule().startsWith("-assumevalues"), readsMinSdkField);
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index 73f7a6f..58d3a74 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -5,7 +5,6 @@
 
 import static org.junit.Assert.fail;
 
-import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
@@ -26,7 +25,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 
 public class JasminTestBase extends TestBase {
@@ -107,21 +105,6 @@
     return ToolHelper.runR8(builder.build(), optionsConsumer);
   }
 
-  protected AndroidApp compileWithR8InDebugMode(
-      JasminBuilder builder,
-      List<String> proguardConfigs,
-      Consumer<InternalOptions> optionsConsumer,
-      Backend backend)
-      throws Exception {
-    R8Command command =
-        ToolHelper.prepareR8CommandBuilder(builder.build(), emptyConsumer(backend))
-            .addLibraryFiles(runtimeJar(backend))
-            .addProguardConfiguration(proguardConfigs, Origin.unknown())
-            .setMode(CompilationMode.DEBUG)
-            .build();
-    return ToolHelper.runR8(command, optionsConsumer);
-  }
-
   protected AndroidApp compileWithR8(
       JasminBuilder builder,
       List<String> proguardConfigs,
@@ -283,8 +266,7 @@
   }
 
   protected MethodSubject getMethodSubject(
-      AndroidApp application, String clazz, MethodSignature signature)
-      throws ExecutionException, IOException {
+      AndroidApp application, String clazz, MethodSignature signature) throws IOException {
     return new CodeInspector(application).clazz(clazz).method(signature);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
index f215af3..b4e4e71 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
@@ -44,7 +44,8 @@
             .setDisableTreeShaking(true)
             .setDisableMinification(true)
             .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
-            .build());
+            .build(),
+        options -> options.getCfCodeAnalysisOptions().setAllowUnreachableCfBlocks(true));
     ProcessResult processResult = ToolHelper.runJava(outputJar, main);
     assertEquals(0, processResult.exitCode);
     return processResult.stdout;
@@ -822,7 +823,10 @@
     //
     // https://github.com/apache/log4j/blob/v1_2-branch/src/main/java/org/apache/log4j/net/SocketAppender.java#L373
     //
-    clazz.addVirtualMethod("run", ImmutableList.of(), "V",
+    clazz.addVirtualMethod(
+        "run",
+        ImmutableList.of(),
+        "V",
         ".limit stack 4",
         ".limit locals 4",
         ".var 0 is this Lorg/apache/log4j/net/SocketAppender$Connector; from L0 to L26",
@@ -835,7 +839,8 @@
         ".line 368",
         ".line 369",
         "  aload 0",
-        "  getfield org/apache/log4j/net/SocketAppender$Connector.this$0 Lorg/apache/log4j/net/SocketAppender;",
+        "  getfield org/apache/log4j/net/SocketAppender$Connector.this$0"
+            + " Lorg/apache/log4j/net/SocketAppender;",
         "  getfield org/apache/log4j/net/SocketAppender.reconnectionDelay I",
         "  i2l",
         "  invokestatic java/lang/Thread.sleep(J)V",
@@ -846,7 +851,8 @@
         "  ldc \"Attempting connection to \"",
         "  invokenonvirtual java/lang/StringBuffer.<init>(Ljava/lang/String;)V",
         "  aload 0",
-        "  getfield org/apache/log4j/net/SocketAppender$Connector.this$0 Lorg/apache/log4j/net/SocketAppender;",
+        "  getfield org/apache/log4j/net/SocketAppender$Connector.this$0"
+            + " Lorg/apache/log4j/net/SocketAppender;",
         "  getfield org/apache/log4j/net/SocketAppender.address Ljava/net/InetAddress;",
         "  invokevirtual java/net/InetAddress.getHostName()Ljava/lang/String;",
         "  invokevirtual java/lang/StringBuffer.append(Ljava/lang/String;)Ljava/lang/StringBuffer;",
@@ -857,10 +863,12 @@
         "  new java/net/Socket",
         "  dup",
         "  aload 0",
-        "  getfield org/apache/log4j/net/SocketAppender$Connector.this$0 Lorg/apache/log4j/net/SocketAppender;",
+        "  getfield org/apache/log4j/net/SocketAppender$Connector.this$0"
+            + " Lorg/apache/log4j/net/SocketAppender;",
         "  getfield org/apache/log4j/net/SocketAppender.address Ljava/net/InetAddress;",
         "  aload 0",
-        "  getfield org/apache/log4j/net/SocketAppender$Connector.this$0 Lorg/apache/log4j/net/SocketAppender;",
+        "  getfield org/apache/log4j/net/SocketAppender$Connector.this$0"
+            + " Lorg/apache/log4j/net/SocketAppender;",
         "  getfield org/apache/log4j/net/SocketAppender.port I",
         "  invokenonvirtual java/net/Socket.<init>(Ljava/net/InetAddress;I)V",
         "  astore 1",
@@ -873,7 +881,8 @@
         "L6:",
         ".line 373",
         "  aload 0",
-        "  getfield org/apache/log4j/net/SocketAppender$Connector.this$0 Lorg/apache/log4j/net/SocketAppender;",
+        "  getfield org/apache/log4j/net/SocketAppender$Connector.this$0"
+            + " Lorg/apache/log4j/net/SocketAppender;",
         "  new java/io/ObjectOutputStream",
         "  dup",
         "  aload 1",
@@ -883,9 +892,11 @@
         "L7:",
         ".line 374",
         "  aload 0",
-        "  getfield org/apache/log4j/net/SocketAppender$Connector.this$0 Lorg/apache/log4j/net/SocketAppender;",
+        "  getfield org/apache/log4j/net/SocketAppender$Connector.this$0"
+            + " Lorg/apache/log4j/net/SocketAppender;",
         "  aconst_null",
-        "  invokestatic org/apache/log4j/net/SocketAppender.access$1(Lorg/apache/log4j/net/SocketAppender;Lorg/apache/log4j/net/SocketAppender$Connector;)V",
+        "  invokestatic"
+            + " org/apache/log4j/net/SocketAppender.access$1(Lorg/apache/log4j/net/SocketAppender;Lorg/apache/log4j/net/SocketAppender$Connector;)V",
         "L8:",
         ".line 375",
         "  ldc \"Connection established. Exiting connector thread.\"",
@@ -924,7 +935,8 @@
         "  ldc \"Remote host \"",
         "  invokenonvirtual java/lang/StringBuffer.<init>(Ljava/lang/String;)V",
         "  aload 0",
-        "  getfield org/apache/log4j/net/SocketAppender$Connector.this$0 Lorg/apache/log4j/net/SocketAppender;",
+        "  getfield org/apache/log4j/net/SocketAppender$Connector.this$0"
+            + " Lorg/apache/log4j/net/SocketAppender;",
         "  getfield org/apache/log4j/net/SocketAppender.address Ljava/net/InetAddress;",
         "  invokevirtual java/net/InetAddress.getHostName()Ljava/lang/String;",
         "  invokevirtual java/lang/StringBuffer.append(Ljava/lang/String;)Ljava/lang/StringBuffer;",
@@ -949,7 +961,8 @@
         "  ldc \"Could not connect to \"",
         "  invokenonvirtual java/lang/StringBuffer.<init>(Ljava/lang/String;)V",
         "  aload 0",
-        "  getfield org/apache/log4j/net/SocketAppender$Connector.this$0 Lorg/apache/log4j/net/SocketAppender;",
+        "  getfield org/apache/log4j/net/SocketAppender$Connector.this$0"
+            + " Lorg/apache/log4j/net/SocketAppender;",
         "  getfield org/apache/log4j/net/SocketAppender.address Ljava/net/InetAddress;",
         "  invokevirtual java/net/InetAddress.getHostName()Ljava/lang/String;",
         "  invokevirtual java/lang/StringBuffer.append(Ljava/lang/String;)Ljava/lang/StringBuffer;",
@@ -978,8 +991,7 @@
         ".catch all from L6 to L12 using L12",
         ".catch java/lang/InterruptedException from L2 to L13 using L13",
         ".catch java/net/ConnectException from L2 to L13 using L16",
-        ".catch java/io/IOException from L2 to L13 using L21"
-    );
+        ".catch java/io/IOException from L2 to L13 using L21");
 
     // Check that the code compiles without an infinite loop. It cannot run by itself.
     AndroidApp app = compileWithD8(builder);
@@ -1065,11 +1077,15 @@
 
     // This is the code for the method
     //
-    // void org.eclipse.jdt.internal.core.JavaModelOperation.run(org.eclipse.core.runtime.IProgressMonitor)
+    // void org.eclipse.jdt.internal.core.JavaModelOperation.run(
+    //    org.eclipse.core.runtime.IProgressMonitor)
     //
     // from struts2/lib/core-3.1.1.jar
     //
-    clazz.addVirtualMethod("run", ImmutableList.of("Lorg/eclipse/core/runtime/IProgressMonitor;"), "V",
+    clazz.addVirtualMethod(
+        "run",
+        ImmutableList.of("Lorg/eclipse/core/runtime/IProgressMonitor;"),
+        "V",
         ".limit stack 3",
         ".limit locals 15",
         ".var 0 is this Lorg/eclipse/jdt/internal/core/JavaModelOperation; from L0 to L50",
@@ -1085,29 +1101,34 @@
         ".var 12 is openable Lorg/eclipse/jdt/internal/core/Openable; from L33 to L37",
         "L0:",
         ".line 705",
-        "  invokestatic org/eclipse/jdt/internal/core/JavaModelManager.getJavaModelManager()Lorg/eclipse/jdt/internal/core/JavaModelManager;",
+        "  invokestatic"
+            + " org/eclipse/jdt/internal/core/JavaModelManager.getJavaModelManager()Lorg/eclipse/jdt/internal/core/JavaModelManager;",
         "  astore 2",
         "L1:",
         ".line 706",
         "  aload 2",
-        "  invokevirtual org/eclipse/jdt/internal/core/JavaModelManager.getDeltaProcessor()Lorg/eclipse/jdt/internal/core/DeltaProcessor;",
+        "  invokevirtual"
+            + " org/eclipse/jdt/internal/core/JavaModelManager.getDeltaProcessor()Lorg/eclipse/jdt/internal/core/DeltaProcessor;",
         "  astore 3",
         "L2:",
         ".line 707",
         "  aload 3",
-        "  getfield org/eclipse/jdt/internal/core/DeltaProcessor.javaModelDeltas Ljava/util/ArrayList;",
+        "  getfield org/eclipse/jdt/internal/core/DeltaProcessor.javaModelDeltas"
+            + " Ljava/util/ArrayList;",
         "  invokevirtual java/util/ArrayList.size()I",
         "  istore 4",
         "L3:",
         ".line 709",
         "  aload 0",
         "  aload 1",
-        "  putfield org/eclipse/jdt/internal/core/JavaModelOperation.progressMonitor Lorg/eclipse/core/runtime/IProgressMonitor;",
+        "  putfield org/eclipse/jdt/internal/core/JavaModelOperation.progressMonitor"
+            + " Lorg/eclipse/core/runtime/IProgressMonitor;",
         "L4:",
         ".line 710",
         "  aload 0",
         "  aload 0",
-        "  invokevirtual org/eclipse/jdt/internal/core/JavaModelOperation.pushOperation(Lorg/eclipse/jdt/internal/core/JavaModelOperation;)V",
+        "  invokevirtual"
+            + " org/eclipse/jdt/internal/core/JavaModelOperation.pushOperation(Lorg/eclipse/jdt/internal/core/JavaModelOperation;)V",
         "L5:",
         ".line 712",
         "  aload 0",
@@ -1115,8 +1136,10 @@
         "  ifeq L6",
         "L7:",
         ".line 715",
-        "  invokestatic org/eclipse/jdt/internal/core/JavaModelManager.getJavaModelManager()Lorg/eclipse/jdt/internal/core/JavaModelManager;",
-        "  getfield org/eclipse/jdt/internal/core/JavaModelManager.deltaState Lorg/eclipse/jdt/internal/core/DeltaProcessingState;",
+        "  invokestatic"
+            + " org/eclipse/jdt/internal/core/JavaModelManager.getJavaModelManager()Lorg/eclipse/jdt/internal/core/JavaModelManager;",
+        "  getfield org/eclipse/jdt/internal/core/JavaModelManager.deltaState"
+            + " Lorg/eclipse/jdt/internal/core/DeltaProcessingState;",
         "  invokevirtual org/eclipse/jdt/internal/core/DeltaProcessingState.initializeRoots()V",
         "L6:",
         ".line 718",
@@ -1164,7 +1187,8 @@
         "L20:",
         ".line 727",
         "  aload 2",
-        "  invokevirtual org/eclipse/jdt/internal/core/JavaModelManager.getDeltaProcessor()Lorg/eclipse/jdt/internal/core/DeltaProcessor;",
+        "  invokevirtual"
+            + " org/eclipse/jdt/internal/core/JavaModelManager.getDeltaProcessor()Lorg/eclipse/jdt/internal/core/DeltaProcessor;",
         "  astore 3",
         "L21:",
         ".line 730",
@@ -1172,7 +1196,8 @@
         "  istore 9",
         "L22:",
         "  aload 3",
-        "  getfield org/eclipse/jdt/internal/core/DeltaProcessor.javaModelDeltas Ljava/util/ArrayList;",
+        "  getfield org/eclipse/jdt/internal/core/DeltaProcessor.javaModelDeltas"
+            + " Ljava/util/ArrayList;",
         "  invokevirtual java/util/ArrayList.size()I",
         "  istore 10",
         "L23:",
@@ -1181,11 +1206,13 @@
         ".line 731",
         "  aload 3",
         "  aload 3",
-        "  getfield org/eclipse/jdt/internal/core/DeltaProcessor.javaModelDeltas Ljava/util/ArrayList;",
+        "  getfield org/eclipse/jdt/internal/core/DeltaProcessor.javaModelDeltas"
+            + " Ljava/util/ArrayList;",
         "  iload 9",
         "  invokevirtual java/util/ArrayList.get(I)Ljava/lang/Object;",
         "  checkcast org/eclipse/jdt/core/IJavaElementDelta",
-        "  invokevirtual org/eclipse/jdt/internal/core/DeltaProcessor.updateJavaModel(Lorg/eclipse/jdt/core/IJavaElementDelta;)V",
+        "  invokevirtual"
+            + " org/eclipse/jdt/internal/core/DeltaProcessor.updateJavaModel(Lorg/eclipse/jdt/core/IJavaElementDelta;)V",
         "L26:",
         ".line 730",
         "  iinc 9 1",
@@ -1199,7 +1226,8 @@
         "  istore 9",
         "L28:",
         "  aload 0",
-        "  getfield org/eclipse/jdt/internal/core/JavaModelOperation.resultElements [Lorg/eclipse/jdt/core/IJavaElement;",
+        "  getfield org/eclipse/jdt/internal/core/JavaModelOperation.resultElements"
+            + " [Lorg/eclipse/jdt/core/IJavaElement;",
         "  arraylength",
         "  istore 10",
         "L29:",
@@ -1207,14 +1235,16 @@
         "L31:",
         ".line 738",
         "  aload 0",
-        "  getfield org/eclipse/jdt/internal/core/JavaModelOperation.resultElements [Lorg/eclipse/jdt/core/IJavaElement;",
+        "  getfield org/eclipse/jdt/internal/core/JavaModelOperation.resultElements"
+            + " [Lorg/eclipse/jdt/core/IJavaElement;",
         "  iload 9",
         "  aaload",
         "  astore 11",
         "L32:",
         ".line 739",
         "  aload 11",
-        "  invokeinterface org/eclipse/jdt/core/IJavaElement.getOpenable()Lorg/eclipse/jdt/core/IOpenable; 0",
+        "  invokeinterface"
+            + " org/eclipse/jdt/core/IJavaElement.getOpenable()Lorg/eclipse/jdt/core/IOpenable; 0",
         "  checkcast org/eclipse/jdt/internal/core/Openable",
         "  astore 12",
         "L33:",
@@ -1229,7 +1259,8 @@
         "L34:",
         ".line 741",
         "  aload 12",
-        "  invokevirtual org/eclipse/jdt/internal/core/Openable.getParent()Lorg/eclipse/jdt/core/IJavaElement;",
+        "  invokevirtual"
+            + " org/eclipse/jdt/internal/core/Openable.getParent()Lorg/eclipse/jdt/core/IJavaElement;",
         "  checkcast org/eclipse/jdt/internal/core/JavaElement",
         "  invokevirtual org/eclipse/jdt/internal/core/JavaElement.close()V",
         "L35:",
@@ -1243,7 +1274,9 @@
         "L36:",
         ".line 746",
         "  aload 11",
-        "  invokeinterface org/eclipse/jdt/core/IJavaElement.getJavaProject()Lorg/eclipse/jdt/core/IJavaProject; 0",
+        "  invokeinterface"
+            + " org/eclipse/jdt/core/IJavaElement.getJavaProject()Lorg/eclipse/jdt/core/IJavaProject;"
+            + " 0",
         "  checkcast org/eclipse/jdt/internal/core/JavaProject",
         "  invokevirtual org/eclipse/jdt/internal/core/JavaProject.resetCaches()V",
         "L37:",
@@ -1261,12 +1294,14 @@
         "L40:",
         ".line 756",
         "  aload 3",
-        "  getfield org/eclipse/jdt/internal/core/DeltaProcessor.javaModelDeltas Ljava/util/ArrayList;",
+        "  getfield org/eclipse/jdt/internal/core/DeltaProcessor.javaModelDeltas"
+            + " Ljava/util/ArrayList;",
         "  invokevirtual java/util/ArrayList.size()I",
         "  iload 4",
         "  if_icmpgt L41",
         "  aload 3",
-        "  getfield org/eclipse/jdt/internal/core/DeltaProcessor.reconcileDeltas Ljava/util/HashMap;",
+        "  getfield org/eclipse/jdt/internal/core/DeltaProcessor.reconcileDeltas"
+            + " Ljava/util/HashMap;",
         "  invokevirtual java/util/HashMap.isEmpty()Z",
         "  ifne L39",
         "L41:",
@@ -1279,7 +1314,8 @@
         "  aload 3",
         "  aconst_null",
         "  iconst_0",
-        "  invokevirtual org/eclipse/jdt/internal/core/DeltaProcessor.fire(Lorg/eclipse/jdt/core/IJavaElementDelta;I)V",
+        "  invokevirtual"
+            + " org/eclipse/jdt/internal/core/DeltaProcessor.fire(Lorg/eclipse/jdt/core/IJavaElementDelta;I)V",
         "  goto L39",
         "L43:",
         ".line 761",
@@ -1295,7 +1331,8 @@
         "L46:",
         ".line 762",
         "  aload 0",
-        "  invokevirtual org/eclipse/jdt/internal/core/JavaModelOperation.popOperation()Lorg/eclipse/jdt/internal/core/JavaModelOperation;",
+        "  invokevirtual"
+            + " org/eclipse/jdt/internal/core/JavaModelOperation.popOperation()Lorg/eclipse/jdt/internal/core/JavaModelOperation;",
         "  pop",
         "L47:",
         ".line 763",
@@ -1316,8 +1353,7 @@
         ".catch all from L3 to L17 using L17",
         ".catch all from L16 to L49 using L17",
         ".catch all from L20 to L43 using L43",
-        ".catch all from L39 to L48 using L43"
-    );
+        ".catch all from L39 to L48 using L43");
 
     // Check that the code compiles without an infinite loop. It cannot run by itself.
     AndroidApp app = compileWithD8(builder);
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeep.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeep.java
index 8230cef..ee16233 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeep.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeep.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
@@ -47,7 +46,6 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepKotlinMetadata()
         .addKeepRules(StringUtils.joinLines("-if class *.Metadata", "-keep class <1>.io.** { *; }"))
-        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .allowDiagnosticWarningMessages()
         .compile()
         .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmpty.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmpty.java
index 1acdcf2..7a7aec4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmpty.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmpty.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.graph.DexAnnotationElement;
-import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
@@ -49,7 +48,6 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepAllClassesRuleWithAllowObfuscation()
         .addKeepKotlinMetadata()
-        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .allowDiagnosticWarningMessages()
         .compile()
         .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java
index 8bcbc9e..1c93e08 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java
@@ -90,7 +90,6 @@
         .addKeepRules(OBFUSCATE_RENAMED, KEEP_KEPT)
         .addKeepRules("-keep class **.Anno")
         .addKeepKotlinMetadata()
-        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .allowDiagnosticWarningMessages()
         .compile()
         .assertWarningMessageThatMatches(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
index 68731b5..16fc35d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
@@ -42,7 +42,6 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepKotlinMetadata()
         .addKeepRules("-keep class kotlin.io.** { *; }")
-        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .allowDiagnosticWarningMessages()
         .compile()
         .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
index 64e7bd8..ceb7d56 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
@@ -50,7 +50,6 @@
         .addKeepAllClassesRule()
         .addKeepKotlinMetadata()
         .addKeepAttributes(
-            ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
             ProguardKeepAttributes.INNER_CLASSES,
             ProguardKeepAttributes.ENCLOSING_METHOD)
         .allowDiagnosticWarningMessages()
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteUnitPrimitiveTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteUnitPrimitiveTest.java
index 07e22c5..5d13395 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteUnitPrimitiveTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteUnitPrimitiveTest.java
@@ -101,7 +101,6 @@
                 kotlinc.getKotlinStdlibJar())
             .addKeepClassAndMembersRules(PKG_LIB + ".*")
             .addKeepAttributes(
-                ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
                 ProguardKeepAttributes.SIGNATURE,
                 ProguardKeepAttributes.INNER_CLASSES,
                 ProguardKeepAttributes.ENCLOSING_METHOD)
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index 5d715f1..baf09cc 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -55,7 +54,6 @@
             .addProgramFiles(kotlinc.getKotlinReflectJar(), kotlinc.getKotlinAnnotationJar())
             .addKeepMainRule(mainClassName)
             .addKeepKotlinMetadata()
-            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
             .allowDiagnosticWarningMessages()
             .setMinApi(parameters.getApiLevel())
             .allowUnusedDontWarnKotlinReflectJvmInternal(kotlinc.isNot(KOTLINC_1_3_72))
diff --git a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
index 4e56011..d94ad7b 100644
--- a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
@@ -56,17 +56,20 @@
 
   @Test
   public void roundTripTest() throws IOException {
-    ClassNameMapper firstMapper = ClassNameMapper.mapperFromFile(Paths.get(ROOT, EXAMPLE_MAP));
-    ClassNameMapper secondMapper = ClassNameMapper.mapperFromString(firstMapper.toString());
+    ClassNameMapper firstMapper =
+        ClassNameMapper.mapperFromFile(Paths.get(ROOT, EXAMPLE_MAP)).sorted();
+    ClassNameMapper secondMapper =
+        ClassNameMapper.mapperFromString(firstMapper.toString()).sorted();
     Assert.assertEquals(firstMapper, secondMapper);
   }
 
   @Test
   public void roundTripTestWithLeadingBOM() throws IOException {
-    ClassNameMapper firstMapper = ClassNameMapper.mapperFromFile(Paths.get(ROOT, EXAMPLE_MAP));
+    ClassNameMapper firstMapper =
+        ClassNameMapper.mapperFromFile(Paths.get(ROOT, EXAMPLE_MAP)).sorted();
     assertTrue(firstMapper.toString().charAt(0) != StringUtils.BOM);
     ClassNameMapper secondMapper =
-        ClassNameMapper.mapperFromString(StringUtils.BOM + firstMapper.toString());
+        ClassNameMapper.mapperFromString(StringUtils.BOM + firstMapper.toString()).sorted();
     assertTrue(secondMapper.toString().charAt(0) != StringUtils.BOM);
     Assert.assertEquals(firstMapper, secondMapper);
     byte[] bytes = Files.readAllBytes(Paths.get(ROOT, EXAMPLE_MAP));
@@ -76,7 +79,7 @@
     assertEquals(0xef, Byte.toUnsignedLong(bytes[0]));
     assertEquals(0xbb, Byte.toUnsignedLong(bytes[1]));
     assertEquals(0xbf, Byte.toUnsignedLong(bytes[2]));
-    ClassNameMapper thirdMapper = ClassNameMapper.mapperFromFile(mapFileWithBOM);
+    ClassNameMapper thirdMapper = ClassNameMapper.mapperFromFile(mapFileWithBOM).sorted();
     assertTrue(thirdMapper.toString().charAt(0) != StringUtils.BOM);
     Assert.assertEquals(firstMapper, thirdMapper);
   }
@@ -93,7 +96,8 @@
             "" + StringUtils.BOM,
             StringUtils.BOM + " " + StringUtils.BOM);
     for (String whitespace : ws) {
-      ClassNameMapper firstMapper = ClassNameMapper.mapperFromFile(Paths.get(ROOT, EXAMPLE_MAP));
+      ClassNameMapper firstMapper =
+          ClassNameMapper.mapperFromFile(Paths.get(ROOT, EXAMPLE_MAP)).sorted();
       assertTrue(firstMapper.toString().charAt(0) != StringUtils.BOM);
       StringBuilder buildWithWhitespace = new StringBuilder();
       char prevChar = '\0';
@@ -114,13 +118,13 @@
         prevChar = c;
       }
       ClassNameMapper secondMapper =
-          ClassNameMapper.mapperFromString(buildWithWhitespace.toString());
+          ClassNameMapper.mapperFromString(buildWithWhitespace.toString()).sorted();
       assertFalse(firstMapper.toString().contains("" + StringUtils.BOM));
       Assert.assertEquals(firstMapper, secondMapper);
       byte[] bytes = Files.readAllBytes(Paths.get(ROOT, EXAMPLE_MAP));
       assertNotEquals(0xef, Byte.toUnsignedLong(bytes[0]));
       Path mapFileWithBOM = writeTextToTempFile(StringUtils.BOM + firstMapper.toString());
-      ClassNameMapper thirdMapper = ClassNameMapper.mapperFromFile(mapFileWithBOM);
+      ClassNameMapper thirdMapper = ClassNameMapper.mapperFromFile(mapFileWithBOM).sorted();
       assertTrue(thirdMapper.toString().charAt(0) != StringUtils.BOM);
       Assert.assertEquals(firstMapper, thirdMapper);
     }
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index 4fdc4b7..1619359 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -12,8 +12,9 @@
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.ProguardMappingSupplier;
+import com.android.tools.r8.retrace.Retrace;
 import com.android.tools.r8.retrace.RetraceCommand;
-import com.android.tools.r8.retrace.RetraceHelper;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.base.Equivalence;
@@ -378,13 +379,16 @@
     List<String> stackTrace =
         stackTraceLines.stream().map(line -> line.originalLine).collect(Collectors.toList());
     stackTrace.add(0, exceptionLine);
-    RetraceHelper.runForTesting(
+    Retrace.run(
         RetraceCommand.builder()
-            .setProguardMapProducer(ProguardMapProducer.fromString(map))
+            .setMappingSupplier(
+                ProguardMappingSupplier.builder()
+                    .setProguardMapProducer(ProguardMapProducer.fromString(map))
+                    .setAllowExperimental(allowExperimentalMapping)
+                    .build())
             .setStackTrace(stackTrace)
             .setRetracedStackTraceConsumer(box::set)
-            .build(),
-        allowExperimentalMapping);
+            .build());
     // Keep the original stderr in the retraced stacktrace.
     return new StackTrace(
         box.get().get(0), internalConvert(box.get().stream().skip(1)), originalStderr);
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/CatchAllRangeWithNoLineNumberTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/CatchAllRangeWithNoLineNumberTest.java
index 34da9a6..4372134 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/CatchAllRangeWithNoLineNumberTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/CatchAllRangeWithNoLineNumberTest.java
@@ -10,8 +10,10 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.retrace.ProguardMapProducer;
-import com.android.tools.r8.retrace.RetraceOptions;
-import com.android.tools.r8.retrace.StringRetrace;
+import com.android.tools.r8.retrace.ProguardMappingSupplier;
+import com.android.tools.r8.retrace.Retrace;
+import com.android.tools.r8.retrace.RetraceCommand;
+import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.StringUtils;
 import java.io.IOException;
 import java.util.Arrays;
@@ -98,13 +100,18 @@
 
   @Test
   public void testCatchAllRangeR8() {
-    List<String> retrace =
-        StringRetrace.create(
-                RetraceOptions.builder()
+    Box<String> retracedString = new Box<>();
+    Retrace.run(
+        RetraceCommand.builder()
+            .setRetracedStackTraceConsumer(
+                retraced -> retracedString.set(StringUtils.lines(retraced)))
+            .setMappingSupplier(
+                ProguardMappingSupplier.builder()
                     .setProguardMapProducer(ProguardMapProducer.fromString(mapping))
                     .build())
-            .retrace(Arrays.asList(stackTrace));
-    assertEquals(retracedR8, StringUtils.lines(retrace));
+            .setStackTrace(Arrays.asList(stackTrace))
+            .build());
+    assertEquals(retracedR8, retracedString.get());
   }
 
   private String getExpected() {
diff --git a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationEnumParameterTest.java b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationEnumParameterTest.java
new file mode 100644
index 0000000..53912a8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationEnumParameterTest.java
@@ -0,0 +1,80 @@
+// 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.proto;
+
+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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+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;
+
+/** This is a regression test for b/235733922 */
+@RunWith(Parameterized.class)
+public class ProtoNormalizationEnumParameterTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8Compat(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepClassAndMembersRules(Main.class)
+        .addKeepClassRules(CustomAnnotation.class)
+        .addKeepRuntimeVisibleParameterAnnotations()
+        .enableInliningAnnotations()
+        .run(parameters.getRuntime(), Main.class, "foo", "bar")
+        .assertSuccessWithOutputLines("2foobar", "TEST_1");
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.PARAMETER)
+  public @interface CustomAnnotation {}
+
+  public enum MyEnum {
+    TEST_1("Foo"),
+    TEST_2("Bar");
+
+    private final String str;
+
+    MyEnum(@CustomAnnotation String str) {
+      this.str = str;
+    }
+
+    public String getStr() {
+      return TEST_1 + str;
+    }
+  }
+
+  public static class Main {
+
+    @NeverInline
+    // The test(int foo, String bar, String baz) is needed to have
+    // MyEnum.<init>(String name, int ordinal, String str) written into this proto.
+    public static void test(int foo, String bar, String baz) {
+      System.out.println(foo + bar + baz);
+    }
+
+    @NeverInline
+    public static void main(String[] args) {
+      test(args.length, args[0], args[1]);
+      MyEnum val = System.currentTimeMillis() > 0 ? MyEnum.TEST_1 : MyEnum.TEST_2;
+      System.out.println(val);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/Regress181837660.java b/src/test/java/com/android/tools/r8/regress/Regress181837660.java
index b5e0a7c..d1275c8 100644
--- a/src/test/java/com/android/tools/r8/regress/Regress181837660.java
+++ b/src/test/java/com/android/tools/r8/regress/Regress181837660.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.dexsplitter.SplitterTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableSet;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -88,13 +89,17 @@
     assertEquals(processResult.stdout, "42\n");
   }
 
-  private void configure(R8FullTestBuilder testBuilder) throws NoSuchMethodException {
-    testBuilder.enableInliningAnnotations().noMinification().setMinApi(parameters.getApiLevel());
+  private void configure(R8FullTestBuilder testBuilder) {
+    configureNoInlineAnnotations(testBuilder);
+    testBuilder.enableInliningAnnotations();
   }
 
-  private void configureNoInlineAnnotations(R8FullTestBuilder testBuilder)
-      throws NoSuchMethodException {
-    testBuilder.noMinification().setMinApi(parameters.getApiLevel());
+  private void configureNoInlineAnnotations(R8FullTestBuilder testBuilder) {
+    testBuilder
+        // Link against android.jar that contains ReflectiveOperationException.
+        .addLibraryFiles(parameters.getDefaultAndroidJarAbove(AndroidApiLevel.K))
+        .noMinification()
+        .setMinApi(parameters.getApiLevel());
   }
 
   public static class BaseClass {
diff --git a/src/test/java/com/android/tools/r8/regress/UndefinedLambdaInterfaceRegress232379893.java b/src/test/java/com/android/tools/r8/regress/UndefinedLambdaInterfaceRegress232379893.java
index b27e96a..56fb81a 100644
--- a/src/test/java/com/android/tools/r8/regress/UndefinedLambdaInterfaceRegress232379893.java
+++ b/src/test/java/com/android/tools/r8/regress/UndefinedLambdaInterfaceRegress232379893.java
@@ -3,10 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.regress;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -35,7 +41,21 @@
         .addKeepMainRule(TestClass.class)
         .addDontWarn(UndefinedInterface.class)
         .addDontShrink()
+        .allowDiagnosticWarningMessages(parameters.isDexRuntime())
         .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              if (parameters.isDexRuntime()) {
+                diagnostics.assertWarningsMatch(
+                    allOf(
+                        diagnosticType(UnverifiableCfCodeDiagnostic.class),
+                        diagnosticMessage(
+                            containsString(
+                                "Unverifiable code in `void "
+                                    + TestClass.class.getTypeName()
+                                    + ".main(java.lang.String[])`"))));
+              }
+            })
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED);
   }
diff --git a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java
index a2ca851..077e3c2 100644
--- a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java
+++ b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.regress.b78493232;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.AsmTestBase;
@@ -17,6 +19,7 @@
 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;
 
 // Variant of Regress78493232, but where the new-instance is forced to flow to a non-trivial phi
@@ -35,17 +38,14 @@
   private static final List<byte[]> CLASS_BYTES =
       ImmutableList.of(Regress78493232Dump_WithPhi.dump());
 
-  private final TestParameters parameters;
+  @Parameter(0)
+  public TestParameters parameters;
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public Regress78493232_WithPhi(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
   @Test
   public void testReference() throws Exception {
     assumeTrue(parameters.isCfRuntime());
@@ -86,11 +86,22 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(CLASSES)
             .addProgramClassFileData(CLASS_BYTES)
+            .allowDiagnosticWarningMessages()
             .treeShaking(treeShake)
             .noMinification()
             .setMinApi(parameters.getApiLevel())
             .addOptionsModification(options -> options.testing.readInputStackMaps = false)
             .addKeepMainRule(MAIN)
+            .compileWithExpectedDiagnostics(
+                diagnostics ->
+                    diagnostics.assertWarningsMatch(
+                        diagnosticMessage(
+                            equalTo(
+                                "Unverifiable code in `java.lang.String regress78493232.Test."
+                                    + "methodCausingIssue(byte, short, int)` at instruction 53: "
+                                    + "Cannot join stacks, expected frame types at stack index 1 "
+                                    + "to join to a precise (non-top) type, but types null and "
+                                    + "uninitialized java.lang.String do not."))))
             .run(parameters.getRuntime(), MAIN);
     checkResult(result);
   }
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageLambdaMissingInterfaceTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageLambdaMissingInterfaceTest.java
index 2265ba0..16a1089 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageLambdaMissingInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageLambdaMissingInterfaceTest.java
@@ -4,13 +4,18 @@
 
 package com.android.tools.r8.repackage;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.startsWith;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -46,9 +51,22 @@
         .applyIf(repackage, this::configureRepackaging)
         .setMinApi(parameters.getApiLevel())
         .addDontWarn(MissingInterface.class)
+        .allowDiagnosticWarningMessages(parameters.isDexRuntime())
         .noClassInlining()
         .enableInliningAnnotations()
-        .compile()
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              if (parameters.isDexRuntime()) {
+                diagnostics.assertWarningsMatch(
+                    allOf(
+                        diagnosticType(UnverifiableCfCodeDiagnostic.class),
+                        diagnosticMessage(
+                            containsString(
+                                "Unverifiable code in `void "
+                                    + ClassWithLambda.class.getTypeName()
+                                    + ".callWithLambda()`"))));
+              }
+            })
         .inspect(
             inspector -> {
               // Find the generated lambda class
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithFeatureSplitTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithFeatureSplitTest.java
index 87fe5f0..2255c28 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithFeatureSplitTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithFeatureSplitTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.apimodel.ApiModelingTestHelper;
 import com.android.tools.r8.dexsplitter.SplitterTestBase.RunInterface;
 import com.android.tools.r8.dexsplitter.SplitterTestBase.SplitRunner;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -42,6 +43,8 @@
   public void test() throws Exception {
     testForR8(parameters.getBackend())
         .addProgramClasses(BaseClass.class)
+        // Link against android.jar that contains ReflectiveOperationException.
+        .addLibraryFiles(parameters.getDefaultAndroidJarAbove(AndroidApiLevel.K))
         .addFeatureSplit(FeatureMain.class, FeatureClass.class)
         .addFeatureSplitRuntime()
         .addKeepFeatureMainRule(FeatureMain.class)
diff --git a/src/test/java/com/android/tools/r8/retrace/DuplicateMappingsTest.java b/src/test/java/com/android/tools/r8/retrace/DuplicateMappingsTest.java
index 06899fd..5dcca78 100644
--- a/src/test/java/com/android/tools/r8/retrace/DuplicateMappingsTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/DuplicateMappingsTest.java
@@ -37,13 +37,16 @@
     TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
     Retrace.run(
         RetraceCommand.builder(diagnosticsHandler)
-            .setProguardMapProducer(
-                ProguardMapProducer.fromString(
-                    StringUtils.lines(
-                        "com.android.tools.r8.retrace.SourceFileTest$ClassWithCustomFileName ->"
-                            + " com.android.tools.r8.retrace.a:",
-                        "# {'id':'sourceFile','fileName':'foobarbaz.java'}",
-                        "# {'id':'sourceFile','fileName':'foobarbaz2.java'}")))
+            .setMappingSupplier(
+                ProguardMappingSupplier.builder()
+                    .setProguardMapProducer(
+                        ProguardMapProducer.fromString(
+                            StringUtils.lines(
+                                "com.android.tools.r8.retrace.SourceFileTest"
+                                    + "$ClassWithCustomFileName -> com.android.tools.r8.retrace.a:",
+                                "# {'id':'sourceFile','fileName':'foobarbaz.java'}",
+                                "# {'id':'sourceFile','fileName':'foobarbaz2.java'}")))
+                    .build())
             .setStackTrace(ImmutableList.of(" at com.android.tools.r8.retrace.a.foo(Unknown)"))
             .setRetracedStackTraceConsumer(
                 strings -> {
diff --git a/src/test/java/com/android/tools/r8/retrace/OverloadsWithoutLineNumberTest.java b/src/test/java/com/android/tools/r8/retrace/OverloadsWithoutLineNumberTest.java
index 56622ac..3aa2f49 100644
--- a/src/test/java/com/android/tools/r8/retrace/OverloadsWithoutLineNumberTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/OverloadsWithoutLineNumberTest.java
@@ -55,11 +55,14 @@
             .collect(Collectors.toList());
     RetraceCommand.Builder builder =
         RetraceCommand.builder()
-            .setProguardMapProducer(ProguardMapProducer.fromString(run.proguardMap()))
+            .setMappingSupplier(
+                ProguardMappingSupplier.builder()
+                    .setProguardMapProducer(ProguardMapProducer.fromString(run.proguardMap()))
+                    .build())
             .setStackTrace(originalStackTrace)
             .setRetracedStackTraceConsumer(box::set)
             .setVerbose(true);
-    RetraceHelper.runForTesting(builder.build(), false);
+    Retrace.run(builder.build());
     // TODO(b/221015863): This should ideally be:
     // at " + typeName(ClassWithOverload.class) + ".test(int)(OverloadsWithoutLineNumberTest.java)
     if (parameters.canUseNativeDexPC()) {
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 31dd142..4602712 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -85,6 +85,7 @@
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
 import java.util.function.Consumer;
 import org.junit.Test;
@@ -95,20 +96,26 @@
 @RunWith(Parameterized.class)
 public class RetraceTests extends TestBase {
 
-  @Parameters(name = "{0}, external: {1}, verbose: {2}")
+  @Parameters(name = "{0}, external: {1}, verbose: {2}, stream: {3}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        getTestParameters().withCfRuntimes().build(), BooleanUtils.values(), BooleanUtils.values());
+        getTestParameters().withCfRuntimes().build(),
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        BooleanUtils.values());
   }
 
   private final TestParameters testParameters;
   private final boolean external;
   private final boolean verbose;
+  private final boolean stream;
 
-  public RetraceTests(TestParameters parameters, boolean external, boolean verbose) {
+  public RetraceTests(
+      TestParameters parameters, boolean external, boolean verbose, boolean stream) {
     this.testParameters = parameters;
     this.external = external;
     this.verbose = verbose;
+    this.stream = stream;
   }
 
   @Test
@@ -152,13 +159,16 @@
     NullStackTrace nullStackTrace = new NullStackTrace();
     RetraceCommand retraceCommand =
         RetraceCommand.builder(diagnosticsHandler)
-            .setProguardMapProducer(ProguardMapProducer.fromString(nullStackTrace.mapping()))
+            .setMappingSupplier(
+                ProguardMappingSupplier.builder()
+                    .setProguardMapProducer(
+                        ProguardMapProducer.fromString(nullStackTrace.mapping()))
+                    .build())
             .setStackTrace(nullStackTrace.obfuscatedStackTrace())
             .setRetracedStackTraceConsumer(retraced -> fail())
             .build();
     try {
       Retrace.run(retraceCommand);
-      fail();
     } catch (RetraceAbortException e) {
       diagnosticsHandler.assertOnlyErrors();
       diagnosticsHandler.assertErrorsCount(1);
@@ -491,16 +501,38 @@
       return new TestDiagnosticMessagesImpl();
     } else {
       TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
-      RetraceCommand retraceCommand =
+      StringBuilder retracedStackTraceBuilder = new StringBuilder();
+      RetraceCommand.Builder builder =
           RetraceCommand.builder(diagnosticsHandler)
-              .setProguardMapProducer(ProguardMapProducer.fromString(stackTraceForTest.mapping()))
-              .setStackTrace(stackTraceForTest.obfuscatedStackTrace())
+              .setMappingSupplier(
+                  ProguardMappingSupplier.builder()
+                      .setProguardMapProducer(
+                          ProguardMapProducer.fromString(stackTraceForTest.mapping()))
+                      .setAllowExperimental(allowExperimentalMapping)
+                      .build())
               .setRetracedStackTraceConsumer(
-                  retraced -> assertEquals(expectedStackTrace, StringUtils.joinLines(retraced)))
-              .setVerbose(verbose)
-              .build();
-      Retrace.runForTesting(retraceCommand, allowExperimentalMapping);
+                  retraced -> {
+                    if (retracedStackTraceBuilder.length() > 0) {
+                      retracedStackTraceBuilder.append(StringUtils.LINE_SEPARATOR);
+                    }
+                    retracedStackTraceBuilder.append(StringUtils.joinLines(retraced));
+                  })
+              .setVerbose(verbose);
+      setStacktraceSupplierAndRetraceConsumer(builder, stackTraceForTest.obfuscatedStackTrace());
+      RetraceCommand retraceCommand = builder.build();
+      Retrace.run(retraceCommand);
+      assertEquals(expectedStackTrace, retracedStackTraceBuilder.toString());
       return diagnosticsHandler;
     }
   }
+
+  private void setStacktraceSupplierAndRetraceConsumer(
+      RetraceCommand.Builder builder, List<String> stackTrace) {
+    if (stream) {
+      Iterator<String> iterator = stackTrace.iterator();
+      builder.setStackTrace(() -> iterator.hasNext() ? ImmutableList.of(iterator.next()) : null);
+    } else {
+      builder.setStackTrace(stackTrace);
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceWhitespaceTest.java b/src/test/java/com/android/tools/r8/retrace/RetraceWhitespaceTest.java
index 7b9c08d..17b6600 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceWhitespaceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceWhitespaceTest.java
@@ -64,7 +64,10 @@
         () ->
             Retrace.run(
                 RetraceCommand.builder()
-                    .setProguardMapProducer(ProguardMapProducer.fromPath(mappingFile))
+                    .setMappingSupplier(
+                        ProguardMappingSupplier.builder()
+                            .setProguardMapProducer(ProguardMapProducer.fromPath(mappingFile))
+                            .build())
                     .setStackTrace(STACKTRACE)
                     .setRetracedStackTraceConsumer(lines -> {})
                     .build()));
diff --git a/src/test/java/com/android/tools/r8/retrace/StackTraceRegularExpressionParserTests.java b/src/test/java/com/android/tools/r8/retrace/StackTraceRegularExpressionParserTests.java
index 91428db..1ea73cc 100644
--- a/src/test/java/com/android/tools/r8/retrace/StackTraceRegularExpressionParserTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/StackTraceRegularExpressionParserTests.java
@@ -1140,7 +1140,11 @@
     TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
     RetraceCommand retraceCommand =
         RetraceCommand.builder(diagnosticsHandler)
-            .setProguardMapProducer(ProguardMapProducer.fromString(stackTraceForTest.mapping()))
+            .setMappingSupplier(
+                ProguardMappingSupplier.builder()
+                    .setProguardMapProducer(
+                        ProguardMapProducer.fromString(stackTraceForTest.mapping()))
+                    .build())
             .setStackTrace(stackTraceForTest.obfuscatedStackTrace())
             .setRetracedStackTraceConsumer(
                 retraced -> {
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInlineInOutlineTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInlineInOutlineTest.java
index 9ca6e87..8a86283 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInlineInOutlineTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInlineInOutlineTest.java
@@ -9,9 +9,8 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.retrace.MappingProvider;
 import com.android.tools.r8.retrace.ProguardMapProducer;
-import com.android.tools.r8.retrace.ProguardMappingProvider;
+import com.android.tools.r8.retrace.ProguardMappingSupplier;
 import com.android.tools.r8.retrace.RetraceFrameElement;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.RetracedMethodReference;
@@ -63,12 +62,11 @@
 
     @Test
     public void test() {
-      MappingProvider mappingProvider =
-          ProguardMappingProvider.builder()
+      ProguardMappingSupplier mappingProvider =
+          ProguardMappingSupplier.builder()
               .setProguardMapProducer(ProguardMapProducer.fromString(mapping))
-              .allowLookupAllClasses()
               .build();
-      Retracer retracer = Retracer.builder().setMappingProvider(mappingProvider).build();
+      Retracer retracer = Retracer.builder().setMappingSupplier(mappingProvider).build();
       List<RetraceFrameElement> outlineRetraced =
           retracer
               .retraceFrame(
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInOutlineStackTrace.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInOutlineStackTrace.java
index 650953c..cfefe61 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInOutlineStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInOutlineStackTrace.java
@@ -10,9 +10,8 @@
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.retrace.MappingProvider;
 import com.android.tools.r8.retrace.ProguardMapProducer;
-import com.android.tools.r8.retrace.ProguardMappingProvider;
+import com.android.tools.r8.retrace.ProguardMappingSupplier;
 import com.android.tools.r8.retrace.RetraceFrameElement;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.RetracedMethodReference;
@@ -71,12 +70,11 @@
 
     @Test
     public void test() {
-      MappingProvider mappingProvider =
-          ProguardMappingProvider.builder()
+      ProguardMappingSupplier mappingProvider =
+          ProguardMappingSupplier.builder()
               .setProguardMapProducer(ProguardMapProducer.fromString(mapping))
-              .allowLookupAllClasses()
               .build();
-      Retracer retracer = Retracer.builder().setMappingProvider(mappingProvider).build();
+      Retracer retracer = Retracer.builder().setMappingSupplier(mappingProvider).build();
       // Retrace the first outline.
       RetraceStackTraceContext outlineContext =
           retraceOutline(
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInlineTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInlineTest.java
index 1c67ac5..34f1217 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInlineTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInlineTest.java
@@ -9,9 +9,8 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.retrace.MappingProvider;
 import com.android.tools.r8.retrace.ProguardMapProducer;
-import com.android.tools.r8.retrace.ProguardMappingProvider;
+import com.android.tools.r8.retrace.ProguardMappingSupplier;
 import com.android.tools.r8.retrace.RetraceFrameElement;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.RetracedMethodReference;
@@ -62,12 +61,11 @@
 
     @Test
     public void test() {
-      MappingProvider mappingProvider =
-          ProguardMappingProvider.builder()
+      ProguardMappingSupplier mappingSupplier =
+          ProguardMappingSupplier.builder()
               .setProguardMapProducer(ProguardMapProducer.fromString(mapping))
-              .allowLookupAllClasses()
               .build();
-      Retracer retracer = Retracer.builder().setMappingProvider(mappingProvider).build();
+      Retracer retracer = Retracer.builder().setMappingSupplier(mappingSupplier).build();
       List<RetraceFrameElement> outlineRetraced =
           retracer
               .retraceFrame(
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineNoInlineTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineNoInlineTest.java
index d77819d..38736ce 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineNoInlineTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineNoInlineTest.java
@@ -10,9 +10,8 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.retrace.MappingProvider;
 import com.android.tools.r8.retrace.ProguardMapProducer;
-import com.android.tools.r8.retrace.ProguardMappingProvider;
+import com.android.tools.r8.retrace.ProguardMappingSupplier;
 import com.android.tools.r8.retrace.RetraceFrameElement;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.RetracedMethodReference;
@@ -62,12 +61,11 @@
 
     @Test
     public void test() {
-      MappingProvider mappingProvider =
-          ProguardMappingProvider.builder()
+      ProguardMappingSupplier mappingProvider =
+          ProguardMappingSupplier.builder()
               .setProguardMapProducer(ProguardMapProducer.fromString(mapping))
-              .allowLookupAllClasses()
               .build();
-      Retracer retracer = Retracer.builder().setMappingProvider(mappingProvider).build();
+      Retracer retracer = Retracer.builder().setMappingSupplier(mappingProvider).build();
       List<RetraceFrameElement> outlineRetraced =
           retracer
               .retraceFrame(
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeResidualTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeResidualTest.java
index 6251b54..76b0ff4 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeResidualTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeResidualTest.java
@@ -10,9 +10,8 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.retrace.MappingProvider;
 import com.android.tools.r8.retrace.ProguardMapProducer;
-import com.android.tools.r8.retrace.ProguardMappingProvider;
+import com.android.tools.r8.retrace.ProguardMappingSupplier;
 import com.android.tools.r8.retrace.RetraceFrameElement;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.RetraceThrownExceptionElement;
@@ -80,12 +79,11 @@
 
     @Test
     public void testUsingObfuscatedName() {
-      MappingProvider mappingProvider =
-          ProguardMappingProvider.builder()
+      ProguardMappingSupplier mappingProvider =
+          ProguardMappingSupplier.builder()
               .setProguardMapProducer(ProguardMapProducer.fromString(mapping))
-              .allowLookupAllClasses()
               .build();
-      Retracer retracer = Retracer.builder().setMappingProvider(mappingProvider).build();
+      Retracer retracer = Retracer.builder().setMappingSupplier(mappingProvider).build();
       List<RetraceThrownExceptionElement> npeRetraced =
           retracer.retraceThrownException(renamedException).stream().collect(Collectors.toList());
       assertEquals(1, npeRetraced.size());
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeTest.java
index 6d3bd5b..4334060 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeTest.java
@@ -11,9 +11,8 @@
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.retrace.MappingProvider;
 import com.android.tools.r8.retrace.ProguardMapProducer;
-import com.android.tools.r8.retrace.ProguardMappingProvider;
+import com.android.tools.r8.retrace.ProguardMappingSupplier;
 import com.android.tools.r8.retrace.RetraceFrameElement;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.RetraceThrownExceptionElement;
@@ -60,15 +59,13 @@
     @Test
     public void testFirstStackLineIsRemoved() {
       TestDiagnosticsHandler testDiagnosticsHandler = new TestDiagnosticsHandler();
-      MappingProvider mappingProvider =
-          ProguardMappingProvider.builder()
+      ProguardMappingSupplier mappingProvider =
+          ProguardMappingSupplier.builder()
               .setProguardMapProducer(ProguardMapProducer.fromString(mapping))
-              .setDiagnosticsHandler(testDiagnosticsHandler)
-              .allowLookupAllClasses()
               .build();
       Retracer retracer =
           Retracer.builder()
-              .setMappingProvider(mappingProvider)
+              .setMappingSupplier(mappingProvider)
               .setDiagnosticsHandler(testDiagnosticsHandler)
               .build();
 
diff --git a/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java b/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java
index 5e4932e..7d4a46b 100644
--- a/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.rewrite;
 
+import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.anyOf;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
@@ -79,6 +80,9 @@
                         containsString("Missing class "),
                         containsString(
                             "required for default or static interface methods desugaring"),
+                        allOf(
+                            containsString("Unverifiable code in `"),
+                            containsString("org.mozilla.javascript.tools.")),
                         equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))))
         .run(parameters.getRuntime(), TestClass.class)
         .applyIf(
diff --git a/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java b/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java
index 2ddf11b..33394dd 100644
--- a/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.rewrite;
 
+import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.anyOf;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
@@ -36,21 +37,20 @@
 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 ScriptEngineTest extends ScriptEngineTestBase {
 
-  private final TestParameters parameters;
+  @Parameter(0)
+  public TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}")
+  @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public ScriptEngineTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
   @Test
   public void test() throws IOException, CompilationFailedException, ExecutionException {
     Path path = temp.newFile("out.zip").toPath();
@@ -96,6 +96,9 @@
                         containsString("Missing class "),
                         containsString(
                             "it is required for default or static interface methods desugaring"),
+                        allOf(
+                            containsString("Unverifiable code in `"),
+                            containsString("org.mozilla.javascript.tools.")),
                         equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))))
         .writeToZip(path)
         .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java b/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
index c3e53cf..5fec641 100644
--- a/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
@@ -30,22 +30,22 @@
 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 FieldReadsJasminTest extends JasminTestBase {
   private static final String CLS = "Empty";
   private static final String MAIN = "Main";
-  private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}")
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public FieldReadsJasminTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
   @Test
   public void testInstanceGet_nonNullReceiver() throws Exception {
     JasminBuilder builder = new JasminBuilder();
@@ -284,20 +284,21 @@
         "  bipush 1",
         "  putstatic Main/sField I",
         "  return");
-    MethodSignature mainMethod = main.addMainMethod(
-        ".limit stack 3",
-        ".limit locals 2",
-        "  getstatic Main/sField I",
-        "  new Empty",
-        "  dup",
-        "  invokespecial Empty/<init>()V",
-        "  getstatic Main/sField I",
-        "  bipush 2",
-        "  if_icmpeq r",
-        "  aconst_null",
-        "  athrow",
-        "r:",
-        "  return");
+    MethodSignature mainMethod =
+        main.addMainMethod(
+            ".limit stack 4",
+            ".limit locals 2",
+            "  getstatic Main/sField I",
+            "  new Empty",
+            "  dup",
+            "  invokespecial Empty/<init>()V",
+            "  getstatic Main/sField I",
+            "  bipush 2",
+            "  if_icmpeq r",
+            "  aconst_null",
+            "  athrow",
+            "r:",
+            "  return");
 
     ensureFieldExistsAndReadOnlyOnce(builder, main, mainMethod, main, "sField");
   }
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 bb63e6f..acc8765 100644
--- a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.anyOf;
 import static org.hamcrest.CoreMatchers.containsString;
@@ -21,6 +23,7 @@
 import com.android.tools.r8.jasmin.JasminTestBase;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.Collection;
@@ -311,14 +314,22 @@
                 })
             .allowDiagnosticWarningMessages(allowDiagnosticWarningMessages)
             .setMinApi(parameters.getApiLevel())
-            .compile()
-            .applyIf(
-                allowDiagnosticWarningMessages,
-                result ->
-                    result.assertAllWarningMessagesMatch(
-                        equalTo(
-                            "The method `void UnverifiableClass.unverifiableMethod()` does not"
-                                + " type check and will be assumed to be unreachable.")))
+            .compileWithExpectedDiagnostics(
+                diagnostics -> {
+                  if (allowDiagnosticWarningMessages) {
+                    diagnostics.assertWarningsMatch(
+                        allOf(
+                            diagnosticType(UnverifiableCfCodeDiagnostic.class),
+                            diagnosticMessage(
+                                containsString(
+                                    "Unverifiable code in `void"
+                                        + " UnverifiableClass.unverifiableMethod()`"))),
+                        diagnosticMessage(
+                            equalTo(
+                                "The method `void UnverifiableClass.unverifiableMethod()` does not"
+                                    + " type check and will be assumed to be unreachable.")));
+                  }
+                })
             .run(parameters.getRuntime(), mainClass.name);
     checkTestRunResult(r8Result, Compiler.R8);
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideCovariantTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideCovariantTest.java
index 23febd2..59fcf6d 100644
--- a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideCovariantTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideCovariantTest.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentHashMap.KeySetView;
@@ -61,14 +60,10 @@
         .compile()
         .addRunClasspathFiles(buildOnDexRuntime(parameters, LibraryUser.class))
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatMatchesIf(
-            parameters.isCfRuntime(), containsString("Hello World"))
-        .assertFailureWithErrorThatThrowsIf(
-            !supportsKeySetView() && parameters.isDexRuntime(), NoSuchMethodError.class)
-        // TODO(b/234579501): We fail to keep the library method override.
         .applyIf(
-            parameters.isDexRuntime() && supportsKeySetView(),
-            TestRunResult::assertSuccessWithEmptyOutput);
+            supportsKeySetView(),
+            result -> result.assertFailureWithErrorThatMatches(containsString("Hello World")),
+            result -> result.assertFailureWithErrorThatThrows(NoSuchMethodError.class));
   }
 
   private byte[] getMainWithoutSyntheticBridgeForKeySet() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java
index 0222e3d..82d690c 100644
--- a/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java
@@ -3,12 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.containsString;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import org.junit.Test;
@@ -40,7 +44,18 @@
         .addKeepMainRule(TestClassForB112849320.class)
         .addOptionsModification(options -> options.inlinerOptions().enableInlining = false)
         .addKeepPackageNamesRule(GoingToBeMissed.class.getPackage())
-        .compile()
+        .allowDiagnosticWarningMessages()
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics.assertWarningsMatch(
+                    allOf(
+                        diagnosticType(UnverifiableCfCodeDiagnostic.class),
+                        diagnosticMessage(
+                            containsString(
+                                "Unverifiable code in `"
+                                    + "void "
+                                    + TestClassForB112849320.class.getTypeName()
+                                    + ".main(java.lang.String[])`")))))
         .addRunClasspathFiles(
             buildOnDexRuntime(
                 parameters,
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
index d241adb..e32e47c 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -66,10 +66,11 @@
         .addKeepRuleFiles(ListUtils.map(keepRulesFiles, Paths::get))
         .addKeepRules("-printusage " + out.resolve(test + PRINT_USAGE_FILE_SUFFIX))
         .applyIf(
-            test.equals("shaking12")
-                && parameters.isDexRuntime()
-                && parameters.getApiLevel().isLessThan(AndroidApiLevel.K),
-            builder -> builder.addDontWarn(ReflectiveOperationException.class))
+            test.equals("shaking12") && parameters.isDexRuntime(),
+            testBuilder ->
+                // Link against android.jar that contains ReflectiveOperationException.
+                testBuilder.addLibraryFiles(
+                    parameters.getDefaultAndroidJarAbove(AndroidApiLevel.K)))
         // Disable inlining to make this test not depend on inlining decisions.
         .addOptionsModification(o -> o.inlinerOptions().enableInlining = false)
         .enableProguardTestOptions()
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index a841c79..9c3bbea 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -92,6 +92,8 @@
       VALID_PROGUARD_DIR + "assume-values-with-return-value.flags";
   private static final String ADAPT_KOTLIN_METADATA =
       VALID_PROGUARD_DIR + "adapt-kotlin-metadata.flags";
+  private static final String KEEP_KOTLIN_METADATA =
+      VALID_PROGUARD_DIR + "keep-kotlin-metadata.flags";
   private static final String INCLUDING =
       VALID_PROGUARD_DIR + "including.flags";
   private static final String INVALID_INCLUDING_1 =
@@ -897,7 +899,22 @@
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     Path path = Paths.get(ADAPT_KOTLIN_METADATA);
     parser.parse(path);
-    checkDiagnostics(handler.infos, path, 1, 1, "Ignoring", "-adaptkotlinmetadata");
+    verifyParserEndsCleanly();
+  }
+
+  @Test
+  public void parseKeepKotlinMetadata() {
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    Path path = Paths.get(KEEP_KOTLIN_METADATA);
+    parser.parse(path);
+    verifyParserEndsCleanly();
+    ProguardConfiguration config = parser.getConfig();
+    assertEquals(
+        "-keepattributes RuntimeVisibleAnnotations", config.getKeepAttributes().toString());
+    assertEquals(
+        StringUtils.joinLines("-keep class kotlin.Metadata {", "  *;", "}"),
+        config.getRules().get(0).toString());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java
index 9b5cc93..ce5e0fe 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java
@@ -88,12 +88,15 @@
 
     classBuilder = builder.addClass(compatLibraryClassName);
 
-    classBuilder.addStaticMethod("compatMethod", ImmutableList.of(), "V",
+    classBuilder.addStaticMethod(
+        "compatMethod",
+        ImmutableList.of(),
+        "V",
+        ".limit stack 2",
         "  getstatic java/lang/System/out Ljava/io/PrintStream;",
         "  ldc \" Compat\"",
         "  invokevirtual java/io/PrintStream/print(Ljava.lang.String;)V",
-        "  return"
-    );
+        "  return");
 
     classBuilder.addStaticMethod("method", ImmutableList.of(), "V",
         ".limit stack 2",
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceMethodResolutionWithMissingLibraryAndProgramClassTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceMethodResolutionWithMissingLibraryAndProgramClassTest.java
index f15630e..5fa184a 100644
--- a/src/test/java/com/android/tools/r8/tracereferences/TraceMethodResolutionWithMissingLibraryAndProgramClassTest.java
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceMethodResolutionWithMissingLibraryAndProgramClassTest.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
 import com.google.common.collect.ImmutableSet;
 import java.nio.file.Path;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 import org.junit.Test;
@@ -103,7 +104,7 @@
             Reference.methodFromMethod(B.class.getMethod("baz")),
             Reference.methodFromMethod(B.class.getMethod("qux")));
     assertEquals(foundSet, consumer.seenMethods);
-    assertEquals(foundSet, consumer.seenMissingMethods);
+    assertEquals(Collections.emptySet(), consumer.seenMissingMethods);
   }
 
   // A is added to both library and program, but the program one is missing the methods {foo,bar}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index efcf8a9..74b5de5 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -36,7 +36,7 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.retrace.Retracer;
-import com.android.tools.r8.retrace.internal.ProguardMappingProviderImpl;
+import com.android.tools.r8.retrace.internal.ProguardMappingSupplierImpl;
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.BiMapContainer;
@@ -185,7 +185,7 @@
     if (lazyRetracer == null) {
       lazyRetracer =
           Retracer.builder()
-              .setMappingProvider(new ProguardMappingProviderImpl(mapping))
+              .setMappingSupplier(new ProguardMappingSupplierImpl(mapping))
               .setDiagnosticsHandler(new TestDiagnosticMessagesImpl())
               .build();
     }
@@ -549,8 +549,8 @@
 
   public Retracer retrace() {
     return Retracer.builder()
-        .setMappingProvider(
-            new ProguardMappingProviderImpl(
+        .setMappingSupplier(
+            new ProguardMappingSupplierImpl(
                 mapping == null ? ClassNameMapper.builder().build() : mapping))
         .setDiagnosticsHandler(new TestDiagnosticMessagesImpl())
         .build();
diff --git a/src/test/java/com/android/tools/r8/workaround/InputWithAbstractMethodOnNonAbstractClassTest.java b/src/test/java/com/android/tools/r8/workaround/InputWithAbstractMethodOnNonAbstractClassTest.java
index aca08d4..f44fff9 100644
--- a/src/test/java/com/android/tools/r8/workaround/InputWithAbstractMethodOnNonAbstractClassTest.java
+++ b/src/test/java/com/android/tools/r8/workaround/InputWithAbstractMethodOnNonAbstractClassTest.java
@@ -74,7 +74,7 @@
   private void inspect(CodeInspector inspector) {
     MethodSubject methodOfInterest = inspector.clazz(Greeter.class).uniqueMethodWithName("dead");
     assertThat(methodOfInterest, isPresent());
-    if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.L)) {
+    if (parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(AndroidApiLevel.L)) {
       assertThat(methodOfInterest, not(isAbstract()));
     } else {
       assertThat(methodOfInterest, isAbstract());
diff --git a/src/test/proguard/valid/keep-kotlin-metadata.flags b/src/test/proguard/valid/keep-kotlin-metadata.flags
new file mode 100644
index 0000000..b5a6fcd
--- /dev/null
+++ b/src/test/proguard/valid/keep-kotlin-metadata.flags
@@ -0,0 +1 @@
+-keepkotlinmetadata
\ No newline at end of file
diff --git a/third_party/api_database/api_database.tar.gz.sha1 b/third_party/api_database/api_database.tar.gz.sha1
index ee68444..3a106aa 100644
--- a/third_party/api_database/api_database.tar.gz.sha1
+++ b/third_party/api_database/api_database.tar.gz.sha1
@@ -1 +1 @@
-72bdd118be45bbd99762b5dfea457c90052e0f22
\ No newline at end of file
+da5a2b797563b19461f62db48bf1e45e2f86752f
\ No newline at end of file
diff --git a/third_party/retrace/binary_compatibility.tar.gz.sha1 b/third_party/retrace/binary_compatibility.tar.gz.sha1
index 30ac55d..abfaa5e 100644
--- a/third_party/retrace/binary_compatibility.tar.gz.sha1
+++ b/third_party/retrace/binary_compatibility.tar.gz.sha1
@@ -1 +1 @@
-c7b9bd1e5be6caa0e8a8cb6634b7fb73fd48ae8e
\ No newline at end of file
+29e9654bef01ef4e00b0e44849b9073a02997b8b
\ No newline at end of file
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 9f3d1b0..5962cc0 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -129,6 +129,16 @@
     help='Run the compilation in a loop',
     default=False,
     action='store_true')
+  parser.add_argument(
+    '--enable-missing-library-api-modeling',
+    help='Run with api modeling',
+    default=False,
+    action='store_true')
+  parser.add_argument(
+    '--android-platform-build',
+    help='Run as a platform build',
+    default=False,
+    action='store_true')
   return parser
 
 def error(msg):
@@ -271,6 +281,20 @@
     return True
   return None
 
+def determine_android_platform_build(args, build_properties):
+  if args.android_platform_build:
+    return args.android_platform_build
+  if 'android-platform-build=true' in build_properties:
+    return True
+  return None
+
+def determine_enable_missing_library_api_modeling(args, build_properties):
+  if args.enable_missing_library_api_modeling:
+    return args.enable_missing_library_api_modeling
+  if 'enable-missing-library-api-modeling=true' in build_properties:
+    return True
+  return None
+
 def determine_properties(build_properties):
   args = []
   for key, value in build_properties.items():
@@ -367,6 +391,8 @@
     out = determine_output(args, temp)
     min_api = determine_min_api(args, build_properties)
     classfile = determine_class_file(args, build_properties)
+    android_platform_build = determine_android_platform_build(args, build_properties)
+    enable_missing_library_api_modeling = determine_enable_missing_library_api_modeling(args, build_properties)
     jar = args.r8_jar if args.r8_jar else download_distribution(version, args.nolib, temp)
     if ':' not in jar and not os.path.exists(jar):
       error("Distribution does not exist: " + jar)
@@ -429,6 +455,10 @@
       cmd.extend(['--min-api', min_api])
     if classfile:
       cmd.extend(['--classfile'])
+    if android_platform_build:
+      cmd.extend(['--android-platform-build'])
+    if enable_missing_library_api_modeling:
+      cmd.extend(['--enable-missing-library-api-modeling'])
     if args.threads:
       cmd.extend(['--threads', args.threads])
     cmd.extend(otherargs)