Merge commit 'a89dbbd97c51b46d21f7cccaca7e45dcb1ecc792' into dev-release
diff --git a/.gitignore b/.gitignore
index b3f67f4..e1b9f6d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -207,6 +207,8 @@
 third_party/opensource-apps/sqldelight.tar.gz
 third_party/opensource-apps/sunflower
 third_party/opensource-apps/sunflower.tar.gz
+third_party/opensource-apps/systemui
+third_party/opensource-apps/systemui.tar.gz
 third_party/opensource-apps/tachiyomi
 third_party/opensource-apps/tachiyomi.tar.gz
 third_party/opensource-apps/tivi
diff --git a/build.gradle b/build.gradle
index b2e669c..7c3a95c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,6 +4,7 @@
 
 import dx.DexMergerTask
 import dx.DxTask
+import desugaredlibrary.CustomConversionAsmRewriterTask
 import net.ltgt.gradle.errorprone.CheckSeverity
 import org.gradle.internal.os.OperatingSystem
 import smali.SmaliTask
@@ -31,7 +32,7 @@
 
 ext {
     androidSupportVersion = '25.4.0'
-    asmVersion = '9.3'  // When updating update tools/asmifier.py and Toolhelper as well.
+    asmVersion = '9.3'  // When updating update tools/asmifier.py, build.src and Toolhelper as well.
     espressoVersion = '3.0.0'
     fastutilVersion = '7.2.0'
     guavaVersion = '30.1.1-jre'
@@ -1007,11 +1008,16 @@
     }
 }
 
-task buildLibraryDesugarConversions(type: Zip, dependsOn: downloadDeps) {
+task rawBuildLibraryDesugarConversions(type: Zip, dependsOn: downloadDeps) {
     from sourceSets.libraryDesugarConversions.output
     include "java/**/*.class"
-    baseName 'library_desugar_conversions'
-    destinationDir file('build/libs')
+    baseName 'library_desugar_conversions_raw'
+    destinationDir file('build/tmp/desugaredlibrary')
+}
+
+task buildLibraryDesugarConversions(type: CustomConversionAsmRewriterTask, dependsOn: rawBuildLibraryDesugarConversions) {
+    rawJar = file("build/tmp/desugaredlibrary/library_desugar_conversions_raw.zip")
+    outputDirectory = file("build/libs")
 }
 
 task testJarSources(type: Jar, dependsOn: [testClasses, buildLibraryDesugarConversions]) {
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 74c5cfd..81527b4 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -7,10 +7,18 @@
 repositories {
     mavenCentral()
 }
+ext {
+    asmVersion = '9.3'
+}
 
 dependencies {
     compile group: 'com.google.guava', name: 'guava', version: '19.0'
     compile group: 'org.smali', name: 'smali', version: '2.2b4'
+    compile group: 'org.ow2.asm', name: 'asm', version: asmVersion
+    compile group: 'org.ow2.asm', name: 'asm-commons', version: asmVersion
+    compile group: 'org.ow2.asm', name: 'asm-tree', version: asmVersion
+    compile group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
+    compile group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
 }
 
 sourceCompatibility = JavaVersion.VERSION_1_8
diff --git a/buildSrc/src/main/java/desugaredlibrary/AsmRewriter.java b/buildSrc/src/main/java/desugaredlibrary/AsmRewriter.java
new file mode 100644
index 0000000..cc1fcf2
--- /dev/null
+++ b/buildSrc/src/main/java/desugaredlibrary/AsmRewriter.java
@@ -0,0 +1,53 @@
+// 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 desugaredlibrary;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public abstract class AsmRewriter {
+
+  public static int ASM_VERSION = Opcodes.ASM9;
+
+  public static byte[] transformInvoke(byte[] bytes, MethodTransformer transformer) {
+    ClassReader reader = new ClassReader(bytes);
+    ClassWriter writer = new ClassWriter(reader, 0);
+    ClassVisitor subvisitor = new InvokeTransformer(writer, transformer);
+    reader.accept(subvisitor, 0);
+    return writer.toByteArray();
+  }
+
+  public static class InvokeTransformer extends ClassVisitor {
+
+    private final MethodTransformer transformer;
+
+    InvokeTransformer(ClassWriter writer, MethodTransformer transformer) {
+      super(ASM_VERSION, writer);
+      this.transformer = transformer;
+    }
+
+    @Override
+    public MethodVisitor visitMethod(
+        int access, String name, String descriptor, String signature, String[] exceptions) {
+      MethodVisitor sub = super.visitMethod(access, name, descriptor, signature, exceptions);
+      transformer.setMv(sub);
+      return transformer;
+    }
+  }
+
+  public static class MethodTransformer extends MethodVisitor {
+
+    protected MethodTransformer(int api) {
+      super(api);
+    }
+
+    public void setMv(MethodVisitor visitor) {
+      this.mv = visitor;
+    }
+  }
+}
diff --git a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
new file mode 100644
index 0000000..137ac12
--- /dev/null
+++ b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
@@ -0,0 +1,54 @@
+// 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 desugaredlibrary;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class CustomConversionAsmRewriteDescription {
+
+  static final String WRAP_CONVERT = "wrap_convert";
+  static final String INVERTED_WRAP_CONVERT = "inverted_wrap_convert";
+  static final String CONVERT = "convert";
+
+  private static final Set<String> ENUM_WRAP_CONVERT_OWNER =
+      ImmutableSet.of(
+          "j$/nio/file/StandardOpenOption",
+          "j$/nio/file/LinkOption",
+          "j$/nio/file/attribute/PosixFilePermission",
+          "j$/util/stream/Collector$Characteristics");
+  private static final Set<String> WRAP_CONVERT_OWNER =
+      ImmutableSet.of(
+          "j$/nio/file/spi/FileSystemProvider",
+          "j$/nio/file/spi/FileTypeDetector",
+          "j$/nio/file/Path",
+          "j$/nio/file/WatchEvent",
+          "j$/nio/file/attribute/BasicFileAttributes",
+          "j$/nio/file/attribute/BasicFileAttributeView",
+          "j$/nio/file/attribute/FileOwnerAttributeView",
+          "j$/nio/file/attribute/PosixFileAttributes",
+          "j$/nio/file/attribute/PosixFileAttributeView");
+
+  static Map<String, String> getJavaWrapConvertOwnerMap() {
+    return computeConvertOwnerMap("$VivifiedWrapper");
+  }
+
+  static Map<String, String> getJ$WrapConvertOwnerMap() {
+    return computeConvertOwnerMap("$Wrapper");
+  }
+
+  private static HashMap<String, String> computeConvertOwnerMap(String suffix) {
+    HashMap<String, String> map = new HashMap<>();
+    for (String theEnum : ENUM_WRAP_CONVERT_OWNER) {
+      map.put(theEnum, theEnum + "$EnumConversion");
+    }
+    for (String owner : WRAP_CONVERT_OWNER) {
+      map.put(owner, owner + suffix);
+    }
+    return map;
+  }
+}
diff --git a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriter.java b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriter.java
new file mode 100644
index 0000000..6107e67
--- /dev/null
+++ b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriter.java
@@ -0,0 +1,183 @@
+// 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 desugaredlibrary;
+
+import static desugaredlibrary.AsmRewriter.ASM_VERSION;
+import static desugaredlibrary.CustomConversionAsmRewriteDescription.CONVERT;
+import static desugaredlibrary.CustomConversionAsmRewriteDescription.INVERTED_WRAP_CONVERT;
+import static desugaredlibrary.CustomConversionAsmRewriteDescription.WRAP_CONVERT;
+import static desugaredlibrary.CustomConversionAsmRewriter.CustomConversionVersion.LEGACY;
+import static org.objectweb.asm.Opcodes.INVOKESTATIC;
+
+import com.google.common.io.ByteStreams;
+import desugaredlibrary.AsmRewriter.MethodTransformer;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.zip.CRC32;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+public class CustomConversionAsmRewriter {
+
+  public enum CustomConversionVersion {
+    LEGACY,
+    LATEST
+  }
+
+  public CustomConversionAsmRewriter(CustomConversionVersion legacy) {
+    this.legacy = legacy;
+  }
+
+  private final CustomConversionVersion legacy;
+  private final Map<String, String> javaWrapConvertOwnerMap =
+      CustomConversionAsmRewriteDescription.getJavaWrapConvertOwnerMap();
+  private final Map<String, String> j$WrapConvertOwnerMap =
+      CustomConversionAsmRewriteDescription.getJ$WrapConvertOwnerMap();
+
+  public static void generateJars(Path jar, Path outputDirectory) throws IOException {
+    for (CustomConversionVersion version : CustomConversionVersion.values()) {
+      new CustomConversionAsmRewriter(version).convert(jar, outputDirectory);
+    }
+  }
+
+  private void convert(Path jar, Path outputDirectory) throws IOException {
+    String fileName = jar.getFileName().toString();
+    String newFileName =
+        fileName.substring(0, fileName.length() - "_raw.jar".length())
+            + (legacy == LEGACY ? "_legacy" : "")
+            + ".jar";
+    Path convertedJar = outputDirectory.resolve(newFileName);
+    internalConvert(jar, convertedJar);
+  }
+
+  private void internalConvert(Path jar, Path convertedJar) throws IOException {
+    OpenOption[] options =
+        new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
+    try (ZipOutputStream out =
+        new ZipOutputStream(
+            new BufferedOutputStream(Files.newOutputStream(convertedJar, options)))) {
+      new CustomConversionAsmRewriter(legacy).convert(jar, out, legacy);
+    }
+  }
+
+  private void convert(
+      Path desugaredLibraryFiles, ZipOutputStream out, CustomConversionVersion legacy)
+      throws IOException {
+    try (ZipFile zipFile = new ZipFile(desugaredLibraryFiles.toFile(), StandardCharsets.UTF_8)) {
+      final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+      while (entries.hasMoreElements()) {
+        ZipEntry entry = entries.nextElement();
+        try (InputStream entryStream = zipFile.getInputStream(entry)) {
+          handleFile(entry, entryStream, out, legacy);
+        }
+      }
+    }
+  }
+
+  private void handleFile(
+      ZipEntry entry, InputStream input, ZipOutputStream out, CustomConversionVersion legacy)
+      throws IOException {
+    if (!entry.getName().endsWith(".class")) {
+      return;
+    }
+    if (legacy == LEGACY
+        && (entry.getName().contains("java.nio.file")
+            || entry.getName().contains("ApiFlips")
+            || entry.getName().contains("java.adapter"))) {
+      return;
+    }
+    final byte[] bytes = ByteStreams.toByteArray(input);
+    input.close();
+    final byte[] rewrittenBytes = transformInvoke(bytes);
+    writeToZipStream(out, entry.getName(), rewrittenBytes, ZipEntry.STORED);
+  }
+
+  public static void writeToZipStream(
+      ZipOutputStream stream, String entry, byte[] content, int compressionMethod)
+      throws IOException {
+    int offset = 0;
+    int length = content.length;
+    CRC32 crc = new CRC32();
+    crc.update(content, offset, length);
+    ZipEntry zipEntry = new ZipEntry(entry);
+    zipEntry.setMethod(compressionMethod);
+    zipEntry.setSize(length);
+    zipEntry.setCrc(crc.getValue());
+    zipEntry.setTime(0);
+    stream.putNextEntry(zipEntry);
+    stream.write(content, offset, length);
+    stream.closeEntry();
+  }
+
+  private byte[] transformInvoke(byte[] bytes) {
+    return AsmRewriter.transformInvoke(bytes, new CustomConversionRewriter(ASM_VERSION));
+  }
+
+  class CustomConversionRewriter extends MethodTransformer {
+
+    protected CustomConversionRewriter(int api) {
+      super(api);
+    }
+
+    @Override
+    public void visitMethodInsn(
+        int opcode, String owner, String name, String descriptor, boolean isInterface) {
+      if (opcode == INVOKESTATIC) {
+        if (name.equals(WRAP_CONVERT)) {
+          convertInvoke(
+              opcode,
+              owner,
+              descriptor,
+              isInterface,
+              javaWrapConvertOwnerMap,
+              j$WrapConvertOwnerMap);
+          return;
+        }
+        if (name.equals(INVERTED_WRAP_CONVERT)) {
+          convertInvoke(
+              opcode,
+              owner,
+              descriptor,
+              isInterface,
+              j$WrapConvertOwnerMap,
+              javaWrapConvertOwnerMap);
+          return;
+        }
+      }
+      super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+    }
+
+    private void convertInvoke(
+        int opcode,
+        String owner,
+        String descriptor,
+        boolean isInterface,
+        Map<String, String> javaMap,
+        Map<String, String> j$Map) {
+      if (!javaMap.containsKey(owner)
+          || !j$Map.containsKey(owner)
+          || !(owner.startsWith("java") || owner.startsWith("j$"))) {
+        throw new RuntimeException("Cannot transform wrap_convert method for " + owner);
+      }
+      if (owner.startsWith("java")) {
+        String newOwner = j$Map.get(owner);
+        super.visitMethodInsn(opcode, newOwner, CONVERT, descriptor, isInterface);
+        return;
+      }
+      assert owner.startsWith("j$");
+      String newOwner = javaMap.get(owner);
+      super.visitMethodInsn(opcode, newOwner, CONVERT, descriptor, isInterface);
+    }
+  }
+}
diff --git a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriterTask.java b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriterTask.java
new file mode 100644
index 0000000..bae3c1c
--- /dev/null
+++ b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriterTask.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 desugaredlibrary;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import javax.inject.Inject;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.workers.IsolationMode;
+import org.gradle.workers.WorkerExecutor;
+
+public class CustomConversionAsmRewriterTask extends DefaultTask {
+
+  private final WorkerExecutor workerExecutor;
+
+  private File rawJar;
+  private File outputDirectory;
+
+  @Inject
+  public CustomConversionAsmRewriterTask(WorkerExecutor workerExecutor) {
+    this.workerExecutor = workerExecutor;
+  }
+
+  @InputFile
+  public File getRawJar() {
+    return rawJar;
+  }
+
+  public void setRawJar(File rawJar) {
+    this.rawJar = rawJar;
+  }
+
+  @OutputDirectory
+  public File getOutputDirectory() {
+    return outputDirectory;
+  }
+
+  public void setOutputDirectory(File outputDirectory) {
+    this.outputDirectory = outputDirectory;
+  }
+
+  @TaskAction
+  void exec() {
+    workerExecutor.submit(
+        Run.class,
+        config -> {
+          config.setIsolationMode(IsolationMode.NONE);
+          config.params(rawJar, outputDirectory);
+        });
+  }
+
+  public static class Run implements Runnable {
+
+    private final File rawJar;
+    private final File outputDirectory;
+
+    @Inject
+    public Run(File rawJar, File outputDirectory) {
+      this.rawJar = rawJar;
+      this.outputDirectory = outputDirectory;
+    }
+
+    @Override
+    public void run() {
+      try {
+        CustomConversionAsmRewriter.generateJars(rawJar.toPath(), outputDirectory.toPath());
+      } catch (IOException e) {
+        throw new UncheckedIOException(e);
+      }
+    }
+  }
+}
diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg
index ca4bc25..0586776 100644
--- a/infra/config/global/generated/cr-buildbucket.cfg
+++ b/infra/config/global/generated/cr-buildbucket.cfg
@@ -151,7 +151,7 @@
       }
     }
     builders {
-      name: "lib_desugar-archive"
+      name: "lib_desugar-archive-jdk11"
       swarming_host: "chrome-swarming.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "cores:8"
@@ -167,6 +167,41 @@
         '{'
         '  "builder_group": "internal.client.r8",'
         '  "recipe": "rex",'
+        '  "test_options": ['
+        '    "--variant=jdk11"'
+        '  ],'
+        '  "test_wrapper": "tools/archive_desugar_jdk_libs.py"'
+        '}'
+      priority: 25
+      execution_timeout_secs: 3600
+      expiration_secs: 126000
+      build_numbers: YES
+      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+    }
+    builders {
+      name: "lib_desugar-archive-jdk8"
+      swarming_host: "chrome-swarming.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "pool:luci.r8.ci"
+      exe {
+        cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
+        cipd_version: "refs/heads/master"
+        cmd: "luciexe"
+      }
+      properties:
+        '{'
+        '  "builder_group": "internal.client.r8",'
+        '  "recipe": "rex",'
+        '  "test_options": ['
+        '    "--variant=jdk8"'
+        '  ],'
         '  "test_wrapper": "tools/archive_desugar_jdk_libs.py"'
         '}'
       priority: 25
diff --git a/infra/config/global/generated/luci-milo.cfg b/infra/config/global/generated/luci-milo.cfg
index e07a48e..9aaa005 100644
--- a/infra/config/global/generated/luci-milo.cfg
+++ b/infra/config/global/generated/luci-milo.cfg
@@ -121,9 +121,14 @@
     short_name: "kotlin_old"
   }
   builders {
-    name: "buildbucket/luci.r8.ci/lib_desugar-archive"
+    name: "buildbucket/luci.r8.ci/lib_desugar-archive-jdk11"
     category: "library_desugar"
-    short_name: "archive"
+    short_name: "jdk11"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/lib_desugar-archive-jdk8"
+    category: "library_desugar"
+    short_name: "jdk8"
   }
   builders {
     name: "buildbucket/luci.r8.ci/desugared_library-head"
diff --git a/infra/config/global/generated/luci-scheduler.cfg b/infra/config/global/generated/luci-scheduler.cfg
index 2c77bc7..10dd059 100644
--- a/infra/config/global/generated/luci-scheduler.cfg
+++ b/infra/config/global/generated/luci-scheduler.cfg
@@ -63,7 +63,7 @@
   }
 }
 job {
-  id: "lib_desugar-archive"
+  id: "lib_desugar-archive-jdk11"
   realm: "ci"
   acl_sets: "ci"
   triggering_policy {
@@ -74,7 +74,22 @@
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "ci"
-    builder: "lib_desugar-archive"
+    builder: "lib_desugar-archive-jdk11"
+  }
+}
+job {
+  id: "lib_desugar-archive-jdk8"
+  realm: "ci"
+  acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 3
+    max_batch_size: 1
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "ci"
+    builder: "lib_desugar-archive-jdk8"
   }
 }
 job {
diff --git a/infra/config/global/main.star b/infra/config/global/main.star
index d1529cf..f93a678 100755
--- a/infra/config/global/main.star
+++ b/infra/config/global/main.star
@@ -240,12 +240,15 @@
             dimensions = dimensions, category = category, release_trigger=release_trigger)
 
 def archivers():
-  for name in ["archive", "archive_release", "lib_desugar-archive"]:
+  for name in ["archive", "archive_release", "lib_desugar-archive-jdk11", "lib_desugar-archive-jdk8"]:
     desugar = "desugar" in name
     properties = {
         "test_wrapper" : "tools/archive_desugar_jdk_libs.py" if desugar else "tools/archive.py",
         "builder_group" : "internal.client.r8"
     }
+    if desugar:
+      properties["test_options"] = ["--variant=jdk11" if "jdk11" in name else "--variant=jdk8"]
+
     r8_builder(
         name,
         category = "library_desugar" if desugar else "archive",
diff --git a/src/library_desugar/java/j$/nio/file/Path.java b/src/library_desugar/java/j$/nio/file/Path.java
index 781ab5f..cb01ec0 100644
--- a/src/library_desugar/java/j$/nio/file/Path.java
+++ b/src/library_desugar/java/j$/nio/file/Path.java
@@ -6,7 +6,7 @@
 
 public class Path {
 
-  public static j$.nio.file.Path wrap_convert(java.nio.file.Path path) {
+  public static j$.nio.file.Path inverted_wrap_convert(java.nio.file.Path path) {
     return null;
   }
 }
diff --git a/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java b/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java
index da93736..af2c8f7 100644
--- a/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java
+++ b/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java
@@ -29,7 +29,7 @@
   static class PlatformFileTypeDetector extends FileTypeDetector {
     @Override
     public String probeContentType(Path path) throws IOException {
-      return j$.nio.file.Files.probeContentType(j$.nio.file.Path.wrap_convert(path));
+      return j$.nio.file.Files.probeContentType(j$.nio.file.Path.inverted_wrap_convert(path));
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/CompatDxHelper.java b/src/main/java/com/android/tools/r8/CompatDxHelper.java
deleted file mode 100644
index 13cd385..0000000
--- a/src/main/java/com/android/tools/r8/CompatDxHelper.java
+++ /dev/null
@@ -1,28 +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.utils.AndroidApp;
-import com.android.tools.r8.utils.InternalOptions;
-
-public class CompatDxHelper {
-  public static void run(D8Command command, Boolean minimalMainDex)
-      throws CompilationFailedException {
-    AndroidApp app = command.getInputApp();
-    InternalOptions options = command.getInternalOptions();
-    // DX allows --multi-dex without specifying a main dex list for legacy devices.
-    // That is broken, but for CompatDX we do the same to not break existing builds
-    // that are trying to transition.
-    options.enableMainDexListCheck = false;
-    // DX has a minimal main dex flag. In compat mode only do minimal main dex
-    // if the flag is actually set.
-    options.minimalMainDex = minimalMainDex;
-    D8.runForTesting(app, options);
-  }
-
-  public static void ignoreDexInArchive(BaseCommand.Builder builder) {
-    builder.setIgnoreDexInArchive(true);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
index b638235..80f679b 100644
--- a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
+++ b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
@@ -15,7 +15,6 @@
       boolean forceProguardCompatibility, DiagnosticsHandler diagnosticsHandler) {
     super(diagnosticsHandler);
     setProguardCompatibility(forceProguardCompatibility);
-    setIgnoreDexInArchive(true);
   }
 
   public CompatProguardCommandBuilder(boolean forceProguardCompatibility) {
@@ -26,6 +25,5 @@
       boolean forceProguardCompatibility, boolean disableVerticalClassMerging) {
     setProguardCompatibility(forceProguardCompatibility);
     setDisableVerticalClassMerging(disableVerticalClassMerging);
-    setIgnoreDexInArchive(true);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 223fc66..4cc8c25 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -442,7 +442,7 @@
       assert appView.appInfo().hasLiveness();
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
 
-      assert new CfOpenClosedInterfacesAnalysis(appViewWithLiveness).run(executorService);
+      new CfOpenClosedInterfacesAnalysis(appViewWithLiveness).run(executorService);
 
       new StartupInstrumentation(appView).instrumentAllClasses(executorService);
 
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 7923102..1496f40 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -127,14 +127,17 @@
 
     Builder(DiagnosticsHandler diagnosticsHandler) {
       super(diagnosticsHandler);
+      setIgnoreDexInArchive(true);
     }
 
     private Builder(AndroidApp app) {
       super(app);
+      setIgnoreDexInArchive(true);
     }
 
     private Builder(AndroidApp app, DiagnosticsHandler diagnosticsHandler) {
       super(app, diagnosticsHandler);
+      setIgnoreDexInArchive(true);
     }
 
     // Internal
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiDataAccess.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiDataAccess.java
index 0539dcf..c788d6b 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiDataAccess.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiDataAccess.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.dex.CompatByteBuffer;
-import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
@@ -92,7 +92,9 @@
       InternalOptions options, DiagnosticsHandler diagnosticsHandler) {
     URL resource = AndroidApiDataAccess.class.getClassLoader().getResource(RESOURCE_NAME);
     if (resource == null) {
-      throw new CompilationError("Could not find the api database at " + RESOURCE_NAME);
+      diagnosticsHandler.warning(
+          new StringDiagnostic("Could not find the api database at " + RESOURCE_NAME));
+      return new AndroidApiDataAccessNoBacking();
     }
     if (options.apiModelingOptions().useMemoryMappedByteBuffer) {
       try {
@@ -126,11 +128,14 @@
     try (InputStream apiInputStream =
         AndroidApiDataAccess.class.getClassLoader().getResourceAsStream(RESOURCE_NAME)) {
       if (apiInputStream == null) {
-        throw new CompilationError("Could not find the api database at: " + resource);
+        diagnosticsHandler.warning(
+            new StringDiagnostic("Could not open the api database at " + RESOURCE_NAME));
+        return new AndroidApiDataAccessNoBacking();
       }
       return new AndroidApiDataAccessInMemory(ByteStreams.toByteArray(apiInputStream));
     } catch (IOException e) {
-      throw new CompilationError("Could not read the api database.", e);
+      diagnosticsHandler.warning(new ExceptionDiagnostic(e));
+      return new AndroidApiDataAccessNoBacking();
     }
   }
 
@@ -306,6 +311,10 @@
         serialized);
   }
 
+  public boolean isNoBacking() {
+    return false;
+  }
+
   public static class AndroidApiDataAccessByteMapped extends AndroidApiDataAccess {
 
     private final CompatByteBuffer mappedByteBuffer;
@@ -437,4 +446,38 @@
       return 0;
     }
   }
+
+  public static class AndroidApiDataAccessNoBacking extends AndroidApiDataAccess {
+
+    @Override
+    int readConstantPoolSize() {
+      throw new Unreachable();
+    }
+
+    @Override
+    PositionAndLength readPositionAndLength(int offset) {
+      throw new Unreachable();
+    }
+
+    @Override
+    boolean payloadHasConstantPoolValue(int offset, int length, byte[] value) {
+      throw new Unreachable();
+    }
+
+    @Override
+    int payloadContainsConstantPoolValue(
+        int offset, int length, byte[] value, BiPredicate<Integer, byte[]> predicate) {
+      throw new Unreachable();
+    }
+
+    @Override
+    byte readApiLevelForPayloadOffset(int offset, int length, byte[] value) {
+      throw new Unreachable();
+    }
+
+    @Override
+    public boolean isNoBacking() {
+      return true;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java
index 22d0c6d..2c6d00d 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java
@@ -175,11 +175,6 @@
     return lookupApiLevel(field);
   }
 
-  private int getConstantPoolId(DexString string) {
-    return constantPoolCache.computeIfAbsent(
-        string, key -> getDataAccess(options, diagnosticsHandler).getConstantPoolIndex(string));
-  }
-
   private AndroidApiLevel lookupApiLevel(DexReference reference) {
     // We use Android platform to track if an element is unknown since no occurrences of that api
     // level exists in the database.
@@ -187,10 +182,21 @@
         lookupCache.computeIfAbsent(
             reference,
             ref -> {
+              // Prefetch the data access
+              if (dataAccess == null) {
+                getDataAccess(options, diagnosticsHandler);
+              }
+              if (dataAccess.isNoBacking()) {
+                return ANDROID_PLATFORM;
+              }
               byte[] uniqueDescriptorForReference;
               try {
                 uniqueDescriptorForReference =
-                    getUniqueDescriptorForReference(ref, this::getConstantPoolId);
+                    getUniqueDescriptorForReference(
+                        ref,
+                        string ->
+                            constantPoolCache.computeIfAbsent(
+                                string, key -> dataAccess.getConstantPoolIndex(string)));
               } catch (Exception e) {
                 uniqueDescriptorForReference = getNonExistingDescriptor();
               }
@@ -198,8 +204,7 @@
                 return ANDROID_PLATFORM;
               } else {
                 byte apiLevelForReference =
-                    getDataAccess(options, diagnosticsHandler)
-                        .getApiLevelForReference(uniqueDescriptorForReference, ref);
+                    dataAccess.getApiLevelForReference(uniqueDescriptorForReference, ref);
                 return (apiLevelForReference <= 0)
                     ? ANDROID_PLATFORM
                     : AndroidApiLevel.getAndroidApiLevel(apiLevelForReference);
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 d1b5e9e..e612d68 100644
--- a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
@@ -545,13 +545,17 @@
         return frameTypeType() + "." + frameType.asPrimitive().getTypeName() + "Type()";
       }
     } else {
-      assert frameType.isInitialized();
+      assert frameType.isInitializedReferenceType();
+      assert !frameType.isInitializedNonNullReferenceTypeWithInterfaces()
+          : "Unexpected InitializedNonNullReferenceTypeWithInterfaces in CfFrame";
       if (frameType.isNullType()) {
-        return frameTypeType() + ".initialized(" + dexItemFactoryType() + ".nullValueType)";
+        return frameTypeType() + ".nullType()";
       } else {
+        assert frameType.isInitializedNonNullReferenceTypeWithoutInterfaces();
         return frameTypeType()
-            + ".initialized("
-            + dexType(frameType.asInitializedReferenceType().getInitializedType())
+            + ".initializedNonNullReference("
+            + dexType(
+                frameType.asInitializedNonNullReferenceTypeWithoutInterfaces().getInitializedType())
             + ")";
       }
     }
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 c3dac32..5869f46 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -68,6 +68,7 @@
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.Monitor;
@@ -457,7 +458,16 @@
 
   private void print(FrameType type) {
     if (type.isInitializedReferenceType()) {
-      appendType(type.asInitializedReferenceType().getInitializedType());
+      if (type.isNullType()) {
+        builder.append("null");
+      } else if (type.isInitializedNonNullReferenceTypeWithInterfaces()) {
+        appendTypeElement(
+            type.asInitializedNonNullReferenceTypeWithInterfaces()
+                .getInitializedTypeWithInterfaces());
+      } else {
+        assert type.isInitializedNonNullReferenceTypeWithoutInterfaces();
+        appendType(type.asInitializedNonNullReferenceTypeWithoutInterfaces().getInitializedType());
+      }
     } else if (type.isUninitializedNew()) {
       builder.append("uninitialized ").append(getLabel(type.getUninitializedLabel()));
     } else {
@@ -775,6 +785,10 @@
     }
   }
 
+  private void appendTypeElement(TypeElement type) {
+    builder.append(type.toString());
+  }
+
   private void appendClass(DexType type) {
     assert type.isArrayType() || type.isClassType();
     if (mapper == null) {
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 0bae2fd..bbffacb 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
@@ -115,14 +115,24 @@
             (state, head) -> {
               if (head.isNullType()) {
                 return getType() == MemberType.OBJECT
-                    ? state.push(config, FrameType.initialized(DexItemFactory.nullValueType))
+                    ? state.push(config, FrameType.nullType())
                     : state.push(appView, config, getType());
               }
-              return state.push(
-                  config,
-                  head.asInitializedReferenceType()
-                      .getInitializedType()
-                      .toArrayElementType(dexItemFactory));
+              if (head.isInitializedNonNullReferenceTypeWithInterfaces()) {
+                return state.push(
+                    config,
+                    head.asInitializedNonNullReferenceTypeWithInterfaces()
+                        .getInitializedTypeWithInterfaces()
+                        .asArrayType()
+                        .getMemberType());
+              } else {
+                assert head.isInitializedNonNullReferenceTypeWithoutInterfaces();
+                return state.push(
+                    config,
+                    head.asInitializedNonNullReferenceTypeWithoutInterfaces()
+                        .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 918203f..41f6774 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
@@ -36,6 +36,16 @@
     return getStoreType();
   }
 
+  @Override
+  public boolean isArrayStore() {
+    return true;
+  }
+
+  @Override
+  public CfArrayStore asArrayStore() {
+    return this;
+  }
+
   private int getStoreType() {
     switch (getType()) {
       case OBJECT:
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 5def6cf..6858fe9 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
@@ -66,10 +66,11 @@
       if (source.isInitialized()) {
         // Both are instantiated types and we resort to primitive type/java type hierarchy checking.
         return isAssignable(
-            source.asInitializedReferenceType().getInitializedType(),
-            target.asInitializedReferenceType().getInitializedType());
+            source.asInitializedReferenceType().getInitializedType(dexItemFactory),
+            target.asInitializedReferenceType().getInitializedType(dexItemFactory));
       }
-      return target.asInitializedReferenceType().getInitializedType() == dexItemFactory.objectType;
+      return target.asInitializedReferenceType().getInitializedType(dexItemFactory)
+          == dexItemFactory.objectType;
     }
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index 80813e1..9ff6d09 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -77,18 +77,6 @@
     return visitor.visit(this, other.asFieldInstruction(), CfFieldInstruction::specify);
   }
 
-  public boolean isFieldGet() {
-    return false;
-  }
-
-  public boolean isStaticFieldGet() {
-    return false;
-  }
-
-  public boolean isStaticFieldPut() {
-    return false;
-  }
-
   public abstract CfFieldInstruction createWithField(DexField field);
 
   @Override
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 f989239..6b2ee79 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
@@ -259,11 +259,10 @@
   }
 
   private void internalRegisterUse(UseRegistry<?> registry, FrameType frameType) {
-    if (frameType.isInitializedReferenceType()) {
-      if (frameType.isNullType()) {
-        return;
-      }
-      registry.registerTypeReference(frameType.asInitializedReferenceType().getInitializedType());
+    assert !frameType.isInitializedNonNullReferenceTypeWithInterfaces();
+    if (frameType.isInitializedNonNullReferenceTypeWithoutInterfaces()) {
+      registry.registerTypeReference(
+          frameType.asInitializedNonNullReferenceTypeWithoutInterfaces().getInitializedType());
     } else if (frameType.isUninitializedNew()) {
       registry.registerTypeReference(frameType.asUninitializedNew().getUninitializedNewType());
     }
@@ -303,12 +302,12 @@
   public static PreciseFrameType getInitializedFrameType(
       UninitializedFrameType unInit, UninitializedFrameType other, DexType newType) {
     if (unInit.isUninitializedThis() && other.isUninitializedThis()) {
-      return FrameType.initialized(newType);
+      return FrameType.initializedNonNullReference(newType);
     }
     if (unInit.isUninitializedNew()
         && other.isUninitializedNew()
         && unInit.getUninitializedLabel() == other.getUninitializedLabel()) {
-      return FrameType.initialized(newType);
+      return FrameType.initializedNonNullReference(newType);
     }
     return other;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
deleted file mode 100644
index 1a62143..0000000
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
+++ /dev/null
@@ -1,220 +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.cf.code;
-
-import com.android.tools.r8.cf.code.CfAssignability.AssignabilityResult;
-import com.android.tools.r8.cf.code.frame.FrameType;
-import com.android.tools.r8.cf.code.frame.PreciseFrameType;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.CfCodeDiagnostics;
-import com.android.tools.r8.graph.CfCodeStackMapValidatingException;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
-import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
-import com.android.tools.r8.optimize.interfaces.analysis.ConcreteCfFrameState;
-import com.android.tools.r8.utils.TraversalContinuation;
-import com.android.tools.r8.utils.collections.ImmutableDeque;
-import com.google.common.collect.Sets;
-import java.util.ArrayDeque;
-import java.util.Deque;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-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;
-  private final ProgramMethod method;
-  private final DexMethod previousMethod;
-
-  private final Map<CfLabel, CfFrame> stateMap;
-  private final List<CfTryCatch> tryCatchRanges;
-
-  private final Deque<CfTryCatch> currentCatchRanges = new ArrayDeque<>();
-  private final Set<CfLabel> tryCatchRangeLabels;
-
-  public CfFrameVerificationHelper(
-      AppView<?> appView,
-      CfCode code,
-      GraphLens codeLens,
-      ProgramMethod method,
-      Map<CfLabel, CfFrame> stateMap,
-      List<CfTryCatch> tryCatchRanges) {
-    this.appView = appView;
-    this.assignability = new CfAssignability(appView);
-    this.code = code;
-    this.codeLens = codeLens;
-    this.method = method;
-    this.previousMethod =
-        appView.graphLens().getOriginalMethodSignature(method.getReference(), codeLens);
-    this.stateMap = stateMap;
-    this.tryCatchRanges = tryCatchRanges;
-    this.factory = appView.dexItemFactory();
-    // Compute all labels that marks a start or end to catch ranges.
-    tryCatchRangeLabels = Sets.newIdentityHashSet();
-    for (CfTryCatch tryCatchRange : tryCatchRanges) {
-      tryCatchRangeLabels.add(tryCatchRange.start);
-      tryCatchRangeLabels.add(tryCatchRange.end);
-    }
-  }
-
-  @Override
-  public CfAssignability getAssignability() {
-    return assignability;
-  }
-
-  @Override
-  public DexMethod getCurrentContext() {
-    return previousMethod;
-  }
-
-  @Override
-  public int getMaxLocals() {
-    return code.getMaxLocals();
-  }
-
-  @Override
-  public int getMaxStack() {
-    return code.getMaxStack();
-  }
-
-  @Override
-  public boolean isImmediateSuperClassOfCurrentContext(DexType type) {
-    // If the code is rewritten according to the graph lens, we perform a strict check that the
-    // given type is the same as the current holder's super class.
-    if (codeLens == appView.graphLens()) {
-      return type == method.getHolder().getSuperType();
-    }
-    // Otherwise, we don't know what the super class of the current class was at the point of the
-    // code lens. We return true, which has the consequence that we may accept a constructor call
-    // for an uninitialized-this value where the constructor is not defined in the immediate parent
-    // class.
-    return true;
-  }
-
-  public void seenLabel(CfLabel label) {
-    if (tryCatchRangeLabels.contains(label)) {
-      for (CfTryCatch tryCatchRange : tryCatchRanges) {
-        if (tryCatchRange.start == label) {
-          currentCatchRanges.add(tryCatchRange);
-        }
-      }
-      currentCatchRanges.removeIf(currentRange -> currentRange.end == label);
-    }
-  }
-
-  public CfCodeDiagnostics checkTryCatchRanges() {
-    for (CfTryCatch tryCatchRange : tryCatchRanges) {
-      CfCodeDiagnostics diagnostics = checkTryCatchRange(tryCatchRange);
-      if (diagnostics != null) {
-        return diagnostics;
-      }
-    }
-    return null;
-  }
-
-  public CfCodeDiagnostics checkTryCatchRange(CfTryCatch tryCatchRange) {
-    // According to the spec:
-    // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1
-    // saying ` and the handler's target (the initial instruction of the handler code) is type
-    // safe assuming an incoming type state T. The type state T is derived from ExcStackFrame
-    // by replacing the operand stack with a stack whose sole element is the handler's
-    // exception class.
-    for (CfLabel target : tryCatchRange.getTargets()) {
-      CfFrame destinationFrame = stateMap.get(target);
-      if (destinationFrame == null) {
-        return CfCodeStackMapValidatingException.invalidTryCatchRange(
-            method, tryCatchRange, "No frame for target catch range target", appView);
-      }
-      // From the spec: the handler's exception class is assignable to the class Throwable.
-      for (DexType guard : tryCatchRange.guards) {
-        if (!assignability.isAssignable(guard, factory.throwableType)) {
-          return CfCodeStackMapValidatingException.invalidTryCatchRange(
-              method,
-              tryCatchRange,
-              "Could not assign " + guard.getTypeName() + " to java.lang.Throwable",
-              appView);
-        }
-        Deque<PreciseFrameType> sourceStack = ImmutableDeque.of(FrameType.initialized(guard));
-        AssignabilityResult assignabilityResult =
-            assignability.isStackAssignable(sourceStack, destinationFrame.getStack());
-        if (assignabilityResult.isFailed()) {
-          return CfCodeStackMapValidatingException.invalidTryCatchRange(
-              method, tryCatchRange, assignabilityResult.asFailed().getMessage(), appView);
-        }
-      }
-    }
-    return null;
-  }
-
-  public CfFrameState checkExceptionEdges(CfFrameState state) {
-    for (CfTryCatch currentCatchRange : currentCatchRanges) {
-      for (CfLabel target : currentCatchRange.getTargets()) {
-        CfFrame destinationFrame = stateMap.get(target);
-        if (destinationFrame == null) {
-          return CfFrameState.error("No frame for target catch range target");
-        }
-        state = state.checkLocals(this, destinationFrame);
-      }
-    }
-    return state;
-  }
-
-  public CfFrameState checkTarget(CfFrameState state, CfLabel label) {
-    CfFrame destinationFrame = getDestinationFrame(label);
-    return destinationFrame != null
-        ? state.checkLocals(this, destinationFrame).checkStack(this, destinationFrame)
-        : CfFrameState.error("No destination frame");
-  }
-
-  private CfFrame getDestinationFrame(CfLabel label) {
-    return stateMap.get(label);
-  }
-
-  public TraversalContinuation<CfCodeDiagnostics, CfFrameState> computeStateForNextInstruction(
-      CfInstruction instruction, int instructionIndex, CfFrameState state) {
-    if (!instruction.isJump()) {
-      return TraversalContinuation.doContinue(state);
-    }
-    if (instructionIndex == code.getInstructions().size() - 1) {
-      return TraversalContinuation.doContinue(CfFrameState.bottom());
-    }
-    if (instructionIndex == code.getInstructions().size() - 2
-        && code.getInstructions().get(instructionIndex + 1).isLabel()) {
-      return TraversalContinuation.doContinue(CfFrameState.bottom());
-    }
-    if (instruction.asJump().hasFallthrough()) {
-      return TraversalContinuation.doContinue(state);
-    }
-    int nextInstructionIndex = instructionIndex + 1;
-    CfInstruction nextInstruction = code.getInstructions().get(nextInstructionIndex);
-    CfFrame nextFrame = null;
-    if (nextInstruction.isFrame()) {
-      nextFrame = nextInstruction.asFrame();
-    } else if (nextInstruction.isLabel()) {
-      nextFrame = getDestinationFrame(nextInstruction.asLabel());
-    }
-    if (nextFrame != null) {
-      CfFrame currentFrameCopy = nextFrame.mutableCopy();
-      return TraversalContinuation.doContinue(
-          new ConcreteCfFrameState(
-              currentFrameCopy.getMutableLocals(),
-              currentFrameCopy.getMutableStack(),
-              currentFrameCopy.computeStackSize()));
-    }
-    return TraversalContinuation.doBreak(
-        CfCodeStackMapValidatingException.invalidStackMapForInstruction(
-            method, nextInstructionIndex, nextInstruction, "Expected frame instruction", appView));
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
new file mode 100644
index 0000000..7866b2d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
@@ -0,0 +1,455 @@
+// 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.cf.code;
+
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.cf.code.CfAssignability.AssignabilityResult;
+import com.android.tools.r8.cf.code.frame.FrameType;
+import com.android.tools.r8.cf.code.frame.PreciseFrameType;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.CfCodeDiagnostics;
+import com.android.tools.r8.graph.CfCodeStackMapValidatingException;
+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.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
+import com.android.tools.r8.optimize.interfaces.analysis.ConcreteCfFrameState;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.collections.ImmutableDeque;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+public class CfFrameVerifier {
+
+  private final AppView<?> appView;
+  private final CfCode code;
+  private final CfAnalysisConfig config;
+  private final CfFrameVerifierEventConsumer eventConsumer;
+  private final DexItemFactory factory;
+  private final ProgramMethod method;
+  private final Optional<DexMethod> previousMethod;
+  private final boolean previousMethodIsInstance;
+
+  private final Deque<CfTryCatch> activeCatchHandlers = new ArrayDeque<>();
+  private final Set<CfLabel> tryCatchRangeLabels;
+
+  public CfFrameVerifier(
+      AppView<?> appView,
+      CfCode code,
+      CfAnalysisConfig config,
+      CfFrameVerifierEventConsumer eventConsumer,
+      ProgramMethod method,
+      Optional<DexMethod> previousMethod,
+      boolean previousMethodIsInstance) {
+    this.appView = appView;
+    this.code = code;
+    this.config = config;
+    this.eventConsumer = eventConsumer;
+    this.factory = appView.dexItemFactory();
+    this.method = method;
+    this.previousMethod = previousMethod;
+    this.previousMethodIsInstance = previousMethodIsInstance;
+    this.tryCatchRangeLabels = code.getTryCatchRangeLabels();
+  }
+
+  public static Builder builder(AppView<?> appView, CfCode code, ProgramMethod method) {
+    return new Builder(appView, code, method);
+  }
+
+  public StackMapStatus run() {
+    if (!appView.options().canUseInputStackMaps()
+        || appView.options().testing.disableStackMapVerification) {
+      return StackMapStatus.NOT_PRESENT;
+    }
+
+    DexEncodedMethod definition = method.getDefinition();
+    if (definition.hasClassFileVersion()
+        && definition.getClassFileVersion().isLessThan(CfVersion.V1_7)) {
+      return StackMapStatus.NOT_PRESENT;
+    }
+
+    // Build a map from labels to frames.
+    TraversalContinuation<CfCodeDiagnostics, Map<CfLabel, CfFrame>> labelToFrameMapOrError =
+        buildLabelToFrameMap();
+    if (labelToFrameMapOrError.shouldBreak()) {
+      return fail(labelToFrameMapOrError);
+    }
+    Map<CfLabel, CfFrame> labelToFrameMap = labelToFrameMapOrError.asContinue().getValue();
+
+    // Check try catch ranges.
+    CfCodeDiagnostics diagnostics = checkTryCatchRanges(labelToFrameMap);
+    if (diagnostics != null) {
+      return fail(diagnostics);
+    }
+
+    // Compute initial state.
+    TraversalContinuation<CfCodeDiagnostics, CfFrameState> initialState = computeInitialState();
+    if (initialState.shouldBreak()) {
+      return fail(initialState);
+    }
+
+    // Linear scan over instructions.
+    CfFrameState state = initialState.asContinue().getValue();
+    for (int i = 0; i < code.getInstructions().size(); i++) {
+      CfInstruction instruction = code.getInstruction(i);
+      assert !state.isError();
+      // Check the exceptional edge prior to evaluating the instruction. The local state is stable
+      // at this point as store operations are not throwing and the current stack does not
+      // affect the exceptional transfer (the exception edge is always a singleton stack).
+      if (instruction.canThrow()) {
+        assert !instruction.isStore();
+        state = checkExceptionEdges(state, labelToFrameMap);
+      }
+      if (instruction.isLabel()) {
+        updateActiveCatchHandlers(instruction.asLabel());
+      }
+      eventConsumer.acceptInstructionState(instruction, state);
+      state = instruction.evaluate(state, appView, config);
+      if (instruction.isJumpWithNormalTarget()) {
+        CfInstruction fallthroughInstruction =
+            (i + 1) < code.getInstructions().size() ? code.getInstruction(i + 1) : null;
+        TraversalContinuation<CfCodeDiagnostics, CfFrameState> traversalContinuation =
+            instruction.traverseNormalTargets(
+                (target, currentState) -> {
+                  if (target != fallthroughInstruction) {
+                    assert target.isLabel();
+                    currentState = checkTarget(currentState, target.asLabel(), labelToFrameMap);
+                  }
+                  return TraversalContinuation.doContinue(currentState);
+                },
+                fallthroughInstruction,
+                state);
+        state = traversalContinuation.asContinue().getValue();
+      }
+      TraversalContinuation<CfCodeDiagnostics, CfFrameState> traversalContinuation =
+          computeStateForNextInstruction(instruction, i, state, labelToFrameMap);
+      if (traversalContinuation.isContinue()) {
+        state = traversalContinuation.asContinue().getValue();
+      } else {
+        return fail(traversalContinuation);
+      }
+      if (state.isError()) {
+        return fail(
+            CfCodeStackMapValidatingException.invalidStackMapForInstruction(
+                method, i, instruction, state.asError().getMessage(), appView));
+      }
+    }
+    return StackMapStatus.VALID;
+  }
+
+  private TraversalContinuation<CfCodeDiagnostics, Map<CfLabel, CfFrame>> buildLabelToFrameMap() {
+    Map<CfLabel, CfFrame> labelToFrameMap = new IdentityHashMap<>();
+    List<CfLabel> labels = new ArrayList<>();
+    boolean requireStackMapFrame = !code.getTryCatchRanges().isEmpty();
+    for (CfInstruction instruction : code.getInstructions()) {
+      if (instruction.isFrame()) {
+        CfFrame frame = instruction.asFrame();
+        if (!labels.isEmpty()) {
+          for (CfLabel label : labels) {
+            if (labelToFrameMap.containsKey(label)) {
+              return TraversalContinuation.doBreak(
+                  CfCodeStackMapValidatingException.multipleFramesForLabel(method, appView));
+            }
+            labelToFrameMap.put(label, frame);
+          }
+        } else if (instruction != code.getInstruction(0)) {
+          // From b/168212806, it is possible that the first instruction is a frame.
+          return TraversalContinuation.doBreak(
+              CfCodeStackMapValidatingException.unexpectedStackMapFrame(method, appView));
+        }
+      }
+      // We are trying to map a frame to a label, but we can have positions in between, so skip
+      // those.
+      if (instruction.isPosition()) {
+        continue;
+      } else if (instruction.isLabel()) {
+        labels.add(instruction.asLabel());
+      } else {
+        labels.clear();
+      }
+      if (!requireStackMapFrame) {
+        requireStackMapFrame = instruction.isJump() && !isFinalAndExitInstruction(instruction);
+      }
+    }
+    // If there are no frames but we have seen a jump instruction, we cannot verify the stack map.
+    if (requireStackMapFrame && labelToFrameMap.isEmpty()) {
+      return TraversalContinuation.doBreak(
+          CfCodeStackMapValidatingException.noFramesForMethodWithJumps(method, appView));
+    }
+    return TraversalContinuation.doContinue(labelToFrameMap);
+  }
+
+  private StackMapStatus fail(TraversalContinuation<CfCodeDiagnostics, ?> traversalContinuation) {
+    assert traversalContinuation.shouldBreak();
+    return fail(traversalContinuation.asBreak().getValue());
+  }
+
+  private StackMapStatus fail(CfCodeDiagnostics diagnostics) {
+    eventConsumer.acceptError(diagnostics);
+    return StackMapStatus.INVALID;
+  }
+
+  private void updateActiveCatchHandlers(CfLabel label) {
+    if (tryCatchRangeLabels.contains(label)) {
+      for (CfTryCatch tryCatchRange : code.getTryCatchRanges()) {
+        if (tryCatchRange.start == label) {
+          activeCatchHandlers.add(tryCatchRange);
+        }
+      }
+      activeCatchHandlers.removeIf(currentRange -> currentRange.end == label);
+    }
+  }
+
+  private CfCodeDiagnostics checkTryCatchRanges(Map<CfLabel, CfFrame> labelToFrameMap) {
+    for (CfTryCatch tryCatchRange : code.getTryCatchRanges()) {
+      CfCodeDiagnostics diagnostics = checkTryCatchRange(tryCatchRange, labelToFrameMap);
+      if (diagnostics != null) {
+        return diagnostics;
+      }
+    }
+    return null;
+  }
+
+  private CfCodeDiagnostics checkTryCatchRange(
+      CfTryCatch tryCatchRange, Map<CfLabel, CfFrame> labelToFrameMap) {
+    // According to the spec:
+    // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1
+    // saying ` and the handler's target (the initial instruction of the handler code) is type
+    // safe assuming an incoming type state T. The type state T is derived from ExcStackFrame
+    // by replacing the operand stack with a stack whose sole element is the handler's
+    // exception class.
+    for (CfLabel target : tryCatchRange.getTargets()) {
+      CfFrame destinationFrame = labelToFrameMap.get(target);
+      if (destinationFrame == null) {
+        return CfCodeStackMapValidatingException.invalidTryCatchRange(
+            method, tryCatchRange, "No frame for target catch range target", appView);
+      }
+      // From the spec: the handler's exception class is assignable to the class Throwable.
+      for (DexType guard : tryCatchRange.guards) {
+        if (!config.getAssignability().isAssignable(guard, factory.throwableType)) {
+          return CfCodeStackMapValidatingException.invalidTryCatchRange(
+              method,
+              tryCatchRange,
+              "Could not assign " + guard.getTypeName() + " to java.lang.Throwable",
+              appView);
+        }
+        Deque<PreciseFrameType> sourceStack =
+            ImmutableDeque.of(FrameType.initializedNonNullReference(guard));
+        AssignabilityResult assignabilityResult =
+            config.getAssignability().isStackAssignable(sourceStack, destinationFrame.getStack());
+        if (assignabilityResult.isFailed()) {
+          return CfCodeStackMapValidatingException.invalidTryCatchRange(
+              method, tryCatchRange, assignabilityResult.asFailed().getMessage(), appView);
+        }
+      }
+    }
+    return null;
+  }
+
+  private CfFrameState checkExceptionEdges(
+      CfFrameState state, Map<CfLabel, CfFrame> labelToFrameMap) {
+    for (CfTryCatch currentCatchRange : activeCatchHandlers) {
+      for (CfLabel target : currentCatchRange.getTargets()) {
+        CfFrame destinationFrame = labelToFrameMap.get(target);
+        if (destinationFrame == null) {
+          return CfFrameState.error("No frame for target catch range target");
+        }
+        state = state.checkLocals(config, destinationFrame);
+      }
+    }
+    return state;
+  }
+
+  private CfFrameState checkTarget(
+      CfFrameState state, CfLabel label, Map<CfLabel, CfFrame> labelToFrameMap) {
+    CfFrame destinationFrame = labelToFrameMap.get(label);
+    return destinationFrame != null
+        ? state.checkLocals(config, destinationFrame).checkStack(config, destinationFrame)
+        : CfFrameState.error("No destination frame");
+  }
+
+  private TraversalContinuation<CfCodeDiagnostics, CfFrameState> computeInitialState() {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    CfFrameState state = new ConcreteCfFrameState();
+    int localIndex = 0;
+    DexMethod context = previousMethod.orElse(method.getReference());
+    if (method.getDefinition().isInstance() || previousMethodIsInstance) {
+      state =
+          state.storeLocal(
+              localIndex,
+              context.isInstanceInitializer(dexItemFactory)
+                      || context.mustBeInlinedIntoInstanceInitializer(appView)
+                      || context.isHorizontallyMergedInstanceInitializer(dexItemFactory)
+                  ? FrameType.uninitializedThis()
+                  : FrameType.initializedNonNullReference(context.getHolderType()),
+              config);
+      localIndex++;
+    }
+    for (DexType parameter : context.getParameters()) {
+      state = state.storeLocal(localIndex, FrameType.initialized(parameter), config);
+      localIndex += parameter.getRequiredRegisters();
+    }
+    if (state.isError()) {
+      return TraversalContinuation.doBreak(
+          CfCodeStackMapValidatingException.invalidStackMapForInstruction(
+              method, 0, code.getInstruction(0), state.asError().getMessage(), appView));
+    }
+    return TraversalContinuation.doContinue(state);
+  }
+
+  private TraversalContinuation<CfCodeDiagnostics, CfFrameState> computeStateForNextInstruction(
+      CfInstruction instruction,
+      int instructionIndex,
+      CfFrameState state,
+      Map<CfLabel, CfFrame> labelToFrameMap) {
+    if (!instruction.isJump()) {
+      return TraversalContinuation.doContinue(state);
+    }
+    if (instructionIndex == code.getInstructions().size() - 1) {
+      return TraversalContinuation.doContinue(CfFrameState.bottom());
+    }
+    if (instructionIndex == code.getInstructions().size() - 2
+        && code.getInstruction(instructionIndex + 1).isLabel()) {
+      return TraversalContinuation.doContinue(CfFrameState.bottom());
+    }
+    if (instruction.asJump().hasFallthrough()) {
+      return TraversalContinuation.doContinue(state);
+    }
+    int nextInstructionIndex = instructionIndex + 1;
+    CfInstruction nextInstruction = code.getInstruction(nextInstructionIndex);
+    CfFrame nextFrame = null;
+    if (nextInstruction.isFrame()) {
+      nextFrame = nextInstruction.asFrame();
+    } else if (nextInstruction.isLabel()) {
+      nextFrame = labelToFrameMap.get(nextInstruction.asLabel());
+    }
+    if (nextFrame != null) {
+      CfFrame currentFrameCopy = nextFrame.mutableCopy();
+      return TraversalContinuation.doContinue(
+          new ConcreteCfFrameState(
+              currentFrameCopy.getMutableLocals(),
+              currentFrameCopy.getMutableStack(),
+              currentFrameCopy.computeStackSize()));
+    }
+    return TraversalContinuation.doBreak(
+        CfCodeStackMapValidatingException.invalidStackMapForInstruction(
+            method, nextInstructionIndex, nextInstruction, "Expected frame instruction", appView));
+  }
+
+  private boolean isFinalAndExitInstruction(CfInstruction instruction) {
+    boolean isReturnOrThrow = instruction.isThrow() || instruction.isReturn();
+    if (!isReturnOrThrow) {
+      return false;
+    }
+    for (int i = code.getInstructions().size() - 1; i >= 0; i--) {
+      CfInstruction instr = code.getInstruction(i);
+      if (instr == instruction) {
+        return true;
+      }
+      if (instr.isPosition() || instr.isLabel()) {
+        continue;
+      }
+      return false;
+    }
+    throw new Unreachable("Instruction " + instruction + " should be in instructions");
+  }
+
+  public enum StackMapStatus {
+    NOT_VERIFIED,
+    NOT_PRESENT,
+    INVALID,
+    VALID;
+
+    public boolean isNotPresent() {
+      return this == NOT_PRESENT;
+    }
+
+    public boolean isValid() {
+      return this == VALID;
+    }
+
+    public boolean isValidOrNotPresent() {
+      return this == VALID || this == NOT_PRESENT;
+    }
+
+    public boolean isInvalidOrNotPresent() {
+      return this == INVALID || this == NOT_PRESENT;
+    }
+  }
+
+  public static class Builder {
+
+    private final AppView<?> appView;
+    private final CfCode code;
+    private final ProgramMethod method;
+
+    private CfAnalysisConfig config;
+    private CfFrameVerifierEventConsumer eventConsumer;
+    private Optional<DexMethod> previousMethod = Optional.empty();
+    private boolean previousMethodIsInstance;
+
+    Builder(AppView<?> appView, CfCode code, ProgramMethod method) {
+      this.appView = appView;
+      this.code = code;
+      this.method = method;
+    }
+
+    public Builder setCodeLens(GraphLens codeLens) {
+      if (codeLens != appView.graphLens()) {
+        this.previousMethod =
+            Optional.of(
+                appView.graphLens().getOriginalMethodSignature(method.getReference(), codeLens));
+        this.previousMethodIsInstance =
+            method.getDefinition().isInstance()
+                || appView
+                    .graphLens()
+                    .lookupPrototypeChangesForMethodDefinition(method.getReference(), codeLens)
+                    .getArgumentInfoCollection()
+                    .isConvertedToStaticMethod();
+      }
+      return this;
+    }
+
+    public Builder setConfig(CfAnalysisConfig config) {
+      this.config = config;
+      return this;
+    }
+
+    public Builder setEventConsumer(CfFrameVerifierEventConsumer eventConsumer) {
+      this.eventConsumer = eventConsumer;
+      return this;
+    }
+
+    public CfFrameVerifier build() {
+      assert eventConsumer != null;
+      return new CfFrameVerifier(
+          appView,
+          code,
+          buildConfig(),
+          eventConsumer,
+          method,
+          previousMethod,
+          previousMethodIsInstance);
+    }
+
+    private CfAnalysisConfig buildConfig() {
+      return config != null
+          ? config
+          : new CfFrameVerifierDefaultAnalysisConfig(appView, code, method, previousMethod);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifierDefaultAnalysisConfig.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifierDefaultAnalysisConfig.java
new file mode 100644
index 0000000..82ea7ef
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifierDefaultAnalysisConfig.java
@@ -0,0 +1,68 @@
+// 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.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
+import java.util.Optional;
+
+public class CfFrameVerifierDefaultAnalysisConfig implements CfAnalysisConfig {
+
+  private final CfAssignability assignability;
+  private final CfCode code;
+  private final ProgramMethod method;
+  private final Optional<DexMethod> previousMethod;
+
+  CfFrameVerifierDefaultAnalysisConfig(
+      AppView<?> appView, CfCode code, ProgramMethod method, Optional<DexMethod> previousMethod) {
+    this.assignability = new CfAssignability(appView);
+    this.code = code;
+    this.method = method;
+    this.previousMethod = previousMethod;
+  }
+
+  @Override
+  public CfAssignability getAssignability() {
+    return assignability;
+  }
+
+  @Override
+  public DexMethod getCurrentContext() {
+    return previousMethod.orElse(method.getReference());
+  }
+
+  @Override
+  public int getMaxLocals() {
+    return code.getMaxLocals();
+  }
+
+  @Override
+  public int getMaxStack() {
+    return code.getMaxStack();
+  }
+
+  @Override
+  public boolean isImmediateSuperClassOfCurrentContext(DexType type) {
+    // If the code is rewritten according to the graph lens, we perform a strict check that the
+    // given type is the same as the current holder's super class.
+    if (!previousMethod.isPresent()) {
+      return type == method.getHolder().getSuperType();
+    }
+    // Otherwise, we don't know what the super class of the current class was at the point of the
+    // code lens. We return true, which has the consequence that we may accept a constructor call
+    // for an uninitialized-this value where the constructor is not defined in the immediate parent
+    // class.
+    return true;
+  }
+
+  @Override
+  public boolean isStrengthenFramesEnabled() {
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifierEventConsumer.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifierEventConsumer.java
new file mode 100644
index 0000000..043dea6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifierEventConsumer.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 com.android.tools.r8.cf.code;
+
+import com.android.tools.r8.graph.CfCodeDiagnostics;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
+
+public interface CfFrameVerifierEventConsumer {
+
+  default void acceptError(CfCodeDiagnostics diagnostics) {}
+
+  default void acceptInstructionState(CfInstruction instruction, CfFrameState state) {}
+}
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 089f5ee..5384772 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
@@ -43,6 +43,11 @@
   }
 
   @Override
+  public boolean isInstanceFieldGet() {
+    return true;
+  }
+
+  @Override
   public CfFieldInstruction createWithField(DexField otherField) {
     return new CfInstanceFieldRead(otherField);
   }
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 c492db1..594f126 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
@@ -46,6 +46,21 @@
   }
 
   @Override
+  public boolean isFieldPut() {
+    return true;
+  }
+
+  @Override
+  public boolean isInstanceFieldPut() {
+    return true;
+  }
+
+  @Override
+  public CfInstanceFieldWrite asInstanceFieldPut() {
+    return this;
+  }
+
+  @Override
   void internalRegisterUse(
       UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerInstanceFieldWrite(getField());
@@ -71,6 +86,7 @@
     return frame
         .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/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 66e3d69..bc8e8fb 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
@@ -135,6 +135,14 @@
     return TraversalContinuation.doContinue(initialValue);
   }
 
+  public boolean isArrayStore() {
+    return false;
+  }
+
+  public CfArrayStore asArrayStore() {
+    return null;
+  }
+
   @Override
   public CfInstruction asCfInstruction() {
     return this;
@@ -182,6 +190,38 @@
     return false;
   }
 
+  public boolean isFieldGet() {
+    return false;
+  }
+
+  public boolean isInstanceFieldGet() {
+    return false;
+  }
+
+  public boolean isStaticFieldGet() {
+    return false;
+  }
+
+  public boolean isFieldPut() {
+    return false;
+  }
+
+  public boolean isInstanceFieldPut() {
+    return false;
+  }
+
+  public CfInstanceFieldWrite asInstanceFieldPut() {
+    return null;
+  }
+
+  public boolean isStaticFieldPut() {
+    return false;
+  }
+
+  public CfStaticFieldWrite asStaticFieldPut() {
+    return null;
+  }
+
   public CfGoto asGoto() {
     return null;
   }
@@ -311,6 +351,10 @@
     return false;
   }
 
+  public CfReturn asReturn() {
+    return null;
+  }
+
   public boolean isReturnVoid() {
     return false;
   }
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 fe5af15..8fb0024 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
@@ -106,6 +106,11 @@
   }
 
   @Override
+  public CfReturn asReturn() {
+    return this;
+  }
+
+  @Override
   public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
     Slot pop = state.pop();
     builder.addReturn(pop.register);
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 bdad2a3..c904d10 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
@@ -42,11 +42,21 @@
   }
 
   @Override
+  public boolean isFieldPut() {
+    return true;
+  }
+
+  @Override
   public boolean isStaticFieldPut() {
     return true;
   }
 
   @Override
+  public CfStaticFieldWrite asStaticFieldPut() {
+    return this;
+  }
+
+  @Override
   void internalRegisterUse(
       UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerStaticFieldWrite(getField());
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 e8eb7b3..42ba495 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
@@ -76,12 +76,17 @@
   }
 
   @Override
+  public NullFrameType asNullType() {
+    return null;
+  }
+
+  @Override
   public boolean isObject() {
     return false;
   }
 
   @Override
-  public DexType getObjectType(DexType context) {
+  public DexType getObjectType(DexItemFactory dexItemFactory, DexType context) {
     assert false : "Unexpected use of getObjectType() for non-object FrameType";
     return null;
   }
@@ -139,6 +144,38 @@
   }
 
   @Override
+  public boolean isInitializedNonNullReferenceType() {
+    return false;
+  }
+
+  @Override
+  public InitializedNonNullReferenceFrameType asInitializedNonNullReferenceType() {
+    return null;
+  }
+
+  @Override
+  public boolean isInitializedNonNullReferenceTypeWithoutInterfaces() {
+    return false;
+  }
+
+  @Override
+  public InitializedNonNullReferenceFrameTypeWithoutInterfaces
+      asInitializedNonNullReferenceTypeWithoutInterfaces() {
+    return null;
+  }
+
+  @Override
+  public boolean isInitializedNonNullReferenceTypeWithInterfaces() {
+    return false;
+  }
+
+  @Override
+  public InitializedNonNullReferenceFrameTypeWithInterfaces
+      asInitializedNonNullReferenceTypeWithInterfaces() {
+    return null;
+  }
+
+  @Override
   public boolean isWide() {
     return false;
   }
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 823380f..bf16f11 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
@@ -9,6 +9,9 @@
 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.ir.analysis.type.PrimitiveTypeElement;
+import com.android.tools.r8.ir.analysis.type.ReferenceTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.naming.NamingLens;
@@ -63,14 +66,50 @@
     return initializedReference(type);
   }
 
+  static InitializedFrameType initialized(TypeElement type) {
+    if (type.isPrimitiveType()) {
+      return primitive(type.asPrimitiveType());
+    }
+    return initializedReference(type.asReferenceType());
+  }
+
   static InitializedReferenceFrameType initializedReference(DexType type) {
     assert type.isReferenceType();
-    return new InitializedReferenceFrameType(type);
+    return type.isNullValueType() ? nullType() : initializedNonNullReference(type);
+  }
+
+  static InitializedReferenceFrameType initializedReference(ReferenceTypeElement type) {
+    return type.isNullType() ? nullType() : initializedNonNullReference(type);
+  }
+
+  static InitializedNonNullReferenceFrameTypeWithoutInterfaces initializedNonNullReference(
+      DexType type) {
+    assert type.isReferenceType();
+    assert !type.isNullValueType();
+    return new InitializedNonNullReferenceFrameTypeWithoutInterfaces(type);
+  }
+
+  static InitializedNonNullReferenceFrameTypeWithInterfaces initializedNonNullReference(
+      ReferenceTypeElement type) {
+    assert !type.isNullType();
+    return new InitializedNonNullReferenceFrameTypeWithInterfaces(type);
+  }
+
+  static NullFrameType nullType() {
+    return NullFrameType.SINGLETON;
   }
 
   static PrimitiveFrameType primitive(DexType type) {
     assert type.isPrimitiveType();
-    switch (type.getDescriptor().getFirstByteAsChar()) {
+    return internalPrimitive(type.getDescriptor().getFirstByteAsChar());
+  }
+
+  static PrimitiveFrameType primitive(PrimitiveTypeElement type) {
+    return internalPrimitive(type.getDescriptor().charAt(0));
+  }
+
+  static PrimitiveFrameType internalPrimitive(char descriptor) {
+    switch (descriptor) {
       case 'Z':
         return booleanType();
       case 'B':
@@ -88,7 +127,7 @@
       case 'S':
         return shortType();
       default:
-        throw new Unreachable("Unexpected primitive type: " + type.getTypeName());
+        throw new Unreachable("Unexpected primitive type: " + descriptor);
     }
   }
 
@@ -116,7 +155,7 @@
     assert memberType.isPrecise();
     switch (memberType) {
       case OBJECT:
-        return FrameType.initialized(factory.objectType);
+        return FrameType.initializedNonNullReference(factory.objectType);
       case BOOLEAN_OR_BYTE:
       case CHAR:
       case SHORT:
@@ -135,7 +174,7 @@
 
   DexType getInitializedType(DexItemFactory dexItemFactory);
 
-  DexType getObjectType(DexType context);
+  DexType getObjectType(DexItemFactory dexItemFactory, DexType context);
 
   Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens);
 
@@ -165,6 +204,20 @@
 
   InitializedReferenceFrameType asInitializedReferenceType();
 
+  boolean isInitializedNonNullReferenceType();
+
+  InitializedNonNullReferenceFrameType asInitializedNonNullReferenceType();
+
+  boolean isInitializedNonNullReferenceTypeWithoutInterfaces();
+
+  InitializedNonNullReferenceFrameTypeWithoutInterfaces
+      asInitializedNonNullReferenceTypeWithoutInterfaces();
+
+  boolean isInitializedNonNullReferenceTypeWithInterfaces();
+
+  InitializedNonNullReferenceFrameTypeWithInterfaces
+      asInitializedNonNullReferenceTypeWithInterfaces();
+
   boolean isInt();
 
   boolean isLong();
@@ -175,6 +228,8 @@
 
   boolean isNullType();
 
+  NullFrameType asNullType();
+
   boolean isObject();
 
   boolean isOneWord();
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/InitializedNonNullReferenceFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedNonNullReferenceFrameType.java
new file mode 100644
index 0000000..d55633a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedNonNullReferenceFrameType.java
@@ -0,0 +1,68 @@
+// 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.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.ir.analysis.type.ReferenceTypeElement;
+
+public abstract class InitializedNonNullReferenceFrameType extends BaseFrameType
+    implements InitializedReferenceFrameType {
+
+  @Override
+  public final boolean isInitialized() {
+    return true;
+  }
+
+  @Override
+  public final boolean isInitializedReferenceType() {
+    return true;
+  }
+
+  @Override
+  public final InitializedNonNullReferenceFrameType asInitializedReferenceType() {
+    return this;
+  }
+
+  @Override
+  public final boolean isInitializedNonNullReferenceType() {
+    return true;
+  }
+
+  @Override
+  public final InitializedNonNullReferenceFrameType asInitializedNonNullReferenceType() {
+    return this;
+  }
+
+  @Override
+  public final boolean isObject() {
+    return true;
+  }
+
+  @Override
+  public final boolean isPrecise() {
+    return true;
+  }
+
+  @Override
+  public final PreciseFrameType asPrecise() {
+    return this;
+  }
+
+  @Override
+  public final SingleFrameType asSingle() {
+    return this;
+  }
+
+  public abstract ReferenceTypeElement getInitializedTypeWithInterfaces(
+      AppView<? extends AppInfoWithClassHierarchy> appView);
+
+  @Override
+  public final DexType getObjectType(DexItemFactory dexItemFactory, DexType context) {
+    return getInitializedType(dexItemFactory);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/InitializedNonNullReferenceFrameTypeWithInterfaces.java b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedNonNullReferenceFrameTypeWithInterfaces.java
new file mode 100644
index 0000000..d1187c0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedNonNullReferenceFrameTypeWithInterfaces.java
@@ -0,0 +1,105 @@
+// 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.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.GraphLens;
+import com.android.tools.r8.ir.analysis.type.ReferenceTypeElement;
+import com.android.tools.r8.naming.NamingLens;
+
+public class InitializedNonNullReferenceFrameTypeWithInterfaces
+    extends InitializedNonNullReferenceFrameType {
+
+  private final ReferenceTypeElement type;
+  private DexType initializedTypeCache;
+
+  InitializedNonNullReferenceFrameTypeWithInterfaces(ReferenceTypeElement type) {
+    assert type != null;
+    assert !type.isNullType();
+    this.type = type;
+  }
+
+  @Override
+  public boolean isInitializedNonNullReferenceTypeWithInterfaces() {
+    return true;
+  }
+
+  @Override
+  public InitializedNonNullReferenceFrameTypeWithInterfaces
+      asInitializedNonNullReferenceTypeWithInterfaces() {
+    return this;
+  }
+
+  @Override
+  public DexType getInitializedType(DexItemFactory dexItemFactory) {
+    if (initializedTypeCache == null) {
+      initializedTypeCache = type.toDexType(dexItemFactory);
+    }
+    return initializedTypeCache;
+  }
+
+  @Override
+  public ReferenceTypeElement getInitializedTypeWithInterfaces(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return getInitializedTypeWithInterfaces();
+  }
+
+  public ReferenceTypeElement getInitializedTypeWithInterfaces() {
+    return type;
+  }
+
+  @Override
+  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+    throw new Unreachable(
+        "Unexpected InitializedNonNullReferenceFrameTypeWithInterfaces in writer");
+  }
+
+  @Override
+  public SingleFrameType join(
+      AppView<? extends AppInfoWithClassHierarchy> appView, SingleFrameType frameType) {
+    if (equals(frameType) || frameType.isNullType()) {
+      return this;
+    }
+    if (frameType.isOneWord() || frameType.isPrimitive() || frameType.isUninitialized()) {
+      return FrameType.oneWord();
+    }
+    assert frameType.isInitializedNonNullReferenceType();
+    ReferenceTypeElement joinType =
+        getInitializedTypeWithInterfaces(appView)
+            .join(
+                frameType
+                    .asInitializedNonNullReferenceType()
+                    .getInitializedTypeWithInterfaces(appView),
+                appView);
+    return FrameType.initializedNonNullReference(joinType);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj == null || getClass() != obj.getClass()) {
+      return false;
+    }
+    InitializedNonNullReferenceFrameTypeWithInterfaces initializedType =
+        (InitializedNonNullReferenceFrameTypeWithInterfaces) obj;
+    return type.equals(initializedType.type);
+  }
+
+  @Override
+  public int hashCode() {
+    return type.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return "Initialized(" + type.toString() + ")";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/InitializedNonNullReferenceFrameTypeWithoutInterfaces.java b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedNonNullReferenceFrameTypeWithoutInterfaces.java
new file mode 100644
index 0000000..88beb14
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedNonNullReferenceFrameTypeWithoutInterfaces.java
@@ -0,0 +1,117 @@
+// 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.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.GraphLens;
+import com.android.tools.r8.ir.analysis.type.ReferenceTypeElement;
+import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.Opcodes;
+
+public class InitializedNonNullReferenceFrameTypeWithoutInterfaces
+    extends InitializedNonNullReferenceFrameType {
+
+  private final DexType type;
+
+  InitializedNonNullReferenceFrameTypeWithoutInterfaces(DexType type) {
+    assert type != null;
+    assert type.isReferenceType();
+    assert !type.isNullValueType();
+    this.type = type;
+  }
+
+  @Override
+  public boolean isInitializedNonNullReferenceTypeWithoutInterfaces() {
+    return true;
+  }
+
+  @Override
+  public InitializedNonNullReferenceFrameTypeWithoutInterfaces
+      asInitializedNonNullReferenceTypeWithoutInterfaces() {
+    return this;
+  }
+
+  @Override
+  public final DexType getInitializedType(DexItemFactory dexItemFactory) {
+    return getInitializedType();
+  }
+
+  public DexType getInitializedType() {
+    return type;
+  }
+
+  @Override
+  public ReferenceTypeElement getInitializedTypeWithInterfaces(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return type.toTypeElement(appView).asReferenceType();
+  }
+
+  @Override
+  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+    DexType rewrittenType = graphLens.lookupType(type);
+    assert rewrittenType != DexItemFactory.nullValueType;
+    switch (rewrittenType.toShorty()) {
+      case 'L':
+        return namingLens.lookupInternalName(rewrittenType);
+      case 'I':
+        return Opcodes.INTEGER;
+      case 'F':
+        return Opcodes.FLOAT;
+      case 'J':
+        return Opcodes.LONG;
+      case 'D':
+        return Opcodes.DOUBLE;
+      default:
+        throw new Unreachable("Unexpected value type: " + rewrittenType);
+    }
+  }
+
+  @Override
+  public SingleFrameType join(
+      AppView<? extends AppInfoWithClassHierarchy> appView, SingleFrameType frameType) {
+    if (equals(frameType) || frameType.isNullType()) {
+      return this;
+    }
+    if (frameType.isOneWord() || frameType.isPrimitive() || frameType.isUninitialized()) {
+      return FrameType.oneWord();
+    }
+    assert frameType.isInitializedNonNullReferenceType();
+    ReferenceTypeElement joinType =
+        getInitializedTypeWithInterfaces(appView)
+            .join(
+                frameType
+                    .asInitializedNonNullReferenceType()
+                    .getInitializedTypeWithInterfaces(appView),
+                appView);
+    return FrameType.initializedNonNullReference(joinType);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj == null || getClass() != obj.getClass()) {
+      return false;
+    }
+    InitializedNonNullReferenceFrameTypeWithoutInterfaces initializedType =
+        (InitializedNonNullReferenceFrameTypeWithoutInterfaces) obj;
+    return type == initializedType.type;
+  }
+
+  @Override
+  public int hashCode() {
+    return type.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return "Initialized(" + type.toString() + ")";
+  }
+}
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 27ada3e..4094f4e 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,154 +4,4 @@
 
 package com.android.tools.r8.cf.code.frame;
 
-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;
-
-public class InitializedReferenceFrameType extends BaseFrameType
-    implements InitializedFrameType, SingleFrameType {
-
-  private final DexType type;
-
-  public InitializedReferenceFrameType(DexType type) {
-    assert type != null;
-    assert type.isReferenceType();
-    this.type = type;
-  }
-
-  @Override
-  public boolean isPrecise() {
-    return true;
-  }
-
-  @Override
-  public PreciseFrameType asPrecise() {
-    return this;
-  }
-
-  @Override
-  public boolean isInitializedReferenceType() {
-    return true;
-  }
-
-  @Override
-  public InitializedReferenceFrameType asInitializedReferenceType() {
-    return this;
-  }
-
-  @Override
-  public SingleFrameType join(
-      AppView<? extends AppInfoWithClassHierarchy> appView, SingleFrameType frameType) {
-    if (equals(frameType)) {
-      return this;
-    }
-    if (frameType.isOneWord() || frameType.isPrimitive() || frameType.isUninitialized()) {
-      return FrameType.oneWord();
-    }
-    DexType otherType = frameType.asInitializedReferenceType().getInitializedType();
-    assert type != otherType;
-    assert type.isReferenceType();
-    if (isNullType()) {
-      return otherType.isReferenceType() ? frameType : FrameType.oneWord();
-    }
-    if (frameType.isNullType()) {
-      return this;
-    }
-    assert type.isArrayType() || type.isClassType();
-    assert otherType.isArrayType() || otherType.isClassType();
-    DexType joinType =
-        DexTypeUtils.toDexType(
-            appView, type.toTypeElement(appView).join(otherType.toTypeElement(appView), appView));
-    return FrameType.initializedReference(joinType);
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (this == obj) {
-      return true;
-    }
-    if (obj == null || getClass() != obj.getClass()) {
-      return false;
-    }
-    InitializedReferenceFrameType initializedType = (InitializedReferenceFrameType) obj;
-    return type == initializedType.type;
-  }
-
-  @Override
-  public int hashCode() {
-    return type.hashCode();
-  }
-
-  @Override
-  public String toString() {
-    return "Initialized(" + type.toString() + ")";
-  }
-
-  @Override
-  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
-    DexType rewrittenType = graphLens.lookupType(type);
-    if (rewrittenType == DexItemFactory.nullValueType) {
-      return Opcodes.NULL;
-    }
-    switch (rewrittenType.toShorty()) {
-      case 'L':
-        return namingLens.lookupInternalName(rewrittenType);
-      case 'I':
-        return Opcodes.INTEGER;
-      case 'F':
-        return Opcodes.FLOAT;
-      case 'J':
-        return Opcodes.LONG;
-      case 'D':
-        return Opcodes.DOUBLE;
-      default:
-        throw new Unreachable("Unexpected value type: " + rewrittenType);
-    }
-  }
-
-  @Override
-  public SingleFrameType asSingle() {
-    return this;
-  }
-
-  @Override
-  public boolean isWide() {
-    return false;
-  }
-
-  @Override
-  public boolean isInitialized() {
-    return true;
-  }
-
-  public DexType getInitializedType() {
-    return type;
-  }
-
-  @Override
-  public DexType getInitializedType(DexItemFactory dexItemFactory) {
-    return getInitializedType();
-  }
-
-  @Override
-  public boolean isNullType() {
-    return type.isNullValueType();
-  }
-
-  @Override
-  public boolean isObject() {
-    return type.isReferenceType();
-  }
-
-  @Override
-  public DexType getObjectType(DexType context) {
-    assert isObject() : "Unexpected use of getObjectType() for non-object FrameType";
-    return type;
-  }
-}
+public interface InitializedReferenceFrameType extends InitializedFrameType, SingleFrameType {}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/NullFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/NullFrameType.java
new file mode 100644
index 0000000..a5e26c8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/NullFrameType.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.cf.code.frame;
+
+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.GraphLens;
+import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.Opcodes;
+
+public class NullFrameType extends SingletonFrameType implements InitializedReferenceFrameType {
+
+  static final NullFrameType SINGLETON = new NullFrameType();
+
+  private NullFrameType() {}
+
+  @Override
+  public boolean isInitialized() {
+    return true;
+  }
+
+  @Override
+  public boolean isInitializedReferenceType() {
+    return true;
+  }
+
+  @Override
+  public NullFrameType asInitializedReferenceType() {
+    return this;
+  }
+
+  @Override
+  public boolean isNullType() {
+    return true;
+  }
+
+  @Override
+  public NullFrameType asNullType() {
+    return this;
+  }
+
+  @Override
+  public boolean isObject() {
+    return true;
+  }
+
+  @Override
+  public boolean isPrecise() {
+    return true;
+  }
+
+  @Override
+  public PreciseFrameType asPrecise() {
+    return this;
+  }
+
+  @Override
+  public SingleFrameType asSingle() {
+    return this;
+  }
+
+  @Override
+  public DexType getInitializedType(DexItemFactory dexItemFactory) {
+    return getInitializedType();
+  }
+
+  public DexType getInitializedType() {
+    return DexItemFactory.nullValueType;
+  }
+
+  @Override
+  public DexType getObjectType(DexItemFactory dexItemFactory, DexType context) {
+    return getInitializedType();
+  }
+
+  @Override
+  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+    return Opcodes.NULL;
+  }
+
+  @Override
+  public SingleFrameType join(
+      AppView<? extends AppInfoWithClassHierarchy> appView, SingleFrameType frameType) {
+    if (this == frameType) {
+      return this;
+    }
+    if (frameType.isOneWord() || frameType.isPrimitive() || frameType.isUninitialized()) {
+      return FrameType.oneWord();
+    }
+    assert frameType.isInitializedNonNullReferenceType();
+    return frameType;
+  }
+
+  @Override
+  public String toString() {
+    return "null";
+  }
+}
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 dfa0786..11f9a33 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
@@ -11,20 +11,18 @@
 
   @Override
   default PreciseFrameType map(Function<DexType, DexType> fn) {
-    if (isObject()) {
-      if (isInitialized()) {
-        DexType type = asInitializedReferenceType().getInitializedType();
-        DexType newType = fn.apply(type);
-        if (type != newType) {
-          return FrameType.initializedReference(newType);
-        }
+    assert !isInitializedNonNullReferenceTypeWithInterfaces();
+    if (isInitializedNonNullReferenceTypeWithoutInterfaces()) {
+      DexType type = asInitializedNonNullReferenceTypeWithoutInterfaces().getInitializedType();
+      DexType newType = fn.apply(type);
+      if (type != newType) {
+        return FrameType.initializedNonNullReference(newType);
       }
-      if (isUninitializedNew()) {
-        DexType type = getUninitializedNewType();
-        DexType newType = fn.apply(type);
-        if (type != newType) {
-          return FrameType.uninitializedNew(getUninitializedLabel(), newType);
-        }
+    } else if (isUninitializedNew()) {
+      DexType type = getUninitializedNewType();
+      DexType newType = fn.apply(type);
+      if (type != newType) {
+        return FrameType.uninitializedNew(getUninitializedLabel(), newType);
       }
     }
     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 a35e4b4..a57c5e6 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
@@ -7,6 +7,7 @@
 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.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.naming.NamingLens;
@@ -24,7 +25,7 @@
   }
 
   @Override
-  public DexType getObjectType(DexType context) {
+  public DexType getObjectType(DexItemFactory dexItemFactory, DexType context) {
     return type;
   }
 
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 5b6730d..21036e4 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
@@ -6,6 +6,7 @@
 
 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.GraphLens;
 import com.android.tools.r8.naming.NamingLens;
@@ -18,7 +19,7 @@
   private UninitializedThis() {}
 
   @Override
-  public DexType getObjectType(DexType context) {
+  public DexType getObjectType(DexItemFactory dexItemFactory, DexType context) {
     return context;
   }
 
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 44289af..0d4f9c9 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -9,7 +9,9 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrameVerificationHelper;
+import com.android.tools.r8.cf.code.CfFrameVerifier;
+import com.android.tools.r8.cf.code.CfFrameVerifier.StackMapStatus;
+import com.android.tools.r8.cf.code.CfFrameVerifierEventConsumer;
 import com.android.tools.r8.cf.code.CfIinc;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfLabel;
@@ -17,7 +19,6 @@
 import com.android.tools.r8.cf.code.CfPosition;
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.cf.code.CfTryCatch;
-import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.dex.code.CfOrDexInstruction;
 import com.android.tools.r8.dex.code.DexBase5Format;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
@@ -39,47 +40,29 @@
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
-import com.android.tools.r8.optimize.interfaces.analysis.ConcreteCfFrameState;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralItem;
 import com.android.tools.r8.utils.structural.StructuralMapping;
 import com.google.common.base.Strings;
+import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Collections;
-import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.ListIterator;
-import java.util.Map;
+import java.util.Set;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
 
 public class CfCode extends Code implements CfWritableCode, StructuralItem<CfCode> {
 
-  public enum StackMapStatus {
-    NOT_VERIFIED,
-    NOT_PRESENT,
-    INVALID,
-    VALID;
-
-    public boolean isValid() {
-      return this == VALID || this == NOT_PRESENT;
-    }
-
-    public boolean isInvalidOrNotPresent() {
-      return this == INVALID || this == NOT_PRESENT;
-    }
-  }
-
   public static class LocalVariableInfo {
 
     private final int index;
@@ -149,7 +132,7 @@
   private List<CfInstruction> instructions;
   private final List<CfTryCatch> tryCatchRanges;
   private final List<LocalVariableInfo> localVariables;
-  private StackMapStatus stackMapStatus = StackMapStatus.NOT_VERIFIED;
+  private StackMapStatus stackMapStatus = CfFrameVerifier.StackMapStatus.NOT_VERIFIED;
   private final com.android.tools.r8.position.Position diagnosticPosition;
   private final BytecodeMetadata<CfInstruction> metadata;
 
@@ -261,7 +244,7 @@
   }
 
   public StackMapStatus getStackMapStatus() {
-    assert stackMapStatus != StackMapStatus.NOT_VERIFIED;
+    assert stackMapStatus != CfFrameVerifier.StackMapStatus.NOT_VERIFIED;
     return stackMapStatus;
   }
 
@@ -281,6 +264,15 @@
     return tryCatchRanges;
   }
 
+  public Set<CfLabel> getTryCatchRangeLabels() {
+    Set<CfLabel> tryCatchRangeLabels = Sets.newIdentityHashSet();
+    for (CfTryCatch tryCatchRange : getTryCatchRanges()) {
+      tryCatchRangeLabels.add(tryCatchRange.start);
+      tryCatchRangeLabels.add(tryCatchRange.end);
+    }
+    return tryCatchRangeLabels;
+  }
+
   public CfInstruction getInstruction(int index) {
     return instructions.get(index);
   }
@@ -415,7 +407,8 @@
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
     GraphLens graphLens = appView.graphLens();
-    assert verifyFrames(method, appView).isValid() : "Could not validate stack map frames";
+    assert verifyFrames(method, appView).isValidOrNotPresent()
+        : "Could not validate stack map frames";
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     InitClassLens initClassLens = appView.initClassLens();
     InternalOptions options = appView.options();
@@ -553,7 +546,7 @@
 
   private void verifyFramesOrRemove(ProgramMethod method, AppView<?> appView, GraphLens codeLens) {
     stackMapStatus = verifyFrames(method, appView, codeLens);
-    if (!stackMapStatus.isValid()) {
+    if (!stackMapStatus.isValidOrNotPresent()) {
       ArrayList<CfInstruction> copy = new ArrayList<>(instructions);
       copy.removeIf(CfInstruction::isFrame);
       setInstructions(copy);
@@ -896,184 +889,23 @@
   }
 
   public StackMapStatus verifyFrames(ProgramMethod method, AppView<?> appView, GraphLens codeLens) {
-    GraphLens graphLens = appView.graphLens();
-    DexEncodedMethod definition = method.getDefinition();
-    if (!appView.options().canUseInputStackMaps()
-        || appView.options().testing.disableStackMapVerification) {
-      return StackMapStatus.NOT_PRESENT;
-    }
-    if (definition.hasClassFileVersion()
-        && definition.getClassFileVersion().isLessThan(CfVersion.V1_7)) {
-      return StackMapStatus.NOT_PRESENT;
-    }
+    CfFrameVerifierEventConsumer eventConsumer =
+        new CfFrameVerifierEventConsumer() {
 
-    RewrittenPrototypeDescription protoChanges =
-        graphLens.lookupPrototypeChangesForMethodDefinition(method.getReference(), codeLens);
-
-    DexMethod previousMethodSignature =
-        graphLens.getOriginalMethodSignature(method.getReference(), codeLens);
-    boolean previousMethodSignatureIsInstance =
-        method.getDefinition().isInstance()
-            || protoChanges.getArgumentInfoCollection().isConvertedToStaticMethod();
-
-    // Build a map from labels to frames.
-    Map<CfLabel, CfFrame> stateMap = new IdentityHashMap<>();
-    List<CfLabel> labels = new ArrayList<>();
-    boolean requireStackMapFrame = !tryCatchRanges.isEmpty();
-    for (CfInstruction instruction : instructions) {
-      if (instruction.isFrame()) {
-        CfFrame frame = instruction.asFrame();
-        if (!labels.isEmpty()) {
-          for (CfLabel label : labels) {
-            if (stateMap.containsKey(label)) {
-              return reportStackMapError(
-                  CfCodeStackMapValidatingException.multipleFramesForLabel(method, appView),
-                  appView);
-            }
-            stateMap.put(label, frame);
+          @Override
+          public void acceptError(CfCodeDiagnostics diagnostics) {
+            // Stack maps was required from version V1_6 (50), but the JVM gave a grace-period and
+            // only started enforcing stack maps from 51 in JVM 8. As a consequence, we have
+            // different android libraries that has V1_7 code but has no stack maps. To not fail on
+            // compilations we only report a warning.
+            appView.options().reporter.warning(diagnostics);
           }
-        } else if (instruction != instructions.get(0)) {
-          // From b/168212806, it is possible that the first instruction is a frame.
-          return reportStackMapError(
-              CfCodeStackMapValidatingException.unexpectedStackMapFrame(method, appView), appView);
-        }
-      }
-      // We are trying to map a frame to a label, but we can have positions in between, so skip
-      // those.
-      if (instruction.isPosition()) {
-        continue;
-      } else if (instruction.isLabel()) {
-        labels.add(instruction.asLabel());
-      } else {
-        labels.clear();
-      }
-      if (!requireStackMapFrame) {
-        requireStackMapFrame = instruction.isJump() && !finalAndExitInstruction(instruction);
-      }
-    }
-    // If there are no frames but we have seen a jump instruction, we cannot verify the stack map.
-    if (requireStackMapFrame && stateMap.isEmpty()) {
-      return reportStackMapError(
-          CfCodeStackMapValidatingException.noFramesForMethodWithJumps(method, appView), appView);
-    }
-    CfFrameVerificationHelper helper =
-        new CfFrameVerificationHelper(appView, this, codeLens, method, stateMap, tryCatchRanges);
-    CfCodeDiagnostics diagnostics = helper.checkTryCatchRanges();
-    if (diagnostics != null) {
-      return reportStackMapError(diagnostics, appView);
-    }
-    TraversalContinuation<CfCodeDiagnostics, CfFrameState> initialState =
-        computeInitialState(
-            appView, helper, method, previousMethodSignature, previousMethodSignatureIsInstance);
-    if (initialState.shouldBreak()) {
-      return reportStackMapError(initialState.asBreak().getValue(), appView);
-    }
-    CfFrameState state = initialState.asContinue().getValue();
-    for (int i = 0; i < instructions.size(); i++) {
-      CfInstruction instruction = instructions.get(i);
-      assert !state.isError();
-      // Check the exceptional edge prior to evaluating the instruction. The local state is stable
-      // at this point as store operations are not throwing and the current stack does not
-      // affect the exceptional transfer (the exception edge is always a singleton stack).
-      if (instruction.canThrow()) {
-        assert !instruction.isStore();
-        state = helper.checkExceptionEdges(state);
-      }
-      if (instruction.isLabel()) {
-        helper.seenLabel(instruction.asLabel());
-      }
-      state = instruction.evaluate(state, appView, helper);
-      if (instruction.isJumpWithNormalTarget()) {
-        CfInstruction fallthroughInstruction =
-            (i + 1) < instructions.size() ? instructions.get(i + 1) : null;
-        TraversalContinuation<CfCodeDiagnostics, CfFrameState> traversalContinuation =
-            instruction.traverseNormalTargets(
-                (target, currentState) -> {
-                  if (target != fallthroughInstruction) {
-                    assert target.isLabel();
-                    currentState = helper.checkTarget(currentState, target.asLabel());
-                  }
-                  return TraversalContinuation.doContinue(currentState);
-                },
-                fallthroughInstruction,
-                state);
-        state = traversalContinuation.asContinue().getValue();
-      }
-      TraversalContinuation<CfCodeDiagnostics, CfFrameState> traversalContinuation =
-          helper.computeStateForNextInstruction(instruction, i, state);
-      if (traversalContinuation.isContinue()) {
-        state = traversalContinuation.asContinue().getValue();
-      } else {
-        return reportStackMapError(traversalContinuation.asBreak().getValue(), appView);
-      }
-      if (state.isError()) {
-        return reportStackMapError(
-            CfCodeStackMapValidatingException.invalidStackMapForInstruction(
-                method, i, instruction, state.asError().getMessage(), appView),
-            appView);
-      }
-    }
-    return StackMapStatus.VALID;
-  }
-
-  private StackMapStatus reportStackMapError(CfCodeDiagnostics diagnostics, AppView<?> appView) {
-    // Stack maps was required from version V1_6 (50), but the JVM gave a grace-period and only
-    // started enforcing stack maps from 51 in JVM 8. As a consequence, we have different android
-    // libraries that has V1_7 code but has no stack maps. To not fail on compilations we only
-    // report a warning.
-    appView.options().reporter.warning(diagnostics);
-    return StackMapStatus.INVALID;
-  }
-
-  private boolean finalAndExitInstruction(CfInstruction instruction) {
-    boolean isReturnOrThrow = instruction.isThrow() || instruction.isReturn();
-    if (!isReturnOrThrow) {
-      return false;
-    }
-    for (int i = instructions.size() - 1; i >= 0; i--) {
-      CfInstruction instr = instructions.get(i);
-      if (instr == instruction) {
-        return true;
-      }
-      if (instr.isPosition() || instr.isLabel()) {
-        continue;
-      }
-      return false;
-    }
-    throw new Unreachable("Instruction " + instruction + " should be in instructions");
-  }
-
-  private TraversalContinuation<CfCodeDiagnostics, CfFrameState> computeInitialState(
-      AppView<?> appView,
-      CfFrameVerificationHelper helper,
-      ProgramMethod method,
-      DexMethod previousMethodSignature,
-      boolean previousMethodSignatureIsInstance) {
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    CfFrameState state = new ConcreteCfFrameState();
-    int localIndex = 0;
-    if (previousMethodSignatureIsInstance) {
-      state =
-          state.storeLocal(
-              localIndex,
-              previousMethodSignature.isInstanceInitializer(dexItemFactory)
-                      || previousMethodSignature.mustBeInlinedIntoInstanceInitializer(appView)
-                      || previousMethodSignature.isHorizontallyMergedInstanceInitializer(
-                          dexItemFactory)
-                  ? FrameType.uninitializedThis()
-                  : FrameType.initialized(previousMethodSignature.getHolderType()),
-              helper);
-      localIndex++;
-    }
-    for (DexType parameter : previousMethodSignature.getParameters()) {
-      state = state.storeLocal(localIndex, FrameType.initialized(parameter), helper);
-      localIndex += parameter.getRequiredRegisters();
-    }
-    if (state.isError()) {
-      return TraversalContinuation.doBreak(
-          CfCodeStackMapValidatingException.invalidStackMapForInstruction(
-              method, 0, instructions.get(0), state.asError().getMessage(), appView));
-    }
-    return TraversalContinuation.doContinue(state);
+        };
+    CfFrameVerifier helper =
+        CfFrameVerifier.builder(appView, this, method)
+            .setCodeLens(codeLens)
+            .setEventConsumer(eventConsumer)
+            .build();
+    return helper.run();
   }
 }
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 5c8046f..fbd4b5a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -2127,6 +2127,10 @@
       return appendPrimitiveMethods.contains(method);
     }
 
+    public boolean isAppendSubArrayMethod(DexMethod method) {
+      return appendSubCharArray == method || appendSubCharSequence == method;
+    }
+
     public boolean isAppendStringMethod(DexMethod method) {
       return method == appendString;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 1efc762..36c45bc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -157,8 +157,7 @@
     if (clazz == null) {
       return false;
     }
-    // TODO(b/214496607): Allow uninstantiated reasoning for closed interfaces.
-    if (clazz.isInterface()) {
+    if (clazz.isInterface() && appView.getOpenClosedInterfacesCollection().isMaybeOpen(clazz)) {
       return false;
     }
     return !appView.appInfo().isInstantiatedDirectlyOrIndirectly(clazz);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
index 549bbf2..a2acb83 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
@@ -33,6 +33,7 @@
     this.variants = variants;
   }
 
+  @Override
   public DexType toDexType(DexItemFactory factory) {
     TypeElement baseTypeLattice = getBaseType();
     DexType baseType;
@@ -183,22 +184,23 @@
       // Return null indicating the join is the same as the member to avoid object allocation.
       return null;
     }
-    if (aMember.isArrayType() && bMember.isArrayType()) {
-      TypeElement aMemberMember = aMember.asArrayType().getMemberType();
-      TypeElement bMemberMember = bMember.asArrayType().getMemberType();
-      TypeElement join =
-          joinMember(
-              aMemberMember,
-              bMemberMember,
-              appView,
-              aMember.nullability().join(bMember.nullability()));
-      return join == null ? null : ArrayTypeElement.create(join, nullability);
-    }
-    if (aMember.isClassType() && bMember.isClassType()) {
-      ReferenceTypeElement join = aMember.asClassType().join(bMember.asClassType(), appView);
+    if (aMember.isReferenceType() && bMember.isReferenceType()) {
+      if (aMember.isArrayType() && bMember.isArrayType()) {
+        TypeElement aMemberMember = aMember.asArrayType().getMemberType();
+        TypeElement bMemberMember = bMember.asArrayType().getMemberType();
+        TypeElement join =
+            joinMember(
+                aMemberMember,
+                bMemberMember,
+                appView,
+                aMember.nullability().join(bMember.nullability()));
+        return join == null ? null : ArrayTypeElement.create(join, nullability);
+      }
+      ReferenceTypeElement join =
+          aMember.asReferenceType().join(bMember.asReferenceType(), appView);
       return ArrayTypeElement.create(join, nullability);
-    }
-    if (aMember.isPrimitiveType() || bMember.isPrimitiveType()) {
+    } else {
+      assert aMember.isPrimitiveType() || bMember.isPrimitiveType();
       if (appView.enableWholeProgramOptimizations()) {
         assert appView.hasClassHierarchy();
         DexItemFactory dexItemFactory = appView.dexItemFactory();
@@ -215,6 +217,5 @@
       }
       return objectClassType(appView, nullability);
     }
-    return objectArrayType(appView, nullability);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/BooleanTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/BooleanTypeElement.java
index e00ecf9..8c14b52 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/BooleanTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/BooleanTypeElement.java
@@ -12,6 +12,11 @@
   }
 
   @Override
+  public String getDescriptor() {
+    return "Z";
+  }
+
+  @Override
   public String getTypeName() {
     return "boolean";
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ByteTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ByteTypeElement.java
index 849f508..b4b1179 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ByteTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ByteTypeElement.java
@@ -12,6 +12,11 @@
   }
 
   @Override
+  public String getDescriptor() {
+    return "B";
+  }
+
+  @Override
   public String getTypeName() {
     return "byte";
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/CharTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/CharTypeElement.java
index 5a47cda..e7984bd 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/CharTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/CharTypeElement.java
@@ -12,6 +12,11 @@
   }
 
   @Override
+  public String getDescriptor() {
+    return "C";
+  }
+
+  @Override
   public String getTypeName() {
     return "char";
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
index 3298b8e..6101905 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
@@ -153,6 +153,7 @@
     return getOrCreateVariant(nullability().meet(nullability));
   }
 
+  @Override
   public DexType toDexType(DexItemFactory dexItemFactory) {
     if (type == dexItemFactory.objectType) {
       DexType singleKnownInterface = getInterfaces().getSingleKnownInterface();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DoubleTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DoubleTypeElement.java
index 85a501c..430897a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DoubleTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DoubleTypeElement.java
@@ -12,6 +12,11 @@
   }
 
   @Override
+  public String getDescriptor() {
+    return "D";
+  }
+
+  @Override
   public String getTypeName() {
     return "double";
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/FloatTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/FloatTypeElement.java
index 9efadf8..d4463cc 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/FloatTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/FloatTypeElement.java
@@ -11,6 +11,11 @@
   }
 
   @Override
+  public String getDescriptor() {
+    return "F";
+  }
+
+  @Override
   public String getTypeName() {
     return "float";
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/IntTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/IntTypeElement.java
index 881cb19..3ecd81b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/IntTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/IntTypeElement.java
@@ -11,6 +11,11 @@
   }
 
   @Override
+  public String getDescriptor() {
+    return "I";
+  }
+
+  @Override
   public String getTypeName() {
     return "int";
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/LongTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/LongTypeElement.java
index 7f71468..b90dbb4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/LongTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/LongTypeElement.java
@@ -12,6 +12,11 @@
   }
 
   @Override
+  public String getDescriptor() {
+    return "J";
+  }
+
+  @Override
   public String getTypeName() {
     return "long";
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeElement.java
index 9797274..1e5260b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeElement.java
@@ -12,6 +12,8 @@
 /** A {@link TypeElement} that abstracts primitive types. */
 public abstract class PrimitiveTypeElement extends TypeElement {
 
+  public abstract String getDescriptor();
+
   public abstract String getTypeName();
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java
index 4aa2116..1f06630 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
 
 public abstract class ReferenceTypeElement extends TypeElement {
 
@@ -39,6 +40,11 @@
     }
 
     @Override
+    public DexType toDexType(DexItemFactory dexItemFactory) {
+      return DexItemFactory.nullValueType;
+    }
+
+    @Override
     public String toString() {
       return nullability.toString() + " " + DexItemFactory.nullValueType.toString();
     }
@@ -116,6 +122,8 @@
     return this;
   }
 
+  public abstract DexType toDexType(DexItemFactory dexItemFactory);
+
   @Override
   public boolean equals(Object o) {
     throw new Unreachable("Should be implemented on each sub type");
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ShortTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ShortTypeElement.java
index fd7d65a..2c73f91 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ShortTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ShortTypeElement.java
@@ -12,6 +12,11 @@
   }
 
   @Override
+  public String getDescriptor() {
+    return "S";
+  }
+
+  @Override
   public String getTypeName() {
     return "short";
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/SinglePrimitiveTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/SinglePrimitiveTypeElement.java
index 5d3d16b..8814afc 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/SinglePrimitiveTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/SinglePrimitiveTypeElement.java
@@ -19,6 +19,11 @@
   }
 
   @Override
+  public String getDescriptor() {
+    throw new Unreachable("Unexpected attempt to get descriptor of " + this);
+  }
+
+  @Override
   public String getTypeName() {
     throw new Unreachable("Unexpected attempt to get type name of " + this);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/WidePrimitiveTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/WidePrimitiveTypeElement.java
index f13800d..2180be1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/WidePrimitiveTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/WidePrimitiveTypeElement.java
@@ -19,6 +19,11 @@
   }
 
   @Override
+  public String getDescriptor() {
+    throw new Unreachable("Unexpected attempt to get descriptor of " + this);
+  }
+
+  @Override
   public String getTypeName() {
     throw new Unreachable("Unexpected attempt to get type name of " + this);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/MemberType.java b/src/main/java/com/android/tools/r8/ir/code/MemberType.java
index c85a687..a23e5b1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MemberType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MemberType.java
@@ -20,6 +20,10 @@
   INT_OR_FLOAT,
   LONG_OR_DOUBLE;
 
+  public boolean isObject() {
+    return this == OBJECT;
+  }
+
   public boolean isPrecise() {
     return this != INT_OR_FLOAT && this != LONG_OR_DOUBLE;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index e6228f7..cb3bf73 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -193,7 +193,7 @@
     DexBuilder.removeRedundantDebugPositions(code);
     CfCode code = buildCfCode();
     assert verifyInvokeInterface(code, appView);
-    assert code.verifyFrames(method, appView, appView.graphLens()).isValid();
+    assert code.verifyFrames(method, appView, appView.graphLens()).isValidOrNotPresent();
     return code;
   }
 
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 4f28ee9..bffd1fe 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
@@ -6,6 +6,7 @@
 import static it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMaps.emptyMap;
 
 import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrameVerifier;
 import com.android.tools.r8.cf.code.CfGoto;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfLabel;
@@ -21,7 +22,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
-import com.android.tools.r8.graph.CfCode.StackMapStatus;
 import com.android.tools.r8.graph.CfCodeDiagnostics;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DebugLocalInfo.PrintLevel;
@@ -638,7 +638,12 @@
 
   private DexType convertUninitialized(FrameType type) {
     if (type.isInitializedReferenceType()) {
-      return type.asInitializedReferenceType().getInitializedType();
+      if (type.isNullType()) {
+        return type.asNullType().getInitializedType();
+      } else {
+        assert type.isInitializedNonNullReferenceTypeWithoutInterfaces();
+        return type.asInitializedNonNullReferenceTypeWithoutInterfaces().getInitializedType();
+      }
     }
     if (type.isPrimitive()) {
       if (type.isSinglePrimitive()) {
@@ -688,7 +693,7 @@
   @Override
   public DexType getPhiTypeForBlock(
       int register, int blockOffset, ValueTypeConstraint constraint, RegisterReadType readType) {
-    assert code.getStackMapStatus() != StackMapStatus.NOT_VERIFIED;
+    assert code.getStackMapStatus() != CfFrameVerifier.StackMapStatus.NOT_VERIFIED;
     if (code.getStackMapStatus().isInvalidOrNotPresent()) {
       return null;
     }
@@ -876,7 +881,7 @@
 
   @Override
   public boolean hasValidTypesFromStackMap() {
-    return code.getStackMapStatus() == StackMapStatus.VALID;
+    return code.getStackMapStatus() == CfFrameVerifier.StackMapStatus.VALID;
   }
 
   @Override
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 e504e3d..2a97c16 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
@@ -80,13 +80,13 @@
 import com.android.tools.r8.ir.optimize.membervaluepropagation.MemberValuePropagation;
 import com.android.tools.r8.ir.optimize.membervaluepropagation.R8MemberValuePropagation;
 import com.android.tools.r8.ir.optimize.outliner.Outliner;
+import com.android.tools.r8.ir.optimize.string.StringBuilderAppendOptimizer;
 import com.android.tools.r8.ir.optimize.string.StringBuilderOptimizer;
 import com.android.tools.r8.ir.optimize.string.StringOptimizer;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.IdentifierNameStringMarker;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorIROptimizer;
-import com.android.tools.r8.optimize.interfaces.analysis.OpenClosedInterfacesAnalysis;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepMethodInfo;
@@ -142,7 +142,6 @@
   private final ServiceLoaderRewriter serviceLoaderRewriter;
   private final EnumValueOptimizer enumValueOptimizer;
   private final EnumUnboxer enumUnboxer;
-  private final OpenClosedInterfacesAnalysis openClosedInterfacesAnalysis;
 
   public final AssumeInserter assumeInserter;
   private final DynamicTypeOptimization dynamicTypeOptimization;
@@ -228,7 +227,6 @@
       this.methodOptimizationInfoCollector = null;
       this.enumValueOptimizer = null;
       this.enumUnboxer = EnumUnboxer.empty();
-      this.openClosedInterfacesAnalysis = OpenClosedInterfacesAnalysis.empty();
       this.assumeInserter = null;
       return;
     }
@@ -262,8 +260,6 @@
       this.memberValuePropagation = new R8MemberValuePropagation(appViewWithLiveness);
       this.methodOptimizationInfoCollector =
           new MethodOptimizationInfoCollector(appViewWithLiveness, this);
-      // TODO(b/214496607): Enable open/closed interfaces analysis.
-      this.openClosedInterfacesAnalysis = OpenClosedInterfacesAnalysis.empty();
       if (options.isMinifying()) {
         this.identifierNameStringMarker = new IdentifierNameStringMarker(appViewWithLiveness);
       } else {
@@ -299,7 +295,6 @@
       this.methodOptimizationInfoCollector = null;
       this.enumValueOptimizer = null;
       this.enumUnboxer = EnumUnboxer.empty();
-      this.openClosedInterfacesAnalysis = OpenClosedInterfacesAnalysis.empty();
     }
     this.stringSwitchRemover =
         options.isStringSwitchConversionEnabled()
@@ -669,7 +664,6 @@
     appView.withArgumentPropagator(
         argumentPropagator -> argumentPropagator.initializeCodeScanner(executorService, timing));
     enumUnboxer.prepareForPrimaryOptimizationPass(graphLensForPrimaryOptimizationPass);
-    openClosedInterfacesAnalysis.prepareForPrimaryOptimizationPass();
     outliner.prepareForPrimaryOptimizationPass(graphLensForPrimaryOptimizationPass);
 
     if (fieldAccessAnalysis != null) {
@@ -857,7 +851,6 @@
     if (inliner != null) {
       inliner.onLastWaveDone(postMethodProcessorBuilder, executorService, timing);
     }
-    openClosedInterfacesAnalysis.onPrimaryOptimizationPassComplete();
 
     // Ensure determinism of method-to-reprocess set.
     appView.testing().checkDeterminism(postMethodProcessorBuilder::dump);
@@ -1164,8 +1157,6 @@
     assert code.verifyTypes(appView);
     assert code.isConsistentSSA(appView);
 
-    openClosedInterfacesAnalysis.analyze(context, code);
-
     if (shouldPassThrough(context)) {
       // If the code is pass trough, do not finalize by overwriting the existing code.
       assert appView.enableWholeProgramOptimizations();
@@ -1325,15 +1316,14 @@
     timing.begin("Rewrite move result");
     codeRewriter.rewriteMoveResult(code);
     timing.end();
-    // TODO(b/114002137): for now, string concatenation depends on rewriteMoveResult.
+    // TODO(b/114002137): Also run for CF
     if (options.enableStringConcatenationOptimization
         && !isDebugMode
         && options.isGeneratingDex()) {
       timing.begin("Rewrite string concat");
-      stringBuilderOptimizer.computeTrivialStringConcatenation(code);
+      StringBuilderAppendOptimizer.run(appView, code);
       timing.end();
     }
-
     timing.begin("Split range invokes");
     codeRewriter.splitRangeInvokeConstants(code);
     timing.end();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index ac5addd..fb51c23 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -685,16 +685,11 @@
                     return newMethod;
                   });
       if (replacement != null) {
-        // Since we've copied the code object from an existing method, the code should already be
-        // processed, and thus we don't need to schedule it for processing in D8.
+        // Since we've copied the code object from an existing method from the same class, the
+        // code is already processed, and thus we don't need to schedule it for processing in D8.
         assert !appView.options().isGeneratingClassFiles() || replacement.getCode().isCfCode();
         assert !appView.options().isGeneratingDex() || replacement.getCode().isDexCode();
-        ProgramMethod newMethod = new ProgramMethod(implMethodHolder, replacement);
-        if (appView.options().isDesugaredLibraryCompilation()) {
-          assert appView.options().isGeneratingClassFiles();
-          needsProcessingConsumer.accept(newMethod);
-        }
-        return newMethod;
+        return new ProgramMethod(implMethodHolder, replacement);
       }
       // The method might already have been moved by another invoke-dynamic targeting it.
       // If so, it must be defined on the holder.
@@ -773,16 +768,11 @@
                     return newMethod;
                   });
       if (replacement != null) {
-        // Since we've copied the code object from an existing method, the code should already be
-        // processed, and thus we don't need to schedule it for processing in D8.
+        // Since we've copied the code object from an existing method from the same class, the
+        // code is already processed, and thus we don't need to schedule it for processing in D8.
         assert !appView.options().isGeneratingClassFiles() || replacement.getCode().isCfCode();
         assert !appView.options().isGeneratingDex() || replacement.getCode().isDexCode();
-        ProgramMethod newMethod = new ProgramMethod(implMethodHolder, replacement);
-        if (appView.options().isDesugaredLibraryCompilation()) {
-          assert appView.options().isGeneratingClassFiles();
-          needsProcessingConsumer.accept(newMethod);
-        }
-        return newMethod;
+        return new ProgramMethod(implMethodHolder, replacement);
       }
       // The method might already have been moved by another invoke-dynamic targeting it.
       // If so, it must be defined on the holder.
@@ -846,10 +836,7 @@
                   .disableAndroidApiLevelCheck()
                   .build());
       accessorClass.addDirectMethod(accessorMethod.getDefinition());
-      if (appView.options().isDesugaredLibraryCompilation()
-          || appView.options().isGeneratingDex()) {
-        needsProcessingConsumer.accept(accessorMethod);
-      }
+      needsProcessingConsumer.accept(accessorMethod);
       return accessorMethod;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java
index 0aa439a..83730de 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java
@@ -87,7 +87,9 @@
     CfLabel tryCatchHandler = new CfLabel();
     builder.add(
         tryCatchHandler,
-        CfFrame.builder().push(FrameType.initialized(factory.throwableType)).build(),
+        CfFrame.builder()
+            .push(FrameType.initializedNonNullReference(factory.throwableType))
+            .build(),
         new CfStore(ValueType.OBJECT, 0),
         new CfNew(factory.serviceLoaderConfigurationErrorType),
         new CfStackInstruction(CfStackInstruction.Opcode.Dup),
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 73aeb93..99b113d 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
@@ -135,12 +135,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType(
                               "Ljava/util/concurrent/atomic/AtomicReferenceArray;")),
                       FrameType.intType(),
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.INT, 1),
@@ -167,12 +167,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType(
                               "Ljava/util/concurrent/atomic/AtomicReferenceArray;")),
                       FrameType.intType(),
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.INT, 1),
@@ -212,12 +212,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType(
                               "Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;")),
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.OBJECT, 1),
@@ -244,12 +244,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType(
                               "Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;")),
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.OBJECT, 1),
@@ -289,11 +289,11 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType(
                               "Ljava/util/concurrent/atomic/AtomicReference;")),
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.OBJECT, 1),
@@ -317,11 +317,11 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType(
                               "Ljava/util/concurrent/atomic/AtomicReference;")),
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
@@ -540,8 +540,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
-                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
                       FrameType.intType(),
                       FrameType.intType()
                     })),
@@ -566,8 +566,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4, 5},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
-                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.intType(),
@@ -614,8 +614,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4, 5},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
-                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.intType(),
@@ -628,8 +628,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
-                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
                       FrameType.intType(),
                       FrameType.intType()
                     })),
@@ -744,8 +744,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.throwableType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.throwableType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInvoke(
@@ -792,12 +792,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.throwableType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.throwableType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     }),
                 new ArrayDeque<>(
                     Arrays.asList(
-                        FrameType.initialized(
+                        FrameType.initializedNonNullReference(
                             options.itemFactory.createType("Ljava/lang/Exception;"))))),
             new CfStore(ValueType.OBJECT, 2),
             label6,
@@ -861,11 +861,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.throwableType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.throwableType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     }),
                 new ArrayDeque<>(
-                    Arrays.asList(FrameType.initialized(options.itemFactory.throwableType)))),
+                    Arrays.asList(
+                        FrameType.initializedNonNullReference(options.itemFactory.throwableType)))),
             new CfStore(ValueType.OBJECT, 2),
             label8,
             new CfNew(options.itemFactory.createType("Ljava/lang/AssertionError;")),
@@ -928,12 +929,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.throwableType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.throwableType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     }),
                 new ArrayDeque<>(
                     Arrays.asList(
-                        FrameType.initialized(
+                        FrameType.initializedNonNullReference(
                             options.itemFactory.createType(
                                 "Ljava/lang/reflect/InvocationTargetException;"))))),
             new CfStore(ValueType.OBJECT, 2),
@@ -952,8 +953,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.throwableType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.throwableType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfGoto(label20),
             label12,
@@ -961,11 +962,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.throwableType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.throwableType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     }),
                 new ArrayDeque<>(
-                    Arrays.asList(FrameType.initialized(options.itemFactory.throwableType)))),
+                    Arrays.asList(
+                        FrameType.initializedNonNullReference(options.itemFactory.throwableType)))),
             new CfStore(ValueType.OBJECT, 2),
             label13,
             new CfLoad(ValueType.OBJECT, 0),
@@ -1017,13 +1019,13 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.throwableType),
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.throwableType)
+                      FrameType.initializedNonNullReference(options.itemFactory.throwableType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.throwableType)
                     }),
                 new ArrayDeque<>(
                     Arrays.asList(
-                        FrameType.initialized(
+                        FrameType.initializedNonNullReference(
                             options.itemFactory.createType("Ljava/lang/Exception;"))))),
             new CfStore(ValueType.OBJECT, 3),
             label18,
@@ -1031,9 +1033,9 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.throwableType),
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.throwableType)
+                      FrameType.initializedNonNullReference(options.itemFactory.throwableType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.throwableType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfThrow(),
@@ -1042,9 +1044,9 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.throwableType),
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.throwableType)
+                      FrameType.initializedNonNullReference(options.itemFactory.throwableType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.throwableType)
                     })),
             new CfLoad(ValueType.OBJECT, 2),
             new CfThrow(),
@@ -1053,8 +1055,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.throwableType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.throwableType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfReturnVoid(),
             label21),
@@ -1148,10 +1150,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("[Ljava/lang/Object;")),
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/ArrayList;")),
-                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("[Ljava/lang/Object;")),
                       FrameType.intType(),
                       FrameType.intType()
                     })),
@@ -1190,8 +1194,10 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/ArrayList;"))
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("[Ljava/lang/Object;")),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/ArrayList;"))
                     })),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInvoke(
@@ -1304,10 +1310,11 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("[Ljava/util/Map$Entry;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/HashMap;")),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/HashMap;")),
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("[Ljava/util/Map$Entry;")),
                       FrameType.intType(),
                       FrameType.intType()
@@ -1421,10 +1428,11 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("[Ljava/util/Map$Entry;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/HashMap;")),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/HashMap;")),
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("[Ljava/util/Map$Entry;")),
                       FrameType.intType(),
                       FrameType.intType()
@@ -1436,9 +1444,10 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("[Ljava/util/Map$Entry;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/HashMap;"))
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/HashMap;"))
                     })),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInvoke(
@@ -1497,9 +1506,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/HashSet;")),
-                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("[Ljava/lang/Object;")),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/HashSet;")),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("[Ljava/lang/Object;")),
                       FrameType.intType(),
                       FrameType.intType()
                     })),
@@ -1581,9 +1593,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/HashSet;")),
-                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("[Ljava/lang/Object;")),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/HashSet;")),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("[Ljava/lang/Object;")),
                       FrameType.intType(),
                       FrameType.intType()
                     })),
@@ -1594,8 +1609,10 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/HashSet;"))
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("[Ljava/lang/Object;")),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/HashSet;"))
                     })),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInvoke(
@@ -1662,11 +1679,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/Collection;")),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/ArrayList;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/Iterator;"))
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/Iterator;"))
                     })),
             new CfLoad(ValueType.OBJECT, 2),
             new CfInvoke(
@@ -1713,9 +1731,10 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/Collection;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/ArrayList;"))
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/ArrayList;"))
                     })),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInvoke(
@@ -1793,9 +1812,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/Map;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/HashMap;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/Iterator;"))
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/Map;")),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/HashMap;")),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/Iterator;"))
                     })),
             new CfLoad(ValueType.OBJECT, 2),
             new CfInvoke(
@@ -1871,8 +1893,10 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/Map;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/HashMap;"))
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/Map;")),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/HashMap;"))
                     })),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInvoke(
@@ -1939,10 +1963,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/Collection;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/HashSet;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/Iterator;"))
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/HashSet;")),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/Iterator;"))
                     })),
             new CfLoad(ValueType.OBJECT, 2),
             new CfInvoke(
@@ -1989,9 +2015,10 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/Collection;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/HashSet;"))
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/HashSet;"))
                     })),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInvoke(
@@ -2455,7 +2482,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.intType()
@@ -2625,7 +2652,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType), FrameType.intType()
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
+                      FrameType.intType()
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.INT, 1),
@@ -2726,7 +2754,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
                       FrameType.intType(),
                       FrameType.longType(),
                       FrameType.longHighType()
@@ -3194,7 +3222,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.intType()
@@ -3318,7 +3346,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.intType(),
@@ -3335,7 +3363,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.intType(),
@@ -3375,7 +3403,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.intType(),
@@ -3420,7 +3448,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4, 5, 6},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.intType(),
@@ -3434,7 +3462,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4, 5, 6},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.intType(),
@@ -3455,7 +3483,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.intType(),
@@ -3522,7 +3550,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.intType(),
@@ -3569,7 +3597,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.intType(),
@@ -3615,7 +3643,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.intType(),
@@ -3644,7 +3672,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.intType(),
@@ -4033,7 +4061,7 @@
                       FrameType.longType(),
                       FrameType.longHighType(),
                       FrameType.intType(),
-                      FrameType.initialized(options.itemFactory.charArrayType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charArrayType),
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.intType()
@@ -4077,7 +4105,7 @@
                       FrameType.longType(),
                       FrameType.longHighType(),
                       FrameType.intType(),
-                      FrameType.initialized(options.itemFactory.charArrayType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charArrayType),
                       FrameType.intType()
                     })),
             new CfLoad(ValueType.INT, 2),
@@ -4104,7 +4132,7 @@
                       FrameType.longType(),
                       FrameType.longHighType(),
                       FrameType.intType(),
-                      FrameType.initialized(options.itemFactory.charArrayType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charArrayType),
                       FrameType.intType()
                     })),
             new CfLoad(ValueType.LONG, 0),
@@ -4129,7 +4157,7 @@
                       FrameType.longType(),
                       FrameType.longHighType(),
                       FrameType.intType(),
-                      FrameType.initialized(options.itemFactory.charArrayType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charArrayType),
                       FrameType.intType(),
                       FrameType.longType(),
                       FrameType.longHighType()
@@ -4170,7 +4198,7 @@
                       FrameType.longType(),
                       FrameType.longHighType(),
                       FrameType.intType(),
-                      FrameType.initialized(options.itemFactory.charArrayType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charArrayType),
                       FrameType.intType(),
                       FrameType.longType(),
                       FrameType.longHighType(),
@@ -4217,7 +4245,7 @@
                       FrameType.longType(),
                       FrameType.longHighType(),
                       FrameType.intType(),
-                      FrameType.initialized(options.itemFactory.charArrayType),
+                      FrameType.initializedNonNullReference(options.itemFactory.charArrayType),
                       FrameType.intType()
                     })),
             new CfNew(options.itemFactory.stringType),
@@ -6154,9 +6182,9 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/Comparator;"))
                     })),
             new CfLoad(ValueType.OBJECT, 2),
@@ -6177,9 +6205,9 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/Comparator;"))
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
@@ -6246,8 +6274,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfIf(If.Type.NE, ValueType.OBJECT, label2),
@@ -6258,8 +6286,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInstanceOf(options.itemFactory.booleanArrayType),
@@ -6290,8 +6318,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfConstNumber(0, ValueType.INT),
             label5,
@@ -6299,8 +6327,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
             new CfReturn(ValueType.INT),
@@ -6309,8 +6337,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInstanceOf(options.itemFactory.byteArrayType),
@@ -6341,8 +6369,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfConstNumber(0, ValueType.INT),
             label9,
@@ -6350,8 +6378,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
             new CfReturn(ValueType.INT),
@@ -6360,8 +6388,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInstanceOf(options.itemFactory.charArrayType),
@@ -6392,8 +6420,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfConstNumber(0, ValueType.INT),
             label13,
@@ -6401,8 +6429,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
             new CfReturn(ValueType.INT),
@@ -6411,8 +6439,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInstanceOf(options.itemFactory.doubleArrayType),
@@ -6443,8 +6471,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfConstNumber(0, ValueType.INT),
             label17,
@@ -6452,8 +6480,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
             new CfReturn(ValueType.INT),
@@ -6462,8 +6490,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInstanceOf(options.itemFactory.floatArrayType),
@@ -6494,8 +6522,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfConstNumber(0, ValueType.INT),
             label21,
@@ -6503,8 +6531,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
             new CfReturn(ValueType.INT),
@@ -6513,8 +6541,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInstanceOf(options.itemFactory.intArrayType),
@@ -6545,8 +6573,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfConstNumber(0, ValueType.INT),
             label25,
@@ -6554,8 +6582,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
             new CfReturn(ValueType.INT),
@@ -6564,8 +6592,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInstanceOf(options.itemFactory.longArrayType),
@@ -6596,8 +6624,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfConstNumber(0, ValueType.INT),
             label29,
@@ -6605,8 +6633,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
             new CfReturn(ValueType.INT),
@@ -6615,8 +6643,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInstanceOf(options.itemFactory.shortArrayType),
@@ -6647,8 +6675,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfConstNumber(0, ValueType.INT),
             label33,
@@ -6656,8 +6684,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
             new CfReturn(ValueType.INT),
@@ -6666,8 +6694,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInstanceOf(options.itemFactory.createType("[Ljava/lang/Object;")),
@@ -6698,8 +6726,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfConstNumber(0, ValueType.INT),
             label37,
@@ -6707,8 +6735,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
             new CfReturn(ValueType.INT),
@@ -6717,8 +6745,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.OBJECT, 1),
@@ -6769,8 +6797,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfConstNumber(1, ValueType.INT),
             new CfGoto(label3),
@@ -6779,8 +6807,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfConstNumber(0, ValueType.INT),
             label3,
@@ -6788,8 +6816,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
             new CfReturn(ValueType.INT),
@@ -6817,7 +6845,9 @@
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
-                    new FrameType[] {FrameType.initialized(options.itemFactory.objectType)})),
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
+                    })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
                 182,
@@ -6830,7 +6860,9 @@
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
-                    new FrameType[] {FrameType.initialized(options.itemFactory.objectType)}),
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
+                    }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
             new CfReturn(ValueType.INT),
             label3),
@@ -6857,13 +6889,17 @@
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
-                    new FrameType[] {FrameType.initialized(options.itemFactory.objectType)})),
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
+                    })),
             new CfConstNumber(0, ValueType.INT),
             label2,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
-                    new FrameType[] {FrameType.initialized(options.itemFactory.objectType)}),
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
+                    }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
             new CfReturn(ValueType.INT),
             label3),
@@ -6890,13 +6926,17 @@
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
-                    new FrameType[] {FrameType.initialized(options.itemFactory.objectType)})),
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
+                    })),
             new CfConstNumber(0, ValueType.INT),
             label2,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
-                    new FrameType[] {FrameType.initialized(options.itemFactory.objectType)}),
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
+                    }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
             new CfReturn(ValueType.INT),
             label3),
@@ -6924,8 +6964,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfLoad(ValueType.OBJECT, 1),
             new CfConstString(options.itemFactory.createString("defaultObj")),
@@ -6966,8 +7006,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/function/Supplier;"))
                     })),
             new CfLoad(ValueType.OBJECT, 1),
@@ -7042,8 +7082,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.stringType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfReturn(ValueType.OBJECT),
@@ -7087,8 +7127,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/function/Supplier;"))
                     })),
             new CfConstNull(),
@@ -7097,12 +7137,13 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/function/Supplier;"))
                     }),
                 new ArrayDeque<>(
-                    Arrays.asList(FrameType.initialized(options.itemFactory.stringType)))),
+                    Arrays.asList(
+                        FrameType.initializedNonNullReference(options.itemFactory.stringType)))),
             new CfStore(ValueType.OBJECT, 2),
             label4,
             new CfNew(options.itemFactory.createType("Ljava/lang/NullPointerException;")),
@@ -7122,8 +7163,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/function/Supplier;"))
                     })),
             new CfLoad(ValueType.OBJECT, 0),
@@ -7180,8 +7221,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.stringType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
@@ -7196,11 +7237,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.stringType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType)
                     }),
                 new ArrayDeque<>(
-                    Arrays.asList(FrameType.initialized(options.itemFactory.stringType)))),
+                    Arrays.asList(
+                        FrameType.initializedNonNullReference(options.itemFactory.stringType)))),
             new CfReturn(ValueType.OBJECT),
             label3),
         ImmutableList.of(),
@@ -7252,10 +7294,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/Optional;")),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/Optional;")),
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/function/Consumer;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/lang/Runnable;"))
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/lang/Runnable;"))
                     })),
             new CfLoad(ValueType.OBJECT, 2),
             new CfInvoke(
@@ -7270,10 +7314,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/Optional;")),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/Optional;")),
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/function/Consumer;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/lang/Runnable;"))
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/lang/Runnable;"))
                     })),
             new CfReturnVoid(),
             label4),
@@ -7327,11 +7373,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/OptionalDouble;")),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/function/DoubleConsumer;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/lang/Runnable;"))
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/lang/Runnable;"))
                     })),
             new CfLoad(ValueType.OBJECT, 2),
             new CfInvoke(
@@ -7346,11 +7393,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/OptionalDouble;")),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/function/DoubleConsumer;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/lang/Runnable;"))
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/lang/Runnable;"))
                     })),
             new CfReturnVoid(),
             label4),
@@ -7404,11 +7452,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/OptionalInt;")),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/function/IntConsumer;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/lang/Runnable;"))
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/lang/Runnable;"))
                     })),
             new CfLoad(ValueType.OBJECT, 2),
             new CfInvoke(
@@ -7423,11 +7472,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/OptionalInt;")),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/function/IntConsumer;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/lang/Runnable;"))
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/lang/Runnable;"))
                     })),
             new CfReturnVoid(),
             label4),
@@ -7481,11 +7531,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/OptionalLong;")),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/function/LongConsumer;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/lang/Runnable;"))
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/lang/Runnable;"))
                     })),
             new CfLoad(ValueType.OBJECT, 2),
             new CfInvoke(
@@ -7500,11 +7551,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/OptionalLong;")),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/function/LongConsumer;")),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/lang/Runnable;"))
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/lang/Runnable;"))
                     })),
             new CfReturnVoid(),
             label4),
@@ -7539,7 +7591,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/Optional;"))
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/Optional;"))
                     })),
             new CfConstNumber(0, ValueType.INT),
             label2,
@@ -7547,7 +7600,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/Optional;"))
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/Optional;"))
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
             new CfReturn(ValueType.INT),
@@ -7583,7 +7637,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/OptionalDouble;"))
                     })),
             new CfConstNumber(0, ValueType.INT),
@@ -7592,7 +7646,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/OptionalDouble;"))
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
@@ -7629,7 +7683,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/OptionalInt;"))
                     })),
             new CfConstNumber(0, ValueType.INT),
@@ -7638,7 +7692,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/OptionalInt;"))
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
@@ -7675,7 +7729,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/OptionalLong;"))
                     })),
             new CfConstNumber(0, ValueType.INT),
@@ -7684,7 +7738,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/OptionalLong;"))
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
@@ -7735,8 +7789,9 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/Optional;")),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/Optional;")),
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/function/Supplier;"))
                     })),
             new CfLoad(ValueType.OBJECT, 1),
@@ -7810,7 +7865,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/Optional;"))
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/Optional;"))
                     })),
             new CfInvoke(
                 184,
@@ -7870,7 +7926,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/OptionalDouble;"))
                     })),
             new CfInvoke(
@@ -7931,7 +7987,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/OptionalInt;"))
                     })),
             new CfInvoke(
@@ -7992,7 +8048,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("Ljava/util/OptionalLong;"))
                     })),
             new CfInvoke(
@@ -8136,7 +8192,9 @@
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
-                    new FrameType[] {FrameType.initialized(options.itemFactory.objectType)})),
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
+                    })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
                 184,
@@ -8151,10 +8209,12 @@
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
-                    new FrameType[] {FrameType.initialized(options.itemFactory.objectType)}),
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
+                    }),
                 new ArrayDeque<>(
                     Arrays.asList(
-                        FrameType.initialized(
+                        FrameType.initializedNonNullReference(
                             options.itemFactory.createType("Ljava/util/stream/Stream;"))))),
             new CfReturn(ValueType.OBJECT),
             label3),
@@ -8196,7 +8256,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
                       FrameType.intType(),
                       FrameType.intType()
                     })),
@@ -8234,7 +8294,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.intType()
@@ -8257,7 +8317,9 @@
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
-                    new FrameType[] {FrameType.initialized(options.itemFactory.stringType)})),
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType)
+                    })),
             new CfConstNumber(1, ValueType.INT),
             new CfReturn(ValueType.INT),
             label9),
@@ -8302,8 +8364,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("[Ljava/lang/CharSequence;"))
                     })),
             new CfNew(options.itemFactory.stringBuilderType),
@@ -8343,10 +8405,10 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("[Ljava/lang/CharSequence;")),
-                      FrameType.initialized(options.itemFactory.stringBuilderType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringBuilderType),
                       FrameType.intType()
                     })),
             new CfLoad(ValueType.INT, 3),
@@ -8389,10 +8451,10 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
-                      FrameType.initialized(
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(
                           options.itemFactory.createType("[Ljava/lang/CharSequence;")),
-                      FrameType.initialized(options.itemFactory.stringBuilderType)
+                      FrameType.initializedNonNullReference(options.itemFactory.stringBuilderType)
                     })),
             new CfLoad(ValueType.OBJECT, 2),
             new CfInvoke(
@@ -8444,8 +8506,9 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/lang/Iterable;"))
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/lang/Iterable;"))
                     })),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -8504,10 +8567,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/lang/Iterable;")),
-                      FrameType.initialized(options.itemFactory.stringBuilderType),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/Iterator;"))
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/lang/Iterable;")),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringBuilderType),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/Iterator;"))
                     })),
             new CfLoad(ValueType.OBJECT, 3),
             new CfInvoke(
@@ -8558,10 +8623,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.charSequenceType),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/lang/Iterable;")),
-                      FrameType.initialized(options.itemFactory.stringBuilderType),
-                      FrameType.initialized(options.itemFactory.createType("Ljava/util/Iterator;"))
+                      FrameType.initializedNonNullReference(options.itemFactory.charSequenceType),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/lang/Iterable;")),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringBuilderType),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Ljava/util/Iterator;"))
                     })),
             new CfLoad(ValueType.OBJECT, 2),
             new CfInvoke(
@@ -8654,7 +8721,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType), FrameType.intType()
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
+                      FrameType.intType()
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
@@ -8675,7 +8743,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
                       FrameType.intType(),
                       FrameType.intType()
                     })),
@@ -8686,7 +8754,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
                       FrameType.intType(),
                       FrameType.intType()
                     })),
@@ -8701,7 +8769,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
                       FrameType.intType(),
                       FrameType.intType()
                     })),
@@ -8805,7 +8873,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
                       FrameType.intType(),
                       FrameType.intType()
                     })),
@@ -8831,10 +8899,10 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
                       FrameType.intType(),
                       FrameType.intType(),
-                      FrameType.initialized(options.itemFactory.stringBuilderType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringBuilderType),
                       FrameType.intType()
                     })),
             new CfLoad(ValueType.INT, 4),
@@ -8860,10 +8928,10 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
                       FrameType.intType(),
                       FrameType.intType(),
-                      FrameType.initialized(options.itemFactory.stringBuilderType)
+                      FrameType.initializedNonNullReference(options.itemFactory.stringBuilderType)
                     })),
             new CfLoad(ValueType.OBJECT, 3),
             new CfInvoke(
@@ -8919,7 +8987,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
                       FrameType.intType(),
                       FrameType.intType()
                     })),
@@ -8956,7 +9024,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.intType()
@@ -8980,7 +9048,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
                       FrameType.intType(),
                       FrameType.intType()
                     })),
@@ -9019,7 +9087,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.intType()
@@ -9043,7 +9111,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
                       FrameType.intType(),
                       FrameType.intType()
                     })),
@@ -9100,7 +9168,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
                       FrameType.intType(),
                       FrameType.intType()
                     })),
@@ -9137,7 +9205,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
                       FrameType.intType(),
                       FrameType.intType(),
                       FrameType.intType()
@@ -9161,7 +9229,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
                       FrameType.intType(),
                       FrameType.intType()
                     })),
@@ -9214,7 +9282,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType), FrameType.intType()
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
+                      FrameType.intType()
                     })),
             new CfLoad(ValueType.INT, 1),
             new CfIf(If.Type.LE, ValueType.INT, label7),
@@ -9250,7 +9319,7 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
                       FrameType.intType(),
                       FrameType.intType()
                     })),
@@ -9273,7 +9342,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType), FrameType.intType()
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
+                      FrameType.intType()
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfConstNumber(0, ValueType.INT),
@@ -9353,12 +9423,12 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.throwableType),
-                      FrameType.initialized(options.itemFactory.throwableType)
+                      FrameType.initializedNonNullReference(options.itemFactory.throwableType),
+                      FrameType.initializedNonNullReference(options.itemFactory.throwableType)
                     }),
                 new ArrayDeque<>(
                     Arrays.asList(
-                        FrameType.initialized(
+                        FrameType.initializedNonNullReference(
                             options.itemFactory.createType("Ljava/lang/Exception;"))))),
             new CfStore(ValueType.OBJECT, 2),
             label4,
@@ -9366,8 +9436,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.throwableType),
-                      FrameType.initialized(options.itemFactory.throwableType)
+                      FrameType.initializedNonNullReference(options.itemFactory.throwableType),
+                      FrameType.initializedNonNullReference(options.itemFactory.throwableType)
                     })),
             new CfReturnVoid(),
             label5),
@@ -9430,10 +9500,12 @@
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
-                    new FrameType[] {FrameType.initialized(options.itemFactory.throwableType)}),
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(options.itemFactory.throwableType)
+                    }),
                 new ArrayDeque<>(
                     Arrays.asList(
-                        FrameType.initialized(
+                        FrameType.initializedNonNullReference(
                             options.itemFactory.createType("Ljava/lang/Exception;"))))),
             new CfStore(ValueType.OBJECT, 1),
             label4,
@@ -9467,12 +9539,13 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4, 5},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.createType("Lsun/misc/Unsafe;")),
-                      FrameType.initialized(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Lsun/misc/Unsafe;")),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
                       FrameType.longType(),
                       FrameType.longHighType(),
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.OBJECT, 1),
@@ -9500,12 +9573,13 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4, 5},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.createType("Lsun/misc/Unsafe;")),
-                      FrameType.initialized(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("Lsun/misc/Unsafe;")),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
                       FrameType.longType(),
                       FrameType.longHighType(),
-                      FrameType.initialized(options.itemFactory.objectType),
-                      FrameType.initialized(options.itemFactory.objectType)
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType),
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
                     })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.OBJECT, 1),
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 9788ee2..a4b79c0 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
@@ -306,7 +306,7 @@
     instructions.add(initializedTrueSecond);
     instructions.add(
         CfFrame.builder()
-            .appendLocal(FrameType.initialized(builder.getFactory().objectType))
+            .appendLocal(FrameType.initializedNonNullReference(builder.getFactory().objectType))
             .build());
     instructions.add(new CfLoad(ValueType.OBJECT, 0));
     instructions.add(new CfMonitor(Monitor.Type.EXIT));
@@ -316,8 +316,8 @@
     instructions.add(tryCatchTarget);
     instructions.add(
         CfFrame.builder()
-            .appendLocal(FrameType.initialized(builder.getFactory().objectType))
-            .push(FrameType.initialized(builder.getFactory().throwableType))
+            .appendLocal(FrameType.initializedNonNullReference(builder.getFactory().objectType))
+            .push(FrameType.initializedNonNullReference(builder.getFactory().throwableType))
             .build());
     instructions.add(new CfStore(ValueType.OBJECT, 1));
     instructions.add(new CfLoad(ValueType.OBJECT, 0));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
index 4da9973..37451f0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
@@ -34,6 +35,7 @@
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.ir.desugar.ProgramAdditions;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -146,6 +148,20 @@
                 if (needsDesugaring(invokedMethod, method)) {
                   prepareDesugarMethodInstruction(invokedMethod, method, programAdditions);
                 }
+              } else if (instruction.isInvokeDynamic()) {
+                // Starting from Java 17, lambda can use nest based access. We need to generate
+                // bridges for the targeted lambda method.
+                CfInvokeDynamic cfInvokeDynamic = instruction.asInvokeDynamic();
+                LambdaDescriptor lambdaDescriptor =
+                    LambdaDescriptor.tryInfer(
+                        cfInvokeDynamic.getCallSite(), appView.appInfoForDesugaring(), method);
+                if (lambdaDescriptor != null) {
+                  DexMember<?, ?> member = lambdaDescriptor.implHandle.member;
+                  if (needsDesugaring(member, method)) {
+                    assert member.isDexMethod();
+                    prepareDesugarMethodInstruction(member.asDexMethod(), method, programAdditions);
+                  }
+                }
               }
             });
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java
index e46b8d6..d3955b6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java
@@ -122,9 +122,10 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")),
-                      FrameType.initialized(options.itemFactory.classType),
-                      FrameType.initialized(options.itemFactory.stringType)
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("[Ljava/lang/Object;")),
+                      FrameType.initializedNonNullReference(options.itemFactory.classType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType)
                     })),
             new CfLoad(ValueType.OBJECT, 2),
             new CfConstString(options.itemFactory.createString(";")),
@@ -142,13 +143,14 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")),
-                      FrameType.initialized(options.itemFactory.classType),
-                      FrameType.initialized(options.itemFactory.stringType)
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("[Ljava/lang/Object;")),
+                      FrameType.initializedNonNullReference(options.itemFactory.classType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType)
                     }),
                 new ArrayDeque<>(
                     Arrays.asList(
-                        FrameType.initialized(
+                        FrameType.initializedNonNullReference(
                             options.itemFactory.createType("[Ljava/lang/String;"))))),
             new CfStore(ValueType.OBJECT, 3),
             label3,
@@ -198,11 +200,13 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4, 5},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")),
-                      FrameType.initialized(options.itemFactory.classType),
-                      FrameType.initialized(options.itemFactory.stringType),
-                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/String;")),
-                      FrameType.initialized(options.itemFactory.stringBuilderType),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("[Ljava/lang/Object;")),
+                      FrameType.initializedNonNullReference(options.itemFactory.classType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("[Ljava/lang/String;")),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringBuilderType),
                       FrameType.intType()
                     })),
             new CfLoad(ValueType.INT, 5),
@@ -267,11 +271,13 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4, 5},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")),
-                      FrameType.initialized(options.itemFactory.classType),
-                      FrameType.initialized(options.itemFactory.stringType),
-                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/String;")),
-                      FrameType.initialized(options.itemFactory.stringBuilderType),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("[Ljava/lang/Object;")),
+                      FrameType.initializedNonNullReference(options.itemFactory.classType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("[Ljava/lang/String;")),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringBuilderType),
                       FrameType.intType()
                     })),
             new CfIinc(5, 1),
@@ -281,11 +287,13 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")),
-                      FrameType.initialized(options.itemFactory.classType),
-                      FrameType.initialized(options.itemFactory.stringType),
-                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/String;")),
-                      FrameType.initialized(options.itemFactory.stringBuilderType)
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("[Ljava/lang/Object;")),
+                      FrameType.initializedNonNullReference(options.itemFactory.classType),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType),
+                      FrameType.initializedNonNullReference(
+                          options.itemFactory.createType("[Ljava/lang/String;")),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringBuilderType)
                     })),
             new CfLoad(ValueType.OBJECT, 4),
             new CfConstString(options.itemFactory.createString("]")),
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index afeb060..1cf1e36 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -921,20 +921,18 @@
           if (options.testing.enableDeadSwitchCaseElimination) {
             SwitchCaseEliminator eliminator =
                 removeUnnecessarySwitchCases(code, theSwitch, iterator, switchCaseAnalyzer);
-            if (eliminator != null) {
-              if (eliminator.mayHaveIntroducedUnreachableBlocks()) {
-                needToRemoveUnreachableBlocks = true;
-              }
-
-              iterator.previous();
-              instruction = iterator.next();
-              if (instruction.isGoto()) {
-                continue;
-              }
-
-              assert instruction.isSwitch();
-              theSwitch = instruction.asSwitch();
+            if (eliminator.mayHaveIntroducedUnreachableBlocks()) {
+              needToRemoveUnreachableBlocks = true;
             }
+
+            iterator.previous();
+            instruction = iterator.next();
+            if (instruction.isGoto()) {
+              continue;
+            }
+
+            assert instruction.isSwitch();
+            theSwitch = instruction.asSwitch();
           }
           if (theSwitch.isIntSwitch()) {
             rewriteIntSwitch(code, blocksIterator, block, iterator, theSwitch.asIntSwitch());
@@ -1111,46 +1109,38 @@
       InstructionListIterator iterator,
       SwitchCaseAnalyzer switchCaseAnalyzer) {
     BasicBlock defaultTarget = theSwitch.fallthroughBlock();
-    SwitchCaseEliminator eliminator = null;
+    SwitchCaseEliminator eliminator = new SwitchCaseEliminator(theSwitch, iterator);
     BasicBlockBehavioralSubsumption behavioralSubsumption =
         new BasicBlockBehavioralSubsumption(appView, code);
 
     // Compute the set of switch cases that can be removed.
+    boolean hasSwitchCaseToDefaultRewrite = false;
     AbstractValue switchAbstractValue = theSwitch.value().getAbstractValue(appView, code.context());
     for (int i = 0; i < theSwitch.numberOfKeys(); i++) {
       BasicBlock targetBlock = theSwitch.targetBlock(i);
 
       if (switchCaseAnalyzer.switchCaseIsAlwaysHit(theSwitch, i)) {
-        if (eliminator == null) {
-          eliminator = new SwitchCaseEliminator(theSwitch, iterator);
-        }
         eliminator.markSwitchCaseAsAlwaysHit(i);
         break;
       }
 
       // This switch case can be removed if the behavior of the target block is equivalent to the
       // behavior of the default block, or if the switch case is unreachable.
-      if (switchCaseAnalyzer.switchCaseIsUnreachable(theSwitch, switchAbstractValue, i)
-          || behavioralSubsumption.isSubsumedBy(targetBlock, defaultTarget)) {
-        if (eliminator == null) {
-          eliminator = new SwitchCaseEliminator(theSwitch, iterator);
-        }
+      if (switchCaseAnalyzer.switchCaseIsUnreachable(theSwitch, switchAbstractValue, i)) {
         eliminator.markSwitchCaseForRemoval(i);
+      } else if (behavioralSubsumption.isSubsumedBy(targetBlock, defaultTarget)) {
+        eliminator.markSwitchCaseForRemoval(i);
+        hasSwitchCaseToDefaultRewrite = true;
       }
     }
 
-    if (eliminator == null || eliminator.isFallthroughLive()) {
-      if (switchCaseAnalyzer.switchFallthroughIsNeverHit(theSwitch, switchAbstractValue)) {
-        if (eliminator == null) {
-          eliminator = new SwitchCaseEliminator(theSwitch, iterator);
-        }
-        eliminator.markSwitchFallthroughAsNeverHit();
-      }
+    if (eliminator.isFallthroughLive()
+        && !hasSwitchCaseToDefaultRewrite
+        && switchCaseAnalyzer.switchFallthroughIsNeverHit(theSwitch, switchAbstractValue)) {
+      eliminator.markSwitchFallthroughAsNeverHit();
     }
 
-    if (eliminator != null) {
-      eliminator.optimize();
-    }
+    eliminator.optimize();
     return eliminator;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
index 527fb13..3a20e47 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
@@ -202,7 +202,7 @@
                     new int[] {0, 1, 2},
                     new FrameType[] {
                       FrameType.intType(),
-                      FrameType.initialized(options.itemFactory.intArrayType),
+                      FrameType.initializedNonNullReference(options.itemFactory.intArrayType),
                       FrameType.intType()
                     })),
             new CfLoad(ValueType.INT, 2),
@@ -224,7 +224,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.intType(), FrameType.initialized(options.itemFactory.intArrayType)
+                      FrameType.intType(),
+                      FrameType.initializedNonNullReference(options.itemFactory.intArrayType)
                     })),
             new CfLoad(ValueType.OBJECT, 1),
             new CfReturn(ValueType.OBJECT),
@@ -298,7 +299,8 @@
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
                     new FrameType[] {
-                      FrameType.intType(), FrameType.initialized(options.itemFactory.stringType)
+                      FrameType.intType(),
+                      FrameType.initializedNonNullReference(options.itemFactory.stringType)
                     })),
             new CfReturnVoid(),
             label3),
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
index 5ecb6bb..05a91e5 100644
--- 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
@@ -38,7 +38,11 @@
         InstructionListIterator iterator,
         Instruction instruction,
         StringBuilderOracle oracle) {
-      assert oracle.isModeledStringBuilderInstruction(instruction);
+      assert oracle.isModeledStringBuilderInstruction(
+          instruction,
+          value ->
+              value.getType().isClassType()
+                  && oracle.isStringBuilderType(value.getType().asClassType().getClassType()));
       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.
@@ -70,7 +74,7 @@
         InstructionListIterator iterator,
         Instruction instruction,
         StringBuilderOracle oracle) {
-      assert oracle.isToString(instruction);
+      assert oracle.isToString(instruction, instruction.getFirstOperand());
       iterator.replaceCurrentInstructionWithConstString(appView, code, replacement);
     }
   }
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
index caf8432..830516e 100644
--- 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
@@ -44,6 +44,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -155,7 +156,7 @@
               DFSNodeWithState<BasicBlock, StringBuilderGraphState> node,
               Function<BasicBlock, DFSNodeWithState<BasicBlock, StringBuilderGraphState>>
                   childNodeConsumer) {
-            Map<Value, StringBuilderNode> currentRoots = new IdentityHashMap<>();
+            Map<Value, StringBuilderNode> currentRoots = new LinkedHashMap<>();
             Map<Value, StringBuilderNode> currentTails = new IdentityHashMap<>();
             BasicBlock block = node.getNode();
             StringBuilderEscapeState previousState = analysis.computeBlockEntryState(block);
@@ -229,7 +230,8 @@
             if (instruction.isAssume()) {
               return;
             }
-            if (oracle.isModeledStringBuilderInstruction(instruction)) {
+            if (oracle.isModeledStringBuilderInstruction(
+                instruction, escapeState::isLiveStringBuilder)) {
               createNodesForStringBuilderInstruction(instruction, escapeState, nodeConsumer);
             } else {
               for (Value newEscapedValue : escapeState.getNewlyEscaped()) {
@@ -291,7 +293,7 @@
                     escapeState,
                     actual -> nodeConsumer.accept(actual, appendNode),
                     escaped -> nodeConsumer.accept(escaped, createMutateNode()));
-              } else if (oracle.isToString(instruction)) {
+              } else if (oracle.isToString(instruction, receiver)) {
                 visitStringBuilderValues(
                     receiver,
                     escapeState,
@@ -433,11 +435,12 @@
               }
             }
             if (state.isPartOfLoop) {
-              for (Value value : state.roots.keySet()) {
-                LoopNode loopNode = StringBuilderNode.createLoopNode();
-                loopNode.addSuccessor(state.roots.get(value));
-                state.roots.put(value, loopNode);
-              }
+              state.roots.replaceAll(
+                  (value, currentRoot) -> {
+                    LoopNode loopNode = StringBuilderNode.createLoopNode();
+                    loopNode.addSuccessor(currentRoot);
+                    return loopNode;
+                  });
             }
             return TraversalContinuation.doContinue(state);
           }
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
index ff4a62a..bc92a0f 100644
--- 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
@@ -53,16 +53,30 @@
   public TransferFunctionResult<StringBuilderEscapeState> apply(
       Instruction instruction, StringBuilderEscapeState state) {
     StringBuilderEscapeState.Builder builder = state.builder();
-    boolean isStringBuilderInstruction = oracle.isModeledStringBuilderInstruction(instruction);
+    boolean isStringBuilderInstruction =
+        oracle.isModeledStringBuilderInstruction(instruction, state::isLiveStringBuilder);
     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);
         }
       }
     }
+    if (isStringBuilderInstruction) {
+      if (instruction.isInvokeMethodWithReceiver()) {
+        Value firstOperand = instruction.getFirstOperand();
+        if (!builder.getLiveStringBuilders().contains(firstOperand)) {
+          // We can have constant NULL being the first operand, which we have not marked as
+          // a live string builder.
+          assert firstOperand.getAliasedValue().isConstZero();
+          builder.addLiveStringBuilder(firstOperand);
+        }
+      } else {
+        assert instruction.isNewInstance();
+      }
+    }
     assert !isStringBuilderInstruction
+        || instruction.isNewInstance()
         || builder.getLiveStringBuilders().contains(instruction.getFirstOperand());
     Value outValue = instruction.outValue();
     if (outValue != 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
index acd5c47..22d5c70 100644
--- 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
@@ -39,8 +39,8 @@
     ImplicitToStringNode getImplicitToStringNode();
   }
 
-  private final Set<StringBuilderNode> successors = Sets.newIdentityHashSet();
-  private final Set<StringBuilderNode> predecessors = Sets.newIdentityHashSet();
+  private final Set<StringBuilderNode> successors = Sets.newLinkedHashSet();
+  private final Set<StringBuilderNode> predecessors = Sets.newLinkedHashSet();
 
   // 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.
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
index 000ca52..01be10c 100644
--- 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
@@ -246,16 +246,12 @@
       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) {
+        removeNode = false;
+        if (currentNode.isSplitReferenceNode()) {
+          removeNode = currentNode.getSuccessors().isEmpty() || currentNode.hasSinglePredecessor();
+        } else if (currentNode.isAppendNode() && !isEscaping) {
           AppendNode appendNode = currentNode.asAppendNode();
           boolean canRemoveIfNoInspectionOrMaterializing =
               !munchingState.inspectingCapacity.contains(root)
@@ -271,8 +267,7 @@
                 appendNode.getInstruction(), RemoveStringBuilderAction.getInstance());
             removeNode = true;
           }
-        }
-        if (currentNode.isInitNode()
+        } else if (currentNode.isInitNode()
             && currentNode.asInitNode().hasConstantArgument()
             && currentNode.hasSinglePredecessor()
             && currentNode.getSinglePredecessor().isNewInstanceNode()
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
index b6c0b4f..dd49ae4 100644
--- 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
@@ -12,10 +12,10 @@
 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;
+import java.util.function.Predicate;
 
 /**
  * The {@link StringBuilderOracle} can answer if an instruction is of particular interest to the
@@ -23,13 +23,14 @@
  */
 interface StringBuilderOracle {
 
-  boolean isModeledStringBuilderInstruction(Instruction instruction);
+  boolean isModeledStringBuilderInstruction(
+      Instruction instruction, Predicate<Value> isLiveStringBuilder);
 
   boolean hasStringBuilderType(Value value);
 
   boolean isStringBuilderType(DexType type);
 
-  boolean isToString(Instruction instruction);
+  boolean isToString(Instruction instruction, Value value);
 
   String getConstantArgument(Instruction instruction);
 
@@ -50,13 +51,18 @@
     }
 
     @Override
-    public boolean isModeledStringBuilderInstruction(Instruction instruction) {
+    public boolean isModeledStringBuilderInstruction(
+        Instruction instruction, Predicate<Value> isLiveStringBuilder) {
       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);
+        if (isStringBuildingMethod(factory.stringBuilderMethods, invokedMethod)
+            || isStringBuildingMethod(factory.stringBufferMethods, invokedMethod)) {
+          return true;
+        }
+        return invokedMethod == factory.objectMembers.toString
+            && isLiveStringBuilder.test(instruction.getFirstOperand());
       }
       return false;
     }
@@ -80,14 +86,18 @@
     }
 
     @Override
-    public boolean isToString(Instruction instruction) {
-      if (!instruction.isInvokeMethodWithReceiver()) {
+    public boolean isToString(Instruction instruction, Value value) {
+      if (!instruction.isInvokeVirtual()) {
         return false;
       }
-      InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
+      InvokeVirtual invoke = instruction.asInvokeVirtual();
+      if (invoke.getReceiver() != value) {
+        return false;
+      }
       DexMethod invokedMethod = invoke.getInvokedMethod();
       return factory.stringBuilderMethods.toString == invokedMethod
-          || factory.stringBufferMethods.toString == invokedMethod;
+          || factory.stringBufferMethods.toString == invokedMethod
+          || factory.objectMembers.toString == invokedMethod;
     }
 
     @Override
@@ -151,13 +161,16 @@
         return false;
       }
       DexMethod invokedMethod = instruction.asInvokeMethod().getInvokedMethod();
+      if (factory.stringBuilderMethods.isAppendSubArrayMethod(invokedMethod)
+          || factory.stringBufferMethods.isAppendSubArrayMethod(invokedMethod)) {
+        return false;
+      }
       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;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
index 162a1cc..0cb8e39 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
@@ -66,7 +66,9 @@
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
-                    new FrameType[] {FrameType.initialized(options.itemFactory.objectType)})),
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
+                    })),
             new CfReturnVoid(),
             label3),
         ImmutableList.of(),
@@ -171,7 +173,9 @@
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0},
-                    new FrameType[] {FrameType.initialized(options.itemFactory.objectType)})),
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(options.itemFactory.objectType)
+                    })),
             new CfReturnVoid(),
             label3),
         ImmutableList.of(),
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 fb04da0..a32d51e 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
@@ -91,9 +91,9 @@
       CfFrame frame =
           CfFrame.builder()
               .appendLocal(FrameType.initialized(typeArray))
-              .appendLocal(FrameType.initialized(factory.intType))
+              .appendLocal(FrameType.intType())
               .appendLocal(FrameType.initialized(convertedTypeArray))
-              .appendLocal(FrameType.initialized(factory.intType))
+              .appendLocal(FrameType.intType())
               .build();
 
       // int t1 = arg.length;
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 ab83fdb..707bf1c 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -596,10 +596,13 @@
 
       case ENUM:
         DexField enumField = value.asDexValueEnum().getValue();
+        // This must not be renamed, as the Java runtime will use Enum.valueOf to find the enum's
+        // referenced in annotations. See b/236691999 for details.
+        assert getNamingLens().lookupName(enumField) == enumField.name;
         visitor.visitEnum(
             name,
             getNamingLens().lookupDescriptor(enumField.getType()).toString(),
-            getNamingLens().lookupName(enumField).toString());
+            enumField.name.toString());
         break;
 
       case FIELD:
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 9591d26..668f6de 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -418,7 +418,7 @@
     return mapVersions;
   }
 
-  public MapVersionMappingInformation getFirstMappingInformation() {
+  public MapVersionMappingInformation getFirstMapVersionInformation() {
     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 352c555..aae3f42 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -365,6 +365,30 @@
     return fieldMembers.values();
   }
 
+  public void visitAllFullyQualifiedReferences(Consumer<String> consumer) {
+    mappedFieldNamingsByName
+        .values()
+        .forEach(
+            mappedFields ->
+                mappedFields.forEach(
+                    mappedField -> {
+                      if (mappedField.renamedSignature.isQualified()) {
+                        consumer.accept(mappedField.renamedSignature.toHolderFromQualified());
+                      }
+                    }));
+    mappedRangesByRenamedName
+        .values()
+        .forEach(
+            mappedRanges -> {
+              mappedRanges.mappedRanges.forEach(
+                  mappedRange -> {
+                    if (mappedRange.signature.isQualified()) {
+                      consumer.accept(mappedRange.signature.toHolderFromQualified());
+                    }
+                  });
+            });
+  }
+
   @Override
   public <T extends Throwable> void forAllMethodNaming(
       ThrowingConsumer<MemberNaming, T> consumer) throws T {
diff --git a/src/main/java/com/android/tools/r8/naming/MapVersion.java b/src/main/java/com/android/tools/r8/naming/MapVersion.java
index a23196c..ae93504 100644
--- a/src/main/java/com/android/tools/r8/naming/MapVersion.java
+++ b/src/main/java/com/android/tools/r8/naming/MapVersion.java
@@ -3,8 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import com.android.tools.r8.Keep;
 import com.android.tools.r8.utils.structural.Ordered;
 
+@Keep
 public enum MapVersion implements Ordered<MapVersion> {
   MAP_VERSION_NONE("none"),
   MAP_VERSION_1_0("1.0"),
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 3684506..134b2d9 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
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.ValueType;
 import java.util.function.BiFunction;
 
@@ -97,6 +98,11 @@
   }
 
   @Override
+  public CfFrameState push(CfAnalysisConfig config, TypeElement type) {
+    return this;
+  }
+
+  @Override
   public CfFrameState push(CfAnalysisConfig config, PreciseFrameType frameType) {
     return this;
   }
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 0a2bec0..bd636c3 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
@@ -19,4 +19,6 @@
   int getMaxStack();
 
   boolean isImmediateSuperClassOfCurrentContext(DexType type);
+
+  boolean isStrengthenFramesEnabled();
 }
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 b136b9b..437d01d 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
@@ -19,6 +19,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractState;
 import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.ValueType;
@@ -173,15 +174,18 @@
 
   @SuppressWarnings("InconsistentOverloads")
   public final CfFrameState popObject(
+      AppView<?> appView,
       DexType expectedType,
       CfAnalysisConfig config,
       BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn) {
     CfAssignability assignability = config.getAssignability();
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
     return pop(
         (state, head) ->
             head.isObject()
                     && assignability.isAssignable(
-                        head.getObjectType(config.getCurrentContext().getHolderType()),
+                        head.getObjectType(
+                            dexItemFactory, config.getCurrentContext().getHolderType()),
                         expectedType)
                 ? fn.apply(state, head)
                 : errorUnexpectedStack(head, expectedType));
@@ -224,6 +228,8 @@
 
   public abstract CfFrameState push(CfAnalysisConfig config, DexType type);
 
+  public abstract CfFrameState push(CfAnalysisConfig config, TypeElement type);
+
   public abstract CfFrameState push(CfAnalysisConfig config, PreciseFrameType frameType);
 
   public final CfFrameState push(
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 ae2e594..bc44d01 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
@@ -5,12 +5,17 @@
 package com.android.tools.r8.optimize.interfaces.analysis;
 
 import com.android.tools.r8.cf.code.CfAssignability;
+import com.android.tools.r8.cf.code.CfFrameVerifier;
+import com.android.tools.r8.cf.code.CfFrameVerifier.StackMapStatus;
+import com.android.tools.r8.cf.code.CfFrameVerifierEventConsumer;
 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.CfCodeDiagnostics;
 import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
@@ -23,17 +28,25 @@
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.cf.CfBlock;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.cf.CfControlFlowGraph;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.cf.CfIntraproceduralDataflowAnalysis;
+import com.android.tools.r8.optimize.interfaces.collection.NonEmptyOpenClosedInterfacesCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.OpenClosedInterfacesOptions;
 import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic;
+import com.android.tools.r8.utils.WorkList;
 import com.android.tools.r8.utils.collections.ProgramMethodMap;
+import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
 
 public class CfOpenClosedInterfacesAnalysis {
 
@@ -41,18 +54,20 @@
   private final CfAssignability assignability;
   private final InternalOptions options;
 
+  private final Set<DexClass> openInterfaces = Sets.newConcurrentHashSet();
+
   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;
+    this.options = appView.options();
   }
 
   public boolean run(ExecutorService executorService) throws ExecutionException {
     processClasses(executorService);
+    setClosedInterfaces();
     reportUnverifiableCodeDiagnostics();
     return true;
   }
@@ -62,19 +77,96 @@
   }
 
   private void processClass(DexProgramClass clazz) {
-    clazz.forEachProgramMethodMatching(DexEncodedMethod::hasCode, this::processMethod);
+    clazz.forEachProgramMethodMatching(
+        DexEncodedMethod::hasCode, method -> openInterfaces.addAll(processMethod(method)));
   }
 
-  private void processMethod(ProgramMethod method) {
+  private Set<DexClass> processMethod(ProgramMethod method) {
     Code code = method.getDefinition().getCode();
     if (!code.isCfCode()) {
       assert code.isDefaultInstanceInitializerCode() || code.isDexCode() || code.isThrowNullCode();
-      return;
+      return Collections.emptySet();
     }
-
     CfCode cfCode = code.asCfCode();
-    CfControlFlowGraph cfg = CfControlFlowGraph.create(cfCode, options);
-    TransferFunction transfer = new TransferFunction(method);
+    CfAnalysisConfig config = createConfig(method, cfCode);
+    CfOpenClosedInterfacesAnalysisHelper helper =
+        new CfOpenClosedInterfacesAnalysisHelper(appView, method, unverifiableCodeDiagnostics);
+    if (runLinearScan(method, cfCode, config, helper).isNotPresent()) {
+      runFixpoint(method, cfCode, config, helper);
+    }
+    return helper.getOpenInterfaces();
+  }
+
+  private CfAnalysisConfig createConfig(ProgramMethod method, CfCode code) {
+    return new CfAnalysisConfig() {
+
+      @Override
+      public CfAssignability getAssignability() {
+        return assignability;
+      }
+
+      @Override
+      public DexMethod getCurrentContext() {
+        return method.getReference();
+      }
+
+      @Override
+      public int getMaxLocals() {
+        return code.getMaxLocals();
+      }
+
+      @Override
+      public int getMaxStack() {
+        return code.getMaxStack();
+      }
+
+      @Override
+      public boolean isImmediateSuperClassOfCurrentContext(DexType type) {
+        return type == method.getHolder().getSuperType();
+      }
+
+      @Override
+      public boolean isStrengthenFramesEnabled() {
+        return true;
+      }
+    };
+  }
+
+  private StackMapStatus runLinearScan(
+      ProgramMethod method,
+      CfCode code,
+      CfAnalysisConfig config,
+      CfOpenClosedInterfacesAnalysisHelper helper) {
+    CfFrameVerifierEventConsumer eventConsumer =
+        new CfFrameVerifierEventConsumer() {
+
+          @Override
+          public void acceptError(CfCodeDiagnostics diagnostics) {
+            helper.registerUnverifiableCodeWithFrames(diagnostics);
+          }
+
+          @Override
+          public void acceptInstructionState(CfInstruction instruction, CfFrameState state) {
+            helper.processInstruction(instruction, state);
+          }
+        };
+    CfFrameVerifier verifier =
+        CfFrameVerifier.builder(appView, code, method)
+            .setConfig(config)
+            .setEventConsumer(eventConsumer)
+            .build();
+    StackMapStatus stackMapStatus = verifier.run();
+    assert stackMapStatus.isValid() || helper.getOpenInterfaces().isEmpty();
+    return stackMapStatus;
+  }
+
+  private void runFixpoint(
+      ProgramMethod method,
+      CfCode code,
+      CfAnalysisConfig config,
+      CfOpenClosedInterfacesAnalysisHelper helper) {
+    CfControlFlowGraph cfg = CfControlFlowGraph.create(code, options);
+    TransferFunction transfer = new TransferFunction(config, method);
     CfIntraproceduralDataflowAnalysis<CfFrameState> analysis =
         new CfIntraproceduralDataflowAnalysis<>(appView, CfFrameState.bottom(), cfg, transfer);
     DataflowAnalysisResult result = analysis.run(cfg.getEntryBlock());
@@ -84,23 +176,19 @@
         continue;
       }
       CfFrameState state = analysis.computeBlockEntryState(block);
+      if (state.isError()) {
+        helper.registerUnverifiableCode(method, 0, state.asError());
+        return;
+      }
       do {
         for (int instructionIndex = block.getFirstInstructionIndex();
             instructionIndex <= block.getLastInstructionIndex();
             instructionIndex++) {
-          // TODO(b/214496607): Determine open interfaces.
-          CfInstruction instruction = cfCode.getInstruction(instructionIndex);
+          CfInstruction instruction = code.getInstruction(instructionIndex);
+          helper.processInstruction(instruction, state);
           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()));
-            }
+            helper.registerUnverifiableCode(method, instructionIndex, state.asError());
             return;
           }
         }
@@ -113,6 +201,43 @@
     }
   }
 
+  private void setClosedInterfaces() {
+    // If open interfaces are not allowed and there are one or more suppressions, we should find at
+    // least one open interface.
+    OpenClosedInterfacesOptions openClosedInterfacesOptions =
+        options.getOpenClosedInterfacesOptions();
+    assert openClosedInterfacesOptions.isOpenInterfacesAllowed()
+            || !openClosedInterfacesOptions.hasSuppressions()
+            || !openInterfaces.isEmpty()
+        : "Expected to find at least one open interface";
+
+    includeParentOpenInterfaces();
+
+    appView.setOpenClosedInterfacesCollection(
+        new NonEmptyOpenClosedInterfacesCollection(
+            openInterfaces.stream()
+                .map(DexClass::getType)
+                .collect(
+                    Collectors.toCollection(
+                        () -> SetUtils.newIdentityHashSet(openInterfaces.size())))));
+  }
+
+  private void includeParentOpenInterfaces() {
+    // This includes all parent interfaces of each open interface in the set of open interfaces,
+    // by using the open interfaces as the seen set.
+    WorkList<DexClass> worklist = WorkList.newWorkList(openInterfaces);
+    worklist.addAllIgnoringSeenSet(openInterfaces);
+    while (worklist.hasNext()) {
+      DexClass openInterface = worklist.next();
+      for (DexType indirectOpenInterfaceType : openInterface.getInterfaces()) {
+        DexClass indirectOpenInterfaceDefinition = appView.definitionFor(indirectOpenInterfaceType);
+        if (indirectOpenInterfaceDefinition != null) {
+          worklist.addIfNotSeen(indirectOpenInterfaceDefinition);
+        }
+      }
+    }
+  }
+
   private void reportUnverifiableCodeDiagnostics() {
     Reporter reporter = appView.reporter();
     List<ProgramMethod> methods = new ArrayList<>(unverifiableCodeDiagnostics.size());
@@ -124,43 +249,11 @@
   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();
-            }
-
-            @Override
-            public int getMaxLocals() {
-              return maxLocals;
-            }
-
-            @Override
-            public int getMaxStack() {
-              return maxStack;
-            }
-
-            @Override
-            public boolean isImmediateSuperClassOfCurrentContext(DexType type) {
-              return type == context.getHolder().getSuperType();
-            }
-          };
+    TransferFunction(CfAnalysisConfig config, ProgramMethod context) {
+      this.config = config;
       this.context = context;
     }
 
@@ -180,7 +273,9 @@
         } else {
           initialState =
               initialState.storeLocal(
-                  localIndex, FrameType.initialized(context.getHolderType()), config);
+                  localIndex,
+                  FrameType.initializedNonNullReference(context.getHolderType()),
+                  config);
         }
         localIndex++;
       }
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysisHelper.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysisHelper.java
new file mode 100644
index 0000000..515fa4e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysisHelper.java
@@ -0,0 +1,216 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.optimize.interfaces.analysis;
+
+import com.android.tools.r8.cf.code.CfArrayStore;
+import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfStaticFieldWrite;
+import com.android.tools.r8.cf.code.frame.FrameType;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCodeDiagnostics;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.InterfaceCollection;
+import com.android.tools.r8.ir.analysis.type.ReferenceTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic;
+import com.android.tools.r8.utils.collections.ProgramMethodMap;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+class CfOpenClosedInterfacesAnalysisHelper {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final DexItemFactory dexItemFactory;
+  private final ProgramMethod method;
+  private final InternalOptions options;
+
+  private final Set<DexClass> openInterfaces = Sets.newIdentityHashSet();
+  private final ProgramMethodMap<UnverifiableCfCodeDiagnostic> unverifiableCodeDiagnostics;
+
+  CfOpenClosedInterfacesAnalysisHelper(
+      AppView<AppInfoWithLiveness> appView,
+      ProgramMethod method,
+      ProgramMethodMap<UnverifiableCfCodeDiagnostic> unverifiableCodeDiagnostics) {
+    this.appView = appView;
+    this.dexItemFactory = appView.dexItemFactory();
+    this.method = method;
+    this.options = appView.options();
+    this.unverifiableCodeDiagnostics = unverifiableCodeDiagnostics;
+  }
+
+  Set<DexClass> getOpenInterfaces() {
+    return openInterfaces;
+  }
+
+  void processInstruction(CfInstruction instruction, CfFrameState state) {
+    assert !state.isError();
+    if (state.isBottom()) {
+      // Unreachable.
+      return;
+    }
+    assert state.isConcrete();
+    ConcreteCfFrameState concreteState = state.asConcrete();
+    if (instruction.isArrayStore()) {
+      processArrayStore(instruction.asArrayStore(), concreteState);
+    } else if (instruction.isInstanceFieldPut()) {
+      processInstanceFieldPut(instruction.asInstanceFieldPut(), concreteState);
+    } else if (instruction.isInvoke()) {
+      processInvoke(instruction.asInvoke(), concreteState);
+    } else if (instruction.isReturn() && !instruction.isReturnVoid()) {
+      processReturn(concreteState);
+    } else if (instruction.isStaticFieldPut()) {
+      processStaticFieldPut(instruction.asStaticFieldPut(), concreteState);
+    }
+  }
+
+  private void processArrayStore(CfArrayStore arrayStore, ConcreteCfFrameState state) {
+    if (!arrayStore.getType().isObject()) {
+      return;
+    }
+    state.peekStackElements(
+        3,
+        stack -> {
+          FrameType array = stack.peekFirst();
+          FrameType value = stack.peekLast();
+          if (array.isInitializedNonNullReferenceType()) {
+            ReferenceTypeElement arrayType =
+                array.asInitializedNonNullReferenceType().getInitializedTypeWithInterfaces(appView);
+            if (arrayType.isArrayType()) {
+              processAssignment(value, arrayType.asArrayType().getMemberType());
+            } else {
+              assert false;
+            }
+          } else {
+            assert false;
+          }
+        },
+        options);
+  }
+
+  private void processInstanceFieldPut(
+      CfInstanceFieldWrite instanceFieldPut, ConcreteCfFrameState state) {
+    state.peekStackElement(
+        head -> processAssignment(head, instanceFieldPut.getField().getType()), options);
+  }
+
+  private void processInvoke(CfInvoke invoke, ConcreteCfFrameState state) {
+    DexMethod invokedMethod = invoke.getMethod();
+    state.peekStackElements(
+        invokedMethod.getNumberOfArguments(invoke.isInvokeStatic()),
+        arguments -> {
+          int argumentIndex = 0;
+          for (FrameType argument : arguments) {
+            DexType parameter =
+                invokedMethod.getArgumentType(argumentIndex, invoke.isInvokeStatic());
+            processAssignment(argument, parameter);
+            argumentIndex++;
+          }
+        },
+        options);
+  }
+
+  private void processReturn(ConcreteCfFrameState state) {
+    state.peekStackElement(head -> processAssignment(head, method.getReturnType()), options);
+  }
+
+  private void processStaticFieldPut(
+      CfStaticFieldWrite staticFieldPut, ConcreteCfFrameState state) {
+    state.peekStackElement(
+        head -> processAssignment(head, staticFieldPut.getField().getType()), options);
+  }
+
+  private void processAssignment(FrameType fromType, DexType toType) {
+    if (fromType.isInitializedNonNullReferenceType()) {
+      processAssignment(
+          fromType.asInitializedNonNullReferenceType().getInitializedTypeWithInterfaces(appView),
+          toType);
+    }
+  }
+
+  private void processAssignment(FrameType fromType, TypeElement toType) {
+    if (fromType.isInitializedNonNullReferenceType()) {
+      processAssignment(
+          fromType.asInitializedNonNullReferenceType().getInitializedTypeWithInterfaces(appView),
+          toType);
+    }
+  }
+
+  private void processAssignment(ReferenceTypeElement fromType, DexType toType) {
+    processAssignment(fromType, toType.toTypeElement(appView));
+  }
+
+  private void processAssignment(TypeElement fromType, TypeElement toType) {
+    // If the type is an interface type, then check that the assigned value is a subtype of the
+    // interface type, or mark the interface as open.
+    if (!toType.isClassType()) {
+      return;
+    }
+    ClassTypeElement toClassType = toType.asClassType();
+    if (toClassType.getClassType() != dexItemFactory.objectType) {
+      return;
+    }
+    InterfaceCollection interfaceCollection = toClassType.getInterfaces();
+    interfaceCollection.forEachKnownInterface(
+        knownInterfaceType -> {
+          DexClass knownInterface = appView.definitionFor(knownInterfaceType);
+          if (knownInterface == null) {
+            return;
+          }
+          assert knownInterface.isInterface();
+          if (fromType.lessThanOrEqualUpToNullability(toType, appView)) {
+            return;
+          }
+          assert verifyOpenInterfaceWitnessIsSuppressed(fromType, knownInterface);
+          openInterfaces.add(knownInterface);
+        });
+  }
+
+  void registerUnverifiableCode(
+      ProgramMethod method, int instructionIndex, ErroneousCfFrameState state) {
+    if (options.getCfCodeAnalysisOptions().isUnverifiableCodeReportingEnabled()) {
+      unverifiableCodeDiagnostics.put(
+          method,
+          new UnverifiableCfCodeDiagnostic(
+              method.getMethodReference(),
+              instructionIndex,
+              state.getMessage(),
+              method.getOrigin()));
+    }
+    openInterfaces.clear();
+  }
+
+  void registerUnverifiableCodeWithFrames(CfCodeDiagnostics diagnostics) {
+    if (options.getCfCodeAnalysisOptions().isUnverifiableCodeReportingEnabled()) {
+      unverifiableCodeDiagnostics.put(
+          method,
+          new UnverifiableCfCodeDiagnostic(
+              method.getMethodReference(),
+              -1,
+              diagnostics.getDiagnosticMessage(),
+              method.getOrigin()));
+    }
+    openInterfaces.clear();
+  }
+
+  private boolean verifyOpenInterfaceWitnessIsSuppressed(
+      TypeElement valueType, DexClass openInterface) {
+    assert options.getOpenClosedInterfacesOptions().isSuppressed(appView, valueType, openInterface)
+        : "Unexpected open interface "
+            + openInterface.getTypeName()
+            + " (assignment: "
+            + valueType
+            + ")";
+    return true;
+  }
+}
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 531db8b..0664659 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
@@ -17,13 +17,16 @@
 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.cf.code.frame.WidePrimitiveFrameType;
 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;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.FunctionUtils;
+import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Iterables;
 import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@@ -35,6 +38,7 @@
 import java.util.Iterator;
 import java.util.Objects;
 import java.util.function.BiFunction;
+import java.util.function.Consumer;
 import java.util.function.UnaryOperator;
 
 public class ConcreteCfFrameState extends CfFrameState {
@@ -78,6 +82,9 @@
     if (assignabilityResult.isFailed()) {
       return error(assignabilityResult.asFailed().getMessage());
     }
+    if (config.isStrengthenFramesEnabled()) {
+      return this;
+    }
     CfFrame frameCopy = frame.mutableCopy();
     return new ConcreteCfFrameState(
         frameCopy.getMutableLocals(), frameCopy.getMutableStack(), stackHeight);
@@ -135,6 +142,29 @@
     return new ConcreteCfFrameState(locals, newStack, stackHeight);
   }
 
+  public void peekStackElement(Consumer<PreciseFrameType> consumer, InternalOptions options) {
+    if (!stack.isEmpty()) {
+      consumer.accept(stack.peekLast());
+    } else {
+      assert options.getTestingOptions().allowTypeErrors;
+    }
+  }
+
+  public void peekStackElements(
+      int number, Consumer<Deque<PreciseFrameType>> consumer, InternalOptions options) {
+    if (stack.size() >= number) {
+      Deque<PreciseFrameType> result = new ArrayDeque<>(number);
+      Iterator<PreciseFrameType> iterator = stack.descendingIterator();
+      while (iterator.hasNext() && number > 0) {
+        result.addFirst(iterator.next());
+        number--;
+      }
+      consumer.accept(result);
+    } else {
+      assert options.getTestingOptions().allowTypeErrors;
+    }
+  }
+
   @Override
   public CfFrameState pop() {
     return pop(FunctionUtils::getFirst);
@@ -202,11 +232,23 @@
   }
 
   private static boolean isArrayTypeOrNull(FrameType frameType) {
-    if (frameType.isInitializedReferenceType()
-        && frameType.asInitializedReferenceType().getInitializedType().isArrayType()) {
-      return true;
+    if (frameType.isInitializedReferenceType()) {
+      if (frameType.isNullType()) {
+        return true;
+      } else if (frameType.isInitializedNonNullReferenceTypeWithInterfaces()) {
+        return frameType
+            .asInitializedNonNullReferenceTypeWithInterfaces()
+            .getInitializedTypeWithInterfaces()
+            .isArrayType();
+      } else {
+        assert frameType.isInitializedNonNullReferenceTypeWithoutInterfaces();
+        return frameType
+            .asInitializedNonNullReferenceTypeWithoutInterfaces()
+            .getInitializedType()
+            .isArrayType();
+      }
     }
-    return frameType.isNullType();
+    return false;
   }
 
   @Override
@@ -244,6 +286,11 @@
   }
 
   @Override
+  public CfFrameState push(CfAnalysisConfig config, TypeElement type) {
+    return push(config, FrameType.initialized(type));
+  }
+
+  @Override
   public CfFrameState push(CfAnalysisConfig config, PreciseFrameType frameType) {
     int newStackHeight = stackHeight + frameType.getWidth();
     if (newStackHeight > config.getMaxStack()) {
@@ -270,7 +317,7 @@
     ArrayDeque<PreciseFrameType> newStack = new ArrayDeque<>();
     int newStackHeight = 0;
     return new ConcreteCfFrameState(newLocals, newStack, newStackHeight)
-        .push(config, FrameType.initialized(guard));
+        .push(config, FrameType.initializedNonNullReference(guard));
   }
 
   @Override
@@ -345,32 +392,18 @@
     ObjectBidirectionalIterator<Entry<FrameType>> otherIterator =
         locals.int2ObjectEntrySet().iterator();
     while (iterator.hasNext() && otherIterator.hasNext()) {
-      Entry<FrameType> entry = nextLocal(iterator);
+      Entry<FrameType> entry = iterator.next();
       int localIndex = entry.getIntKey();
       FrameType frameType = entry.getValue();
 
-      Entry<FrameType> otherEntry = nextLocal(otherIterator);
+      Entry<FrameType> otherEntry = otherIterator.next();
       int otherLocalIndex = otherEntry.getIntKey();
       FrameType otherFrameType = otherEntry.getValue();
 
       if (localIndex < otherLocalIndex) {
-        joinLocalsWithDifferentIndices(
-            localIndex,
-            frameType,
-            otherLocalIndex,
-            otherFrameType,
-            iterator,
-            otherIterator,
-            builder);
+        joinLocalsWithDifferentIndices(localIndex, otherLocalIndex, otherIterator, builder);
       } else if (otherLocalIndex < localIndex) {
-        joinLocalsWithDifferentIndices(
-            otherLocalIndex,
-            otherFrameType,
-            localIndex,
-            frameType,
-            otherIterator,
-            iterator,
-            builder);
+        joinLocalsWithDifferentIndices(otherLocalIndex, localIndex, iterator, builder);
       } else {
         joinLocalsWithSameIndex(
             localIndex, frameType, otherFrameType, iterator, otherIterator, appView, builder);
@@ -382,31 +415,12 @@
 
   private void joinLocalsWithDifferentIndices(
       int localIndex,
-      FrameType frameType,
       int otherLocalIndex,
-      FrameType otherFrameType,
-      ObjectBidirectionalIterator<Entry<FrameType>> iterator,
       ObjectBidirectionalIterator<Entry<FrameType>> otherIterator,
       CfFrame.Builder builder) {
     assert localIndex < otherLocalIndex;
-
-    // Check if the smaller local does not overlap with the larger local.
-    if (frameType.isSingle() || localIndex + 1 < otherLocalIndex) {
-      setLocalToTop(localIndex, frameType, builder);
-      previousLocal(otherIterator);
-      return;
-    }
-
-    // The smaller local is a wide that overlaps with the larger local.
-    setLocalToTop(localIndex, frameType, builder);
-
-    // If the larger local is a wide, then its high part is no longer usable. We also need to handle
-    // overlapping of the other local and the next local in the iterator.
-    if (otherFrameType.isWide()) {
-      int lastLocalIndexMarkedTop = otherLocalIndex + 1;
-      setSingleLocalToTop(lastLocalIndexMarkedTop, builder);
-      handleOverlappingLocals(lastLocalIndexMarkedTop, iterator, otherIterator, builder);
-    }
+    setSingleLocalToTop(localIndex, builder);
+    otherIterator.previous();
   }
 
   private void joinLocalsWithSameIndex(
@@ -422,16 +436,19 @@
         joinSingleLocalsWithSameIndex(
             localIndex, frameType.asSingle(), otherFrameType.asSingle(), appView, builder);
       } else {
-        setWideLocalToTop(localIndex, builder);
-        handleOverlappingLocals(localIndex + 1, iterator, otherIterator, builder);
+        joinSingleAndWideLocalsWithSameIndex(localIndex, builder);
       }
     } else {
       if (otherFrameType.isWide()) {
         joinWideLocalsWithSameIndex(
-            localIndex, frameType.asWide(), otherFrameType.asWide(), builder);
+            localIndex,
+            frameType.asWidePrimitive(),
+            otherFrameType.asWidePrimitive(),
+            iterator,
+            otherIterator,
+            builder);
       } else {
-        setWideLocalToTop(localIndex, builder);
-        handleOverlappingLocals(localIndex + 1, otherIterator, iterator, builder);
+        joinSingleAndWideLocalsWithSameIndex(localIndex, builder);
       }
     }
   }
@@ -445,103 +462,78 @@
     builder.store(localIndex, frameType.join(appView, otherFrameType));
   }
 
-  private void joinWideLocalsWithSameIndex(
-      int localIndex,
-      WideFrameType frameType,
-      WideFrameType otherFrameType,
-      CfFrame.Builder builder) {
-    WideFrameType join = frameType.join(otherFrameType);
-    if (join.isPrecise()) {
-      builder.store(localIndex, join);
-    } else {
-      assert join.isTwoWord();
-      setWideLocalToTop(localIndex, builder);
-    }
+  private void joinSingleAndWideLocalsWithSameIndex(int localIndex, CfFrame.Builder builder) {
+    setSingleLocalToTop(localIndex, builder);
   }
 
-  // TODO(b/231521474): By splitting each wide type into single left/right types, the join of each
-  //  (single) local index can be determined by looking at only locals[i] and otherLocals[i] (i.e.,
-  //  there is no carry-over). Thus this entire method could be avoided.
-  private void handleOverlappingLocals(
-      int lastLocalIndexMarkedTop,
+  private void joinWideLocalsWithSameIndex(
+      int localIndex,
+      WidePrimitiveFrameType frameType,
+      WidePrimitiveFrameType otherFrameType,
       ObjectBidirectionalIterator<Entry<FrameType>> iterator,
       ObjectBidirectionalIterator<Entry<FrameType>> otherIterator,
       CfFrame.Builder builder) {
-    ObjectBidirectionalIterator<Entry<FrameType>> currentIterator = iterator;
-    while (currentIterator.hasNext()) {
-      Entry<FrameType> entry = nextLocal(currentIterator);
-      int currentLocalIndex = entry.getIntKey();
-      FrameType currentFrameType = entry.getValue();
-
-      // Check if this local overlaps with the previous wide local that was set to top. If not, then
-      // this local is not affected.
-      if (lastLocalIndexMarkedTop < currentLocalIndex) {
-        // The current local still needs to be handled, thus this rewinds the iterator.
-        previousLocal(currentIterator);
-        break;
-      }
-
-      // Verify that the low part of the current local has been set to top.
-      assert builder.hasLocal(currentLocalIndex);
-      assert builder.getLocal(currentLocalIndex).isOneWord();
-
-      // If the current local is not a wide, then we're done.
-      if (currentFrameType.isSingle()) {
-        // The current local has become top due to the overlap with a wide local. Therefore, this
-        // intentionally does not rewind the iterator.
-        break;
-      }
-
-      // The current local is a wide. We mark its high local index as top due to the overlap, and
-      // check if this wide local overlaps with a wide local in the other locals.
-      lastLocalIndexMarkedTop = currentLocalIndex + 1;
-      setSingleLocalToTop(lastLocalIndexMarkedTop, builder);
-      currentIterator = currentIterator == iterator ? otherIterator : iterator;
+    if (frameType.isWidePrimitiveLow() != otherFrameType.isWidePrimitiveLow()) {
+      setSingleLocalToTop(localIndex, builder);
+      return;
     }
+    if (frameType == otherFrameType) {
+      builder.store(localIndex, frameType);
+    } else {
+      setWideLocalToTop(localIndex, builder);
+    }
+    acceptWidePrimitiveHigh(localIndex, frameType, iterator);
+    acceptWidePrimitiveHigh(localIndex, otherFrameType, otherIterator);
+  }
+
+  private void acceptWidePrimitiveHigh(
+      int localIndex,
+      WidePrimitiveFrameType frameType,
+      ObjectBidirectionalIterator<Entry<FrameType>> iterator) {
+    assert iterator.hasNext();
+    Entry<FrameType> entry = iterator.next();
+    int nextLocalIndex = entry.getIntKey();
+    assert nextLocalIndex == localIndex + 1;
+    FrameType nextFrameType = entry.getValue();
+    assert nextFrameType == frameType.getHighType();
   }
 
   private void joinLocalsOnlyPresentInOne(
       ObjectBidirectionalIterator<Entry<FrameType>> iterator,
       CfFrame.Builder builder,
       UnaryOperator<FrameType> joinWithMissingLocal) {
+    if (!iterator.hasNext()) {
+      return;
+    }
+    Entry<FrameType> firstEntry = iterator.next();
+    if (firstEntry.getValue().isWidePrimitiveHigh()) {
+      setSingleLocalToTop(firstEntry.getIntKey(), builder);
+    } else {
+      joinLocalOnlyPresentInOne(iterator, firstEntry, builder, joinWithMissingLocal);
+    }
     while (iterator.hasNext()) {
-      Entry<FrameType> entry = nextLocal(iterator);
-      int localIndex = entry.getIntKey();
-      FrameType frameType = entry.getValue();
-      FrameType joinFrameType = joinWithMissingLocal.apply(frameType);
-      assert joinFrameType.isSingle() == frameType.isSingle();
-      if (joinFrameType.isOneWord() || joinFrameType.isTwoWord()) {
-        setLocalToTop(localIndex, joinFrameType, builder);
-      } else {
-        builder.store(localIndex, joinFrameType);
-      }
+      Entry<FrameType> entry = iterator.next();
+      joinLocalOnlyPresentInOne(iterator, entry, builder, joinWithMissingLocal);
     }
   }
 
-  private Entry<FrameType> nextLocal(ObjectBidirectionalIterator<Entry<FrameType>> iterator) {
-    Entry<FrameType> entry = iterator.next();
+  private void joinLocalOnlyPresentInOne(
+      ObjectBidirectionalIterator<Entry<FrameType>> iterator,
+      Entry<FrameType> entry,
+      CfFrame.Builder builder,
+      UnaryOperator<FrameType> joinWithMissingLocal) {
+    int localIndex = entry.getIntKey();
     FrameType frameType = entry.getValue();
-    if (frameType.isWidePrimitive()) {
-      assert frameType.isDoubleLow() || frameType.isLongLow();
-      Entry<FrameType> highEntry = iterator.next();
-      assert highEntry.getIntKey() == entry.getIntKey() + 1;
-      assert highEntry.getValue() == frameType.asWidePrimitive().getHighType();
-    } else {
-      assert !frameType.isWide();
+    assert !frameType.isWidePrimitiveHigh();
+    if (frameType.isWidePrimitiveLow()) {
+      acceptWidePrimitiveHigh(localIndex, frameType.asWidePrimitive(), iterator);
     }
-    return entry;
-  }
-
-  private void previousLocal(ObjectBidirectionalIterator<Entry<FrameType>> iterator) {
-    Entry<FrameType> entry = iterator.previous();
-    FrameType frameType = entry.getValue();
-    if (frameType.isWidePrimitive()) {
-      assert frameType.isDoubleHigh() || frameType.isLongHigh();
-      Entry<FrameType> lowEntry = iterator.previous();
-      assert lowEntry.getIntKey() == entry.getIntKey() - 1;
-      assert lowEntry.getValue() == frameType.asWidePrimitive().getLowType();
+    FrameType joinFrameType = joinWithMissingLocal.apply(frameType);
+    assert joinFrameType.isSingle() == frameType.isSingle();
+    if (joinFrameType.isOneWord() || joinFrameType.isTwoWord()) {
+      setLocalToTop(localIndex, joinFrameType, builder);
     } else {
-      assert !frameType.isWide();
+      builder.store(localIndex, joinFrameType);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/EmptyOpenClosedInterfacesAnalysis.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/EmptyOpenClosedInterfacesAnalysis.java
deleted file mode 100644
index 6e09082..0000000
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/EmptyOpenClosedInterfacesAnalysis.java
+++ /dev/null
@@ -1,35 +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.optimize.interfaces.analysis;
-
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.code.IRCode;
-
-public class EmptyOpenClosedInterfacesAnalysis extends OpenClosedInterfacesAnalysis {
-
-  private static final EmptyOpenClosedInterfacesAnalysis INSTANCE =
-      new EmptyOpenClosedInterfacesAnalysis();
-
-  private EmptyOpenClosedInterfacesAnalysis() {}
-
-  static EmptyOpenClosedInterfacesAnalysis getInstance() {
-    return INSTANCE;
-  }
-
-  @Override
-  public void analyze(ProgramMethod method, IRCode code) {
-    // Intentionally empty.
-  }
-
-  @Override
-  public void prepareForPrimaryOptimizationPass() {
-    // Intentionally empty.
-  }
-
-  @Override
-  public void onPrimaryOptimizationPassComplete() {
-    // Intentionally empty.
-  }
-}
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 1274958..eb4d25f 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
@@ -11,7 +11,13 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.ReferenceTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.utils.Pair;
+import java.util.Iterator;
 import java.util.function.BiFunction;
 
 /** An analysis state representing that the code does not type check. */
@@ -54,15 +60,30 @@
 
   private static String format(FrameType frameType, FormatKind formatKind) {
     if (frameType.isInitialized()) {
-      if (frameType.isObject()) {
-        DexType initializedType = frameType.asInitializedReferenceType().getInitializedType();
-        if (initializedType.isArrayType()) {
-          return initializedType.getTypeName();
-        } else if (initializedType.isClassType()) {
-          return "initialized " + initializedType.getTypeName();
-        } else {
-          assert initializedType.isNullValueType();
+      if (frameType.isInitializedReferenceType()) {
+        if (frameType.isNullType()) {
           return "null";
+        } else if (frameType.isInitializedNonNullReferenceTypeWithInterfaces()) {
+          ReferenceTypeElement initializedType =
+              frameType
+                  .asInitializedNonNullReferenceTypeWithInterfaces()
+                  .getInitializedTypeWithInterfaces();
+          if (initializedType.isArrayType()) {
+            return format(initializedType);
+          } else {
+            assert initializedType.isClassType();
+            return "initialized " + format(initializedType);
+          }
+        } else {
+          assert frameType.isInitializedNonNullReferenceTypeWithoutInterfaces();
+          DexType initializedType =
+              frameType.asInitializedNonNullReferenceTypeWithoutInterfaces().getInitializedType();
+          if (initializedType.isArrayType()) {
+            return initializedType.getTypeName();
+          } else {
+            assert initializedType.isClassType();
+            return "initialized " + initializedType.getTypeName();
+          }
         }
       } else {
         assert frameType.isPrimitive();
@@ -88,6 +109,45 @@
     }
   }
 
+  private static String format(TypeElement type) {
+    if (type.isArrayType()) {
+      ArrayTypeElement arrayType = type.asArrayType();
+      TypeElement baseType = arrayType.getBaseType();
+      assert baseType.isClassType() || baseType.isPrimitiveType();
+      boolean parenthesize =
+          baseType.isClassType() && !baseType.asClassType().getInterfaces().isEmpty();
+      StringBuilder result = new StringBuilder();
+      if (parenthesize) {
+        result.append("(");
+      }
+      result.append(format(baseType));
+      if (parenthesize) {
+        result.append(")");
+      }
+      for (int i = 0; i < arrayType.getNesting(); i++) {
+        result.append("[]");
+      }
+      return result.toString();
+    } else if (type.isClassType()) {
+      ClassTypeElement classType = type.asClassType();
+      StringBuilder result = new StringBuilder(classType.getClassType().getTypeName());
+      if (!classType.getInterfaces().isEmpty()) {
+        Iterator<Pair<DexType, Boolean>> iterator =
+            classType.getInterfaces().getInterfaceList().iterator();
+        result.append(" implements ").append(iterator.next().getFirst().getTypeName());
+        while (iterator.hasNext()) {
+          result.append(", ").append(iterator.next().getFirst().getTypeName());
+        }
+      }
+      return result.toString();
+    } else if (type.isNullType()) {
+      return "null";
+    } else {
+      assert type.isPrimitiveType();
+      return type.asPrimitiveType().getTypeName();
+    }
+  }
+
   public static String formatExpected(ValueType valueType) {
     return format(valueType);
   }
@@ -182,6 +242,11 @@
   }
 
   @Override
+  public CfFrameState push(CfAnalysisConfig config, TypeElement type) {
+    return this;
+  }
+
+  @Override
   public CfFrameState push(CfAnalysisConfig config, PreciseFrameType frameType) {
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/OpenClosedInterfacesAnalysis.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/OpenClosedInterfacesAnalysis.java
deleted file mode 100644
index dc13d50..0000000
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/OpenClosedInterfacesAnalysis.java
+++ /dev/null
@@ -1,21 +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.optimize.interfaces.analysis;
-
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.code.IRCode;
-
-public abstract class OpenClosedInterfacesAnalysis {
-
-  public static EmptyOpenClosedInterfacesAnalysis empty() {
-    return EmptyOpenClosedInterfacesAnalysis.getInstance();
-  }
-
-  public abstract void analyze(ProgramMethod method, IRCode code);
-
-  public abstract void prepareForPrimaryOptimizationPass();
-
-  public abstract void onPrimaryOptimizationPassComplete();
-}
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/OpenClosedInterfacesAnalysisImpl.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/OpenClosedInterfacesAnalysisImpl.java
deleted file mode 100644
index 4e474f4..0000000
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/OpenClosedInterfacesAnalysisImpl.java
+++ /dev/null
@@ -1,200 +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.optimize.interfaces.analysis;
-
-import static com.android.tools.r8.ir.code.Opcodes.ARRAY_PUT;
-import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
-import static com.android.tools.r8.ir.code.Opcodes.RETURN;
-import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
-import com.android.tools.r8.ir.analysis.type.InterfaceCollection;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.ArrayPut;
-import com.android.tools.r8.ir.code.FieldPut;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Return;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.optimize.interfaces.collection.NonEmptyOpenClosedInterfacesCollection;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.InternalOptions.OpenClosedInterfacesOptions;
-import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.WorkList;
-import com.google.common.collect.Sets;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-public class OpenClosedInterfacesAnalysisImpl extends OpenClosedInterfacesAnalysis {
-
-  private final AppView<AppInfoWithLiveness> appView;
-  private final DexItemFactory dexItemFactory;
-
-  private Set<DexClass> openInterfaces;
-
-  public OpenClosedInterfacesAnalysisImpl(AppView<AppInfoWithLiveness> appView) {
-    this.appView = appView;
-    this.dexItemFactory = appView.dexItemFactory();
-  }
-
-  @Override
-  public void analyze(ProgramMethod method, IRCode code) {
-    if (openInterfaces == null) {
-      return;
-    }
-    // Analyze each instruction that may assign to an interface type.
-    for (Instruction instruction : code.instructions()) {
-      switch (instruction.opcode()) {
-        case ARRAY_PUT:
-          analyzeArrayPut(instruction.asArrayPut());
-          break;
-        case INSTANCE_PUT:
-        case STATIC_PUT:
-          analyzeFieldPut(instruction.asFieldPut());
-          break;
-        case INVOKE_DIRECT:
-        case INVOKE_INTERFACE:
-        case INVOKE_STATIC:
-        case INVOKE_SUPER:
-        case INVOKE_VIRTUAL:
-          analyzeInvokeMethod(instruction.asInvokeMethod());
-          break;
-        case RETURN:
-          analyzeReturn(instruction.asReturn(), method);
-          break;
-        default:
-          break;
-      }
-    }
-  }
-
-  private void analyzeArrayPut(ArrayPut arrayPut) {
-    Value array = arrayPut.array();
-    TypeElement arrayType = array.getType();
-    if (!arrayType.isArrayType()) {
-      return;
-    }
-    TypeElement valueType = arrayPut.value().getType();
-    TypeElement arrayMemberType = arrayType.asArrayType().getMemberType();
-    checkAssignment(valueType, arrayMemberType);
-  }
-
-  private void analyzeFieldPut(FieldPut fieldPut) {
-    TypeElement valueType = fieldPut.value().getType();
-    TypeElement fieldType = fieldPut.getField().getTypeElement(appView);
-    checkAssignment(valueType, fieldType);
-  }
-
-  private void analyzeInvokeMethod(InvokeMethod invoke) {
-    DexTypeList parameters = invoke.getInvokedMethod().getParameters();
-    for (int parameterIndex = 0; parameterIndex < parameters.size(); parameterIndex++) {
-      Value argument = invoke.getArgumentForParameter(parameterIndex);
-      TypeElement argumentType = argument.getType();
-      TypeElement parameterType = parameters.get(parameterIndex).toTypeElement(appView);
-      checkAssignment(argumentType, parameterType);
-    }
-  }
-
-  private void analyzeReturn(Return returnInstruction, ProgramMethod context) {
-    if (returnInstruction.isReturnVoid()) {
-      return;
-    }
-    TypeElement valueType = returnInstruction.returnValue().getType();
-    TypeElement returnType = context.getReturnType().toTypeElement(appView);
-    checkAssignment(valueType, returnType);
-  }
-
-  private void checkAssignment(TypeElement fromType, TypeElement toType) {
-    // If the type is an interface type, then check that the assigned value is a subtype of the
-    // interface type, or mark the interface as open.
-    if (!toType.isClassType()) {
-      return;
-    }
-    ClassTypeElement toClassType = toType.asClassType();
-    if (toClassType.getClassType() != dexItemFactory.objectType) {
-      return;
-    }
-    InterfaceCollection interfaceCollection = toClassType.getInterfaces();
-    interfaceCollection.forEachKnownInterface(
-        knownInterfaceType -> {
-          DexClass knownInterface = appView.definitionFor(knownInterfaceType);
-          if (knownInterface == null) {
-            return;
-          }
-          assert knownInterface.isInterface();
-          if (fromType.lessThanOrEqualUpToNullability(toType, appView)) {
-            return;
-          }
-          assert verifyOpenInterfaceWitnessIsSuppressed(fromType, knownInterface);
-          openInterfaces.add(knownInterface);
-        });
-  }
-
-  @Override
-  public void prepareForPrimaryOptimizationPass() {
-    openInterfaces = Sets.newConcurrentHashSet();
-  }
-
-  @Override
-  public void onPrimaryOptimizationPassComplete() {
-    // If open interfaces are not allowed and there are one or more suppressions, we should find at
-    // least one open interface.
-    OpenClosedInterfacesOptions options = appView.options().getOpenClosedInterfacesOptions();
-    assert options.isOpenInterfacesAllowed()
-            || !options.hasSuppressions()
-            || !openInterfaces.isEmpty()
-        : "Expected to find at least one open interface";
-
-    includeParentOpenInterfaces();
-    appView.setOpenClosedInterfacesCollection(
-        new NonEmptyOpenClosedInterfacesCollection(
-            openInterfaces.stream()
-                .map(DexClass::getType)
-                .collect(
-                    Collectors.toCollection(
-                        () -> SetUtils.newIdentityHashSet(openInterfaces.size())))));
-    openInterfaces = null;
-  }
-
-  private void includeParentOpenInterfaces() {
-    // This includes all parent interfaces of each open interface in the set of open interfaces,
-    // by using the open interfaces as the seen set.
-    WorkList<DexClass> worklist = WorkList.newWorkList(openInterfaces);
-    worklist.addAllIgnoringSeenSet(openInterfaces);
-    while (worklist.hasNext()) {
-      DexClass openInterface = worklist.next();
-      for (DexType indirectOpenInterfaceType : openInterface.getInterfaces()) {
-        DexClass indirectOpenInterfaceDefinition = appView.definitionFor(indirectOpenInterfaceType);
-        if (indirectOpenInterfaceDefinition != null) {
-          worklist.addIfNotSeen(indirectOpenInterfaceDefinition);
-        }
-      }
-    }
-  }
-
-  private boolean verifyOpenInterfaceWitnessIsSuppressed(
-      TypeElement valueType, DexClass openInterface) {
-    OpenClosedInterfacesOptions options = appView.options().getOpenClosedInterfacesOptions();
-    assert options.isSuppressed(appView, valueType, openInterface)
-        : "Unexpected open interface "
-            + openInterface.getTypeName()
-            + " (assignment: "
-            + valueType
-            + ")";
-    return true;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java
index f8aeb81..df264e4 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java
@@ -22,8 +22,7 @@
 
   @Override
   public boolean isDefinitelyClosed(DexClass clazz) {
-    // TODO(b/214496607): Should return false.
-    return true;
+    return false;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingPartitionFromKeySupplier.java b/src/main/java/com/android/tools/r8/retrace/MappingPartitionFromKeySupplier.java
new file mode 100644
index 0000000..9b8c0a1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/MappingPartitionFromKeySupplier.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;
+
+import com.android.tools.r8.Keep;
+
+/***
+ * Supplier to provide the bytes for a partition specified by a key. Retrace guarantee that the
+ * supplier is always called after a call with the key to {@link RegisterMappingPartitionCallback}
+ * and a call to {@link PrepareMappingPartitionsCallback}
+ *
+ */
+@FunctionalInterface
+@Keep
+public interface MappingPartitionFromKeySupplier {
+
+  byte[] get(String key);
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingPartitionKeyInfo.java b/src/main/java/com/android/tools/r8/retrace/MappingPartitionKeyInfo.java
deleted file mode 100644
index 56f20b1..0000000
--- a/src/main/java/com/android/tools/r8/retrace/MappingPartitionKeyInfo.java
+++ /dev/null
@@ -1,31 +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.references.FieldReference;
-import com.android.tools.r8.references.MethodReference;
-import com.android.tools.r8.references.TypeReference;
-import java.util.function.Consumer;
-
-@Keep
-public interface MappingPartitionKeyInfo {
-
-  void getKeysForClass(ClassReference reference, Consumer<String> keyConsumer);
-
-  void getKeysForClassAndMethodName(
-      ClassReference reference, String methodName, Consumer<String> keyConsumer);
-
-  void getKeysForMethod(MethodReference reference, Consumer<String> keyConsumer);
-
-  void getKeysForField(FieldReference fieldReference, Consumer<String> keyConsumer);
-
-  void getKeysForType(TypeReference typeReference, Consumer<String> keyConsumer);
-
-  static MappingPartitionKeyInfo getDefault(byte[] metadata) {
-    return null;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingPartitioner.java b/src/main/java/com/android/tools/r8/retrace/MappingPartitionMetadata.java
similarity index 74%
rename from src/main/java/com/android/tools/r8/retrace/MappingPartitioner.java
rename to src/main/java/com/android/tools/r8/retrace/MappingPartitionMetadata.java
index 10c641a..32fc36d 100644
--- a/src/main/java/com/android/tools/r8/retrace/MappingPartitioner.java
+++ b/src/main/java/com/android/tools/r8/retrace/MappingPartitionMetadata.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.Keep;
 
 @Keep
-public interface MappingPartitioner {
+public interface MappingPartitionMetadata {
 
-  MappingPartitions partition(ProguardMapProducer mapProducer);
+  byte[] getBytes();
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingSupplier.java b/src/main/java/com/android/tools/r8/retrace/MappingSupplier.java
index d5a8f6a..707023d 100644
--- a/src/main/java/com/android/tools/r8/retrace/MappingSupplier.java
+++ b/src/main/java/com/android/tools/r8/retrace/MappingSupplier.java
@@ -18,7 +18,8 @@
    *
    * @param classReference The minified class reference allowed to be lookup up.
    */
-  public abstract T registerClassUse(ClassReference classReference);
+  public abstract T registerClassUse(
+      DiagnosticsHandler diagnosticsHandler, ClassReference classReference);
 
   public abstract void verifyMappingFileHash(DiagnosticsHandler diagnosticsHandler);
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingSupplierBuilder.java b/src/main/java/com/android/tools/r8/retrace/MappingSupplierBuilder.java
index c57d88a..2a8a646 100644
--- a/src/main/java/com/android/tools/r8/retrace/MappingSupplierBuilder.java
+++ b/src/main/java/com/android/tools/r8/retrace/MappingSupplierBuilder.java
@@ -8,7 +8,7 @@
 
 @Keep
 public abstract class MappingSupplierBuilder<
-    P extends MappingSupplier, T extends MappingSupplierBuilder<P, T>> {
+    P extends MappingSupplier<P>, T extends MappingSupplierBuilder<P, T>> {
 
   public abstract T self();
 
diff --git a/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplier.java b/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplier.java
new file mode 100644
index 0000000..d56aee5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplier.java
@@ -0,0 +1,66 @@
+// 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.naming.MapVersion;
+import com.android.tools.r8.retrace.internal.PartitionMappingSupplierBuilderImpl;
+
+@Keep
+public abstract class PartitionMappingSupplier extends MappingSupplier<PartitionMappingSupplier> {
+
+  public static Builder builder() {
+    return new PartitionMappingSupplierBuilderImpl(MapVersion.MAP_VERSION_NONE);
+  }
+
+  public static NoMetadataBuilder<?> noMetadataBuilder(MapVersion mapVersion) {
+    return new PartitionMappingSupplierBuilderImpl(mapVersion);
+  }
+
+  @Keep
+  public abstract static class NoMetadataBuilder<B extends NoMetadataBuilder<B>>
+      extends MappingSupplierBuilder<PartitionMappingSupplier, B> {
+
+    /***
+     * Callback to be notified of a partition that is later going to be needed. When all needed
+     * partitions are found the callback specified to {@code setPrepareMappingPartitionsCallback} is
+     * called.
+     *
+     * @param registerPartitionCallback the consumer to get keys for partitions.
+     */
+    public abstract Builder setRegisterMappingPartitionCallback(
+        RegisterMappingPartitionCallback registerPartitionCallback);
+
+    /***
+     * A callback notifying that all partitions should be prepared. The prepare callback is
+     * guaranteed to be called prior to any calls to the partition supplier.
+     *
+     * @param prepare the callback to listen for when partitions should be prepared.
+     */
+    public abstract Builder setPrepareMappingPartitionsCallback(
+        PrepareMappingPartitionsCallback prepare);
+
+    /***
+     * Set the partition supplier that is needed for retracing. All partitions needed has been
+     * declared earlier and this should block until the bytes associated with the partition is
+     * ready.
+     *
+     * @param partitionSupplier the function to return a partition to retrace
+     */
+    public abstract Builder setMappingPartitionFromKeySupplier(
+        MappingPartitionFromKeySupplier partitionSupplier);
+  }
+
+  @Keep
+  public abstract static class Builder extends NoMetadataBuilder<Builder> {
+
+    /***
+     * Sets the serialized metadata that was obtained when partitioning.
+     *
+     * @param metadata the serialized metadata
+     */
+    public abstract Builder setMetadata(byte[] metadata);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/PrepareMappingPartitionsCallback.java b/src/main/java/com/android/tools/r8/retrace/PrepareMappingPartitionsCallback.java
new file mode 100644
index 0000000..b19babd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/PrepareMappingPartitionsCallback.java
@@ -0,0 +1,19 @@
+// 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;
+
+/***
+ * Callback to be called to prepare all partitions registered by
+ * {@link RegisterMappingPartitionCallback}. This is guaranteed to be called before calling
+ * {@link MappingPartitionFromKeySupplier}.
+ */
+@FunctionalInterface
+@Keep
+public interface PrepareMappingPartitionsCallback {
+
+  void prepare();
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/ProguardMapPartitioner.java b/src/main/java/com/android/tools/r8/retrace/ProguardMapPartitioner.java
new file mode 100644
index 0000000..5805ae8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/ProguardMapPartitioner.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;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.retrace.internal.ProguardMapPartitionerOnClassNameToText.ProguardMapPartitionerBuilderImpl;
+import java.io.IOException;
+
+@Keep
+public interface ProguardMapPartitioner {
+
+  MappingPartitionMetadata run() throws IOException;
+
+  static ProguardMapPartitionerBuilder<?, ?> builder(DiagnosticsHandler diagnosticsHandler) {
+    return new ProguardMapPartitionerBuilderImpl(diagnosticsHandler);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/ProguardMapPartitionerBuilder.java b/src/main/java/com/android/tools/r8/retrace/ProguardMapPartitionerBuilder.java
new file mode 100644
index 0000000..42fd546
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/ProguardMapPartitionerBuilder.java
@@ -0,0 +1,19 @@
+// 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 java.util.function.Consumer;
+
+@Keep
+public interface ProguardMapPartitionerBuilder<
+    B extends ProguardMapPartitionerBuilder<B, P>, P extends ProguardMapPartitioner> {
+
+  B setProguardMapProducer(ProguardMapProducer proguardMapProducer);
+
+  B setPartitionConsumer(Consumer<MappingPartition> consumer);
+
+  P build();
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingPartitions.java b/src/main/java/com/android/tools/r8/retrace/RegisterMappingPartitionCallback.java
similarity index 62%
rename from src/main/java/com/android/tools/r8/retrace/MappingPartitions.java
rename to src/main/java/com/android/tools/r8/retrace/RegisterMappingPartitionCallback.java
index 9bb9eb7..df5c943 100644
--- a/src/main/java/com/android/tools/r8/retrace/MappingPartitions.java
+++ b/src/main/java/com/android/tools/r8/retrace/RegisterMappingPartitionCallback.java
@@ -5,12 +5,13 @@
 package com.android.tools.r8.retrace;
 
 import com.android.tools.r8.Keep;
-import java.util.function.Consumer;
 
+/***
+ * Interface for registering a mapping partition to be used later.
+ */
+@FunctionalInterface
 @Keep
-public interface MappingPartitions {
+public interface RegisterMappingPartitionCallback {
 
-  byte[] getMetadata();
-
-  void visitPartitions(Consumer<MappingPartition> consumer);
+  void register(String key);
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/ResultWithContext.java b/src/main/java/com/android/tools/r8/retrace/ResultWithContext.java
index 03988a9..4949aec 100644
--- a/src/main/java/com/android/tools/r8/retrace/ResultWithContext.java
+++ b/src/main/java/com/android/tools/r8/retrace/ResultWithContext.java
@@ -5,11 +5,17 @@
 package com.android.tools.r8.retrace;
 
 import com.android.tools.r8.Keep;
+import java.util.List;
+import java.util.function.Consumer;
 
 @Keep
 public interface ResultWithContext<T> {
 
   RetraceStackTraceContext getContext();
 
-  T getResult();
+  List<T> getLines();
+
+  void forEach(Consumer<T> consumer);
+
+  boolean isEmpty();
 }
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 bb1bd0e..29b68ab 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -181,7 +181,7 @@
    * @param context The context to retrace the stack trace in
    * @return list of potentially ambiguous stack traces.
    */
-  public ResultWithContext<List<List<List<T>>>> retraceStackTrace(
+  public ResultWithContext<List<List<T>>> retraceStackTrace(
       List<T> stackTrace, RetraceStackTraceContext context) {
     ListUtils.forEachWithIndex(
         stackTrace,
@@ -203,7 +203,7 @@
    * @param context The context to retrace the stack trace in
    * @return list of potentially ambiguous stack traces.
    */
-  public ResultWithContext<List<List<List<T>>>> retraceStackTraceParsed(
+  public ResultWithContext<List<List<T>>> retraceStackTraceParsed(
       List<ST> stackTrace, RetraceStackTraceContext context) {
     RetraceStackTraceElementProxyEquivalence<T, ST> equivalence =
         new RetraceStackTraceElementProxyEquivalence<>(isVerbose);
@@ -255,7 +255,7 @@
    * @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 ResultWithContext<List<List<T>>> retraceFrame(
+  public ResultWithContext<List<T>> retraceFrame(
       T stackTraceFrame, RetraceStackTraceContext context) {
     Map<RetraceStackTraceElementProxy<T, ST>, List<T>> ambiguousBlocks = new HashMap<>();
     List<RetraceStackTraceElementProxy<T, ST>> ambiguousKeys = new ArrayList<>();
@@ -287,8 +287,7 @@
    * @param context The context to retrace the stack trace in
    * @return the retraced stack trace line
    */
-  public ResultWithContext<List<T>> retraceLine(
-      T stackTraceLine, RetraceStackTraceContext context) {
+  public ResultWithContext<T> retraceLine(T stackTraceLine, RetraceStackTraceContext context) {
     ST parsedLine = stackTraceLineParser.parse(stackTraceLine);
     Box<RetraceStackTraceContext> contextBox = new Box<>(context);
     List<T> result =
@@ -361,25 +360,27 @@
         parsedStackTrace.forEach(
             proxy -> {
               if (proxy.hasClassName()) {
-                mappingSupplier.registerClassUse(proxy.getClassReference());
+                mappingSupplier.registerClassUse(diagnosticsHandler, proxy.getClassReference());
               }
               if (proxy.hasMethodArguments()) {
                 Arrays.stream(proxy.getMethodArguments().split(","))
-                    .forEach(typeName -> registerUseFromTypeReference(mappingSupplier, typeName));
+                    .forEach(
+                        typeName ->
+                            registerUseFromTypeReference(
+                                mappingSupplier, typeName, diagnosticsHandler));
               }
               if (proxy.hasFieldOrReturnType() && !proxy.getFieldOrReturnType().equals("void")) {
-                registerUseFromTypeReference(mappingSupplier, proxy.getFieldOrReturnType());
+                registerUseFromTypeReference(
+                    mappingSupplier, proxy.getFieldOrReturnType(), diagnosticsHandler);
               }
             });
         timing.begin("Retracing");
-        ResultWithContext<List<String>> listResultWithContext =
-            stringRetracer.retraceParsed(parsedStackTrace, context);
+        ResultWithContext<String> result = stringRetracer.retraceParsed(parsedStackTrace, context);
         timing.end();
         timing.begin("Report result");
-        context = listResultWithContext.getContext();
-        List<String> result = listResultWithContext.getResult();
+        context = result.getContext();
         if (!result.isEmpty() || currentStackTrace.isEmpty()) {
-          command.getRetracedStackTraceConsumer().accept(result);
+          command.getRetracedStackTraceConsumer().accept(result.getLines());
         }
         timing.end();
       }
@@ -393,13 +394,13 @@
   }
 
   private static void registerUseFromTypeReference(
-      MappingSupplier<?> mappingSupplier, String typeName) {
+      MappingSupplier<?> mappingSupplier, String typeName, DiagnosticsHandler diagnosticsHandler) {
     TypeReference typeReference = Reference.typeFromTypeName(typeName);
     if (typeReference.isArray()) {
       typeReference = typeReference.asArray().getBaseType();
     }
     if (typeReference.isClass()) {
-      mappingSupplier.registerClassUse(typeReference.asClass());
+      mappingSupplier.registerClassUse(diagnosticsHandler, typeReference.asClass());
     }
   }
 
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 7c00ed2..513b39f 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceOptions.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceOptions.java
@@ -20,12 +20,12 @@
   private final boolean verifyMappingFileHash;
   private final String regularExpression;
   private final DiagnosticsHandler diagnosticsHandler;
-  private final MappingSupplier mappingSupplier;
+  private final MappingSupplier<?> mappingSupplier;
 
   private RetraceOptions(
       String regularExpression,
       DiagnosticsHandler diagnosticsHandler,
-      MappingSupplier mappingSupplier,
+      MappingSupplier<?> mappingSupplier,
       boolean isVerbose,
       boolean verifyMappingFileHash) {
     this.regularExpression = regularExpression;
@@ -54,7 +54,7 @@
     return diagnosticsHandler;
   }
 
-  public MappingSupplier getMappingSupplier() {
+  public MappingSupplier<?> getMappingSupplier() {
     return mappingSupplier;
   }
 
@@ -78,7 +78,7 @@
     private boolean isVerbose;
     private boolean verifyMappingFileHash;
     private final DiagnosticsHandler diagnosticsHandler;
-    private MappingSupplier mappingSupplier;
+    private MappingSupplier<?> mappingSupplier;
     private String regularExpression = defaultRegularExpression();
 
     Builder(DiagnosticsHandler diagnosticsHandler) {
@@ -98,11 +98,17 @@
     }
 
     /** Set a mapping supplier for providing mapping contents. */
-    public Builder setMappingSupplier(MappingSupplier producer) {
-      this.mappingSupplier = producer;
+    public Builder setMappingSupplier(MappingSupplier<?> supplier) {
+      this.mappingSupplier = supplier;
       return this;
     }
 
+    /** Helper method to set a ProguardMapSupplier from a ProguardMapProducer */
+    public Builder setProguardMapProducer(ProguardMapProducer producer) {
+      return setMappingSupplier(
+          ProguardMappingSupplier.builder().setProguardMapProducer(producer).build());
+    }
+
     /**
      * Set a regular expression for parsing the incoming text. The Regular expression must not use
      * naming groups and has special wild cards according to proguard retrace. Note, this will
diff --git a/src/main/java/com/android/tools/r8/retrace/StreamSupplier.java b/src/main/java/com/android/tools/r8/retrace/StreamSupplier.java
new file mode 100644
index 0000000..4a51497
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/StreamSupplier.java
@@ -0,0 +1,19 @@
+// 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;
+
+/**
+ * Supplier of input lines to be retraced.
+ *
+ * @param <E> the type of the {@link Throwable}
+ */
+@FunctionalInterface
+@Keep
+public interface StreamSupplier<E extends Throwable> {
+
+  String getNext() throws E;
+}
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 09f30ca..164a208 100644
--- a/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
@@ -18,7 +18,6 @@
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
-import java.util.function.Supplier;
 
 /**
  * Specialized Retrace class for retracing string retraces, with special handling for appending
@@ -82,12 +81,12 @@
    * @param context The context to retrace the stack trace in
    * @return the retraced stack trace
    */
-  public ResultWithContext<List<String>> retrace(
+  public ResultWithContext<String> retrace(
       List<String> stackTrace, RetraceStackTraceContext context) {
-    ResultWithContext<List<List<List<String>>>> listResultWithContext =
+    ResultWithContext<List<List<String>>> listResultWithContext =
         retraceStackTrace(stackTrace, context);
     List<String> retracedStrings = new ArrayList<>();
-    for (List<List<String>> newLines : listResultWithContext.getResult()) {
+    for (List<List<String>> newLines : listResultWithContext.getLines()) {
       ListUtils.forEachWithIndex(
           newLines,
           (inlineFrames, ambiguousIndex) -> {
@@ -122,12 +121,12 @@
    * @param context The context to retrace the stack trace in
    * @return the retraced stack trace
    */
-  public ResultWithContext<List<String>> retraceParsed(
+  public ResultWithContext<String> retraceParsed(
       List<StackTraceElementStringProxy> stackTrace, RetraceStackTraceContext context) {
-    ResultWithContext<List<List<List<String>>>> listResultWithContext =
+    ResultWithContext<List<List<String>>> listResultWithContext =
         retraceStackTraceParsed(stackTrace, context);
     List<String> retracedStrings = new ArrayList<>();
-    for (List<List<String>> newLines : listResultWithContext.getResult()) {
+    for (List<List<String>> newLines : listResultWithContext.getLines()) {
       ListUtils.forEachWithIndex(
           newLines,
           (inlineFrames, ambiguousIndex) -> {
@@ -161,12 +160,11 @@
    * @param context The context to retrace the stack trace in
    * @return the retraced frames
    */
-  public ResultWithContext<List<String>> retrace(
+  public ResultWithContext<String> retrace(
       String stackTraceLine, RetraceStackTraceContext context) {
-    ResultWithContext<List<List<String>>> listResultWithContext =
-        retraceFrame(stackTraceLine, context);
+    ResultWithContext<List<String>> listResultWithContext = retraceFrame(stackTraceLine, context);
     List<String> result = new ArrayList<>();
-    joinAmbiguousLines(listResultWithContext.getResult(), result::add);
+    joinAmbiguousLines(listResultWithContext.getLines(), result::add);
     return ResultWithContextImpl.create(result, listResultWithContext.getContext());
   }
 
@@ -176,13 +174,14 @@
    * @param lineSupplier the supplier of strings with returning null as terminator
    * @param lineConsumer the consumer of retraced strings
    */
-  public void retrace(Supplier<String> lineSupplier, Consumer<String> lineConsumer) {
+  public <E extends Throwable> void retraceSupplier(
+      StreamSupplier<E> lineSupplier, Consumer<String> lineConsumer) throws E {
     RetraceStackTraceContext context = RetraceStackTraceContext.empty();
     String retraceLine;
-    while ((retraceLine = lineSupplier.get()) != null) {
-      ResultWithContext<List<String>> result = retrace(retraceLine, context);
+    while ((retraceLine = lineSupplier.getNext()) != null) {
+      ResultWithContext<String> result = retrace(retraceLine, context);
       context = result.getContext();
-      result.getResult().forEach(lineConsumer);
+      result.forEach(lineConsumer);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionImpl.java
new file mode 100644
index 0000000..4a12147
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionImpl.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.internal;
+
+import com.android.tools.r8.retrace.MappingPartition;
+
+public class MappingPartitionImpl implements MappingPartition {
+
+  private final String key;
+  private final byte[] payload;
+
+  public MappingPartitionImpl(String key, byte[] payload) {
+    this.key = key;
+    this.payload = payload;
+  }
+
+  @Override
+  public String getKey() {
+    return key;
+  }
+
+  @Override
+  public byte[] getPayload() {
+    return payload;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionKeyStrategy.java b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionKeyStrategy.java
new file mode 100644
index 0000000..ad53968
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionKeyStrategy.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 com.android.tools.r8.retrace.internal;
+
+public enum MappingPartitionKeyStrategy {
+  OBFUSCATED_TYPE_NAME_AS_KEY(0);
+
+  final int serializedKey;
+
+  MappingPartitionKeyStrategy(int serializedKey) {
+    this.serializedKey = serializedKey;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java
new file mode 100644
index 0000000..6c2e3ca
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.internal;
+
+import static com.android.tools.r8.retrace.internal.MappingPartitionKeyStrategy.OBFUSCATED_TYPE_NAME_AS_KEY;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.naming.MapVersion;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.retrace.MappingPartitionMetadata;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.google.common.primitives.Ints;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+public interface MappingPartitionMetadataInternal extends MappingPartitionMetadata {
+
+  String getKey(ClassReference classReference);
+
+  MapVersion getMapVersion();
+
+  byte ZERO_BYTE = (byte) 0;
+
+  static MappingPartitionMetadataInternal createFromBytes(
+      byte[] bytes, MapVersion fallBackMapVersion, DiagnosticsHandler diagnosticsHandler) {
+    if (bytes == null) {
+      return obfuscatedTypeNameAsKey(fallBackMapVersion);
+    } else if (bytes.length > 2) {
+      int serializedStrategyId = Ints.fromBytes(ZERO_BYTE, ZERO_BYTE, bytes[0], bytes[1]);
+      MapVersion mapVersion = MapVersion.fromName(new String(bytes, 2, bytes.length - 2));
+      if (serializedStrategyId == OBFUSCATED_TYPE_NAME_AS_KEY.serializedKey) {
+        return obfuscatedTypeNameAsKey(mapVersion);
+      }
+    }
+    RuntimeException exception = new RuntimeException("Unable to build key strategy from metadata");
+    diagnosticsHandler.error(new ExceptionDiagnostic(exception));
+    throw exception;
+  }
+
+  static ObfuscatedTypeNameAsKeyMetadata obfuscatedTypeNameAsKey(MapVersion mapVersion) {
+    return new ObfuscatedTypeNameAsKeyMetadata(mapVersion);
+  }
+
+  class ObfuscatedTypeNameAsKeyMetadata implements MappingPartitionMetadataInternal {
+
+    private final MapVersion mapVersion;
+
+    private ObfuscatedTypeNameAsKeyMetadata(MapVersion mapVersion) {
+      this.mapVersion = mapVersion;
+    }
+
+    @Override
+    public String getKey(ClassReference classReference) {
+      return classReference.getTypeName();
+    }
+
+    @Override
+    public MapVersion getMapVersion() {
+      return mapVersion;
+    }
+
+    @Override
+    public byte[] getBytes() {
+      try {
+        ByteArrayOutputStream temp = new ByteArrayOutputStream();
+        DataOutputStream dataOutputStream = new DataOutputStream(temp);
+        dataOutputStream.writeShort(OBFUSCATED_TYPE_NAME_AS_KEY.serializedKey);
+        dataOutputStream.writeBytes(mapVersion.getName());
+        dataOutputStream.close();
+        return temp.toByteArray();
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierBuilderImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierBuilderImpl.java
new file mode 100644
index 0000000..2f69645
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierBuilderImpl.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.retrace.internal;
+
+import com.android.tools.r8.naming.MapVersion;
+import com.android.tools.r8.retrace.MappingPartitionFromKeySupplier;
+import com.android.tools.r8.retrace.PartitionMappingSupplier;
+import com.android.tools.r8.retrace.PrepareMappingPartitionsCallback;
+import com.android.tools.r8.retrace.RegisterMappingPartitionCallback;
+
+public class PartitionMappingSupplierBuilderImpl extends PartitionMappingSupplier.Builder {
+
+  private MappingPartitionFromKeySupplier partitionSupplier;
+  private RegisterMappingPartitionCallback registerPartitionCallback = key -> {};
+  private PrepareMappingPartitionsCallback prepare = () -> {};
+  private byte[] metadata;
+  private final MapVersion fallbackMapVersion;
+  private boolean allowExperimental = false;
+
+  public PartitionMappingSupplierBuilderImpl(MapVersion fallbackMapVersion) {
+    this.fallbackMapVersion = fallbackMapVersion;
+  }
+
+  @Override
+  public PartitionMappingSupplier.Builder self() {
+    return this;
+  }
+
+  @Override
+  public PartitionMappingSupplier.Builder setAllowExperimental(boolean allowExperimental) {
+    this.allowExperimental = allowExperimental;
+    return self();
+  }
+
+  @Override
+  public PartitionMappingSupplier.Builder setMetadata(byte[] metadata) {
+    this.metadata = metadata;
+    return self();
+  }
+
+  @Override
+  public PartitionMappingSupplier.Builder setRegisterMappingPartitionCallback(
+      RegisterMappingPartitionCallback registerPartitionCallback) {
+    this.registerPartitionCallback = registerPartitionCallback;
+    return self();
+  }
+
+  @Override
+  public PartitionMappingSupplier.Builder setPrepareMappingPartitionsCallback(
+      PrepareMappingPartitionsCallback prepare) {
+    this.prepare = prepare;
+    return self();
+  }
+
+  @Override
+  public PartitionMappingSupplier.Builder setMappingPartitionFromKeySupplier(
+      MappingPartitionFromKeySupplier partitionSupplier) {
+    this.partitionSupplier = partitionSupplier;
+    return self();
+  }
+
+  @Override
+  public PartitionMappingSupplier build() {
+    if (partitionSupplier == null) {
+      throw new RuntimeException(
+          "Cannot build without providing a mapping partition from key supplier.");
+    }
+    return new PartitionMappingSupplierImpl(
+        metadata,
+        registerPartitionCallback,
+        prepare,
+        partitionSupplier,
+        allowExperimental,
+        fallbackMapVersion);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierImpl.java
new file mode 100644
index 0000000..dfddd37
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierImpl.java
@@ -0,0 +1,138 @@
+// 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 static com.google.common.base.Predicates.alwaysTrue;
+
+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.mappinginformation.MapVersionMappingInformation;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.InvalidMappingFileException;
+import com.android.tools.r8.retrace.MappingPartitionFromKeySupplier;
+import com.android.tools.r8.retrace.PartitionMappingSupplier;
+import com.android.tools.r8.retrace.PrepareMappingPartitionsCallback;
+import com.android.tools.r8.retrace.RegisterMappingPartitionCallback;
+import com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.ProguardMapReaderWithFilteringInputBuffer;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+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 PartitionMappingSupplierImpl extends PartitionMappingSupplier {
+
+  private final byte[] metadata;
+  private final RegisterMappingPartitionCallback registerPartitionCallback;
+  private final PrepareMappingPartitionsCallback prepare;
+  private final MappingPartitionFromKeySupplier partitionSupplier;
+  private final boolean allowExperimental;
+  private final MapVersion fallbackMapVersion;
+
+  private ClassNameMapper classNameMapper;
+  private final Set<String> pendingKeys = new LinkedHashSet<>();
+  private final Set<String> builtKeys = new HashSet<>();
+
+  private MappingPartitionMetadataInternal mappingPartitionMetadataCache;
+
+  PartitionMappingSupplierImpl(
+      byte[] metadata,
+      RegisterMappingPartitionCallback registerPartitionCallback,
+      PrepareMappingPartitionsCallback prepare,
+      MappingPartitionFromKeySupplier partitionSupplier,
+      boolean allowExperimental,
+      MapVersion fallbackMapVersion) {
+    this.metadata = metadata;
+    this.registerPartitionCallback = registerPartitionCallback;
+    this.prepare = prepare;
+    this.partitionSupplier = partitionSupplier;
+    this.allowExperimental = allowExperimental;
+    this.fallbackMapVersion = fallbackMapVersion;
+  }
+
+  private MappingPartitionMetadataInternal getMetadata(DiagnosticsHandler diagnosticsHandler) {
+    if (mappingPartitionMetadataCache != null) {
+      return mappingPartitionMetadataCache;
+    }
+    return mappingPartitionMetadataCache =
+        MappingPartitionMetadataInternal.createFromBytes(
+            metadata, fallbackMapVersion, diagnosticsHandler);
+  }
+
+  @Override
+  Set<MapVersionMappingInformation> getMapVersions(DiagnosticsHandler diagnosticsHandler) {
+    return Collections.singleton(
+        new MapVersionMappingInformation(getMetadata(diagnosticsHandler).getMapVersion(), ""));
+  }
+
+  @Override
+  ClassNamingForNameMapper getClassNaming(DiagnosticsHandler diagnosticsHandler, String typeName) {
+    registerClassUse(diagnosticsHandler, Reference.classFromTypeName(typeName));
+    return getClassNameMapper(diagnosticsHandler).getClassNaming(typeName);
+  }
+
+  @Override
+  String getSourceFileForClass(DiagnosticsHandler diagnosticsHandler, String typeName) {
+    // Getting source file should not trigger new fetches of partitions so we are not calling
+    // register here.
+    return getClassNameMapper(diagnosticsHandler).getSourceFile(typeName);
+  }
+
+  private ClassNameMapper getClassNameMapper(DiagnosticsHandler diagnosticsHandler) {
+    MappingPartitionMetadataInternal metadata = getMetadata(diagnosticsHandler);
+    if (!pendingKeys.isEmpty()) {
+      prepare.prepare();
+    }
+    for (String pendingKey : pendingKeys) {
+      try {
+        LineReader reader =
+            new ProguardMapReaderWithFilteringInputBuffer(
+                new ByteArrayInputStream(partitionSupplier.get(pendingKey)), alwaysTrue(), true);
+        classNameMapper =
+            ClassNameMapper.mapperFromLineReaderWithFiltering(
+                    reader, metadata.getMapVersion(), diagnosticsHandler, true, allowExperimental)
+                .combine(this.classNameMapper);
+      } catch (IOException e) {
+        throw new InvalidMappingFileException(e);
+      }
+    }
+    builtKeys.addAll(pendingKeys);
+    pendingKeys.clear();
+    if (classNameMapper == null) {
+      classNameMapper = ClassNameMapper.builder().build();
+    }
+    return classNameMapper;
+  }
+
+  @Override
+  public PartitionMappingSupplier registerClassUse(
+      DiagnosticsHandler diagnosticsHandler, ClassReference classReference) {
+    registerKeyUse(getMetadata(diagnosticsHandler).getKey(classReference));
+    return this;
+  }
+
+  private void registerKeyUse(String key) {
+    if (!builtKeys.contains(key) && pendingKeys.add(key)) {
+      registerPartitionCallback.register(key);
+    }
+  }
+
+  @Override
+  public void verifyMappingFileHash(DiagnosticsHandler diagnosticsHandler) {
+    String errorMessage = "Cannot verify map file hash for partitions";
+    diagnosticsHandler.error(new StringDiagnostic(errorMessage));
+    throw new RuntimeException(errorMessage);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java
new file mode 100644
index 0000000..9770a0b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java
@@ -0,0 +1,189 @@
+// 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 static com.google.common.base.Predicates.alwaysTrue;
+
+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.mappinginformation.FileNameInformation;
+import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
+import com.android.tools.r8.retrace.MappingPartition;
+import com.android.tools.r8.retrace.MappingPartitionMetadata;
+import com.android.tools.r8.retrace.ProguardMapPartitioner;
+import com.android.tools.r8.retrace.ProguardMapPartitionerBuilder;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+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.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+public class ProguardMapPartitionerOnClassNameToText implements ProguardMapPartitioner {
+
+  private final ProguardMapProducer proguardMapProducer;
+  private final Consumer<MappingPartition> mappingPartitionConsumer;
+  private final DiagnosticsHandler diagnosticsHandler;
+
+  private ProguardMapPartitionerOnClassNameToText(
+      ProguardMapProducer proguardMapProducer,
+      Consumer<MappingPartition> mappingPartitionConsumer,
+      DiagnosticsHandler diagnosticsHandler) {
+    this.proguardMapProducer = proguardMapProducer;
+    this.mappingPartitionConsumer = mappingPartitionConsumer;
+    this.diagnosticsHandler = diagnosticsHandler;
+  }
+
+  @Override
+  public MappingPartitionMetadata run() throws IOException {
+    PartitionLineReader reader =
+        new PartitionLineReader(
+            proguardMapProducer.isFileBacked()
+                ? new ProguardMapReaderWithFilteringMappedBuffer(
+                    proguardMapProducer.getPath(), alwaysTrue(), true)
+                : new ProguardMapReaderWithFilteringInputBuffer(
+                    proguardMapProducer.get(), alwaysTrue(), true));
+    // Produce a global mapper to read all from the reader but also to capture all source file
+    // mappings.
+    ClassNameMapper classMapper =
+        ClassNameMapper.mapperFromLineReaderWithFiltering(
+            reader, MapVersion.MAP_VERSION_UNKNOWN, diagnosticsHandler, true, true);
+    // We can then iterate over all sections.
+    reader.forEachClassMapping(
+        (classMapping, entries) -> {
+          try {
+            String payload = StringUtils.join("\n", entries);
+            ClassNameMapper classNameMapper = ClassNameMapper.mapperFromString(payload);
+            if (classNameMapper.getClassNameMappings().size() != 1) {
+              diagnosticsHandler.error(
+                  new StringDiagnostic("Multiple class names in payload\n: " + payload));
+              return;
+            }
+            Entry<String, ClassNamingForNameMapper> currentClassMapping =
+                classNameMapper.getClassNameMappings().entrySet().iterator().next();
+            ClassNamingForNameMapper value = currentClassMapping.getValue();
+            Set<String> seenMappings = new HashSet<>();
+            StringBuilder payloadWithClassReferences = new StringBuilder();
+            value.visitAllFullyQualifiedReferences(
+                holder -> {
+                  if (seenMappings.add(holder)) {
+                    payloadWithClassReferences.append(
+                        getSourceFileMapping(holder, classMapper.getSourceFile(holder)));
+                  }
+                });
+            payloadWithClassReferences.append(payload);
+            mappingPartitionConsumer.accept(
+                new MappingPartitionImpl(
+                    currentClassMapping.getKey(),
+                    payloadWithClassReferences.toString().getBytes(StandardCharsets.UTF_8)));
+          } catch (IOException e) {
+            diagnosticsHandler.error(new ExceptionDiagnostic(e));
+          }
+        });
+    MapVersion mapVersion = MapVersion.MAP_VERSION_UNKNOWN;
+    MapVersionMappingInformation mapVersionInfo = classMapper.getFirstMapVersionInformation();
+    if (mapVersionInfo != null) {
+      mapVersion = mapVersionInfo.getMapVersion();
+    }
+    return MappingPartitionMetadataInternal.obfuscatedTypeNameAsKey(mapVersion);
+  }
+
+  private String getSourceFileMapping(String className, String sourceFile) {
+    if (sourceFile == null) {
+      return "";
+    }
+    return className
+        + " -> "
+        + className
+        + ":"
+        + "\n  # "
+        + FileNameInformation.build(sourceFile).serialize()
+        + "\n";
+  }
+
+  public static class PartitionLineReader implements LineReader {
+
+    private final ProguardMapReaderWithFiltering lineReader;
+    private final Map<String, List<String>> readSections = new LinkedHashMap<>();
+    private final List<String> preamble;
+    private List<String> currentList;
+
+    public PartitionLineReader(ProguardMapReaderWithFiltering lineReader) {
+      this.lineReader = lineReader;
+      currentList = new ArrayList<>();
+      preamble = currentList;
+    }
+
+    @Override
+    public String readLine() throws IOException {
+      String readLine = lineReader.readLine();
+      if (readLine == null) {
+        return null;
+      }
+      if (lineReader.isClassMapping()) {
+        currentList = new ArrayList<>();
+        readSections.put(readLine, currentList);
+      }
+      currentList.add(readLine);
+      return readLine;
+    }
+
+    @Override
+    public void close() throws IOException {
+      lineReader.close();
+    }
+
+    public void forEachClassMapping(BiConsumer<String, List<String>> consumer) {
+      readSections.forEach(consumer);
+    }
+  }
+
+  public static class ProguardMapPartitionerBuilderImpl
+      implements ProguardMapPartitionerBuilder<
+          ProguardMapPartitionerBuilderImpl, ProguardMapPartitionerOnClassNameToText> {
+
+    private ProguardMapProducer proguardMapProducer;
+    private Consumer<MappingPartition> mappingPartitionConsumer;
+    private final DiagnosticsHandler diagnosticsHandler;
+
+    public ProguardMapPartitionerBuilderImpl(DiagnosticsHandler diagnosticsHandler) {
+      this.diagnosticsHandler = diagnosticsHandler;
+    }
+
+    @Override
+    public ProguardMapPartitionerBuilderImpl setPartitionConsumer(
+        Consumer<MappingPartition> consumer) {
+      this.mappingPartitionConsumer = consumer;
+      return this;
+    }
+
+    @Override
+    public ProguardMapPartitionerBuilderImpl setProguardMapProducer(
+        ProguardMapProducer proguardMapProducer) {
+      this.proguardMapProducer = proguardMapProducer;
+      return this;
+    }
+
+    @Override
+    public ProguardMapPartitionerOnClassNameToText build() {
+      return new ProguardMapPartitionerOnClassNameToText(
+          proguardMapProducer, mappingPartitionConsumer, 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 e6589d5f..7c30dcd 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
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.LineParserState.COMPLETE_CLASS_MAPPING;
 import static com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.LineParserState.IS_COMMENT_SOURCE_FILE;
+import static com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.LineParserState.NOT_CLASS_MAPPING_OR_SOURCE_FILE;
 import static java.lang.Integer.MAX_VALUE;
 
 import com.android.tools.r8.errors.Unreachable;
@@ -207,6 +208,7 @@
 
   private boolean isInsideClassOfInterest = false;
   private boolean seenFirstClass = false;
+  private LineParserState lineParserState = NOT_CLASS_MAPPING_OR_SOURCE_FILE;
 
   @Override
   public String readLine() throws IOException {
@@ -218,7 +220,7 @@
       if (filter == null) {
         return new String(bytes, startIndex, endIndex - startIndex, StandardCharsets.UTF_8);
       }
-      LineParserState lineParserState = LineParserState.computeState(bytes, startIndex, endIndex);
+      lineParserState = LineParserState.computeState(bytes, startIndex, endIndex);
       if (lineParserState == COMPLETE_CLASS_MAPPING) {
         seenFirstClass = true;
         String classMapping = getBufferAsString(bytes);
@@ -235,6 +237,10 @@
     }
   }
 
+  public boolean isClassMapping() {
+    return lineParserState == COMPLETE_CLASS_MAPPING;
+  }
+
   private String getBufferAsString(byte[] bytes) {
     return new String(bytes, startIndex, endIndex - startIndex, StandardCharsets.UTF_8);
   }
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
index a6ae27b..632ca66 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierImpl.java
@@ -112,13 +112,14 @@
     if (classNameMapper == null) {
       return MapVersion.MAP_VERSION_NONE;
     } else {
-      MapVersionMappingInformation mapVersion = classNameMapper.getFirstMappingInformation();
+      MapVersionMappingInformation mapVersion = classNameMapper.getFirstMapVersionInformation();
       return mapVersion == null ? MapVersion.MAP_VERSION_UNKNOWN : mapVersion.getMapVersion();
     }
   }
 
   @Override
-  public ProguardMappingSupplier registerClassUse(ClassReference classReference) {
+  public ProguardMappingSupplier registerClassUse(
+      DiagnosticsHandler diagnosticsHandler, ClassReference classReference) {
     if (!hasClassMappingFor(classReference.getTypeName())) {
       pendingClassMappings.add(classReference.getTypeName());
     }
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
index ab92a65..e65ecd5 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ResultWithContextImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ResultWithContextImpl.java
@@ -6,18 +6,20 @@
 
 import com.android.tools.r8.retrace.ResultWithContext;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import java.util.List;
+import java.util.function.Consumer;
 
 public class ResultWithContextImpl<T> implements ResultWithContext<T> {
 
-  private final T result;
+  private final List<T> result;
   private final RetraceStackTraceContext context;
 
-  private ResultWithContextImpl(T result, RetraceStackTraceContext context) {
+  private ResultWithContextImpl(List<T> result, RetraceStackTraceContext context) {
     this.result = result;
     this.context = context;
   }
 
-  public static <T> ResultWithContext<T> create(T result, RetraceStackTraceContext context) {
+  public static <T> ResultWithContext<T> create(List<T> result, RetraceStackTraceContext context) {
     return new ResultWithContextImpl<>(result, context);
   }
 
@@ -27,7 +29,17 @@
   }
 
   @Override
-  public T getResult() {
+  public List<T> getLines() {
     return result;
   }
+
+  @Override
+  public void forEach(Consumer<T> consumer) {
+    result.forEach(consumer);
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return result.isEmpty();
+  }
 }
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 689a3f0..6214a5a 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -5387,10 +5387,16 @@
                     fieldReference, new FieldAccessInfoImpl(fieldReference));
         fieldAccessInfo.setReadFromAnnotation();
         markFieldAsLive(field, context, reason);
-        // When an annotation has a field of an enum type the JVM will use the values() method on
-        // that enum class if the field is referenced.
+        // In a class file an enum reference in an annotation is written as enum descriptor and
+        // enum name. At runtime the JVM use valueOf on the enum class with the name to get the
+        // instance. This indirectly use the values() method on that enum class. Also keep the
+        // name of the field and the name of the enum in sync as otherwise recovering the field to
+        // name relationship requires analysis of the enum <clinit> when this CF code is processed
+        // again (e.g. as input to D8 for converting to DEX). See b/236691999 for more info.
         if (options.isGeneratingClassFiles() && field.getHolder().isEnum()) {
           markEnumValuesAsReachable(field.getHolder(), reason);
+          applyMinimumKeepInfoWhenLive(
+              field, KeepFieldInfo.newEmptyJoiner().disallowMinification());
         }
       } else {
         // There is no dispatch on annotations, so only keep what is directly referenced.
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 1932bd0..fe4baac 100644
--- a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
@@ -205,14 +205,22 @@
         assert !synthesizingContextReferences.isEmpty();
         for (DexReference synthesizingContextReference : synthesizingContextReferences) {
           if (synthesizingContextReference.isDexMethod()) {
-            DexProgramClass holder =
+            DexProgramClass synthesizingContextHolder =
                 appView
                     .definitionFor(synthesizingContextReference.getContextType())
                     .asProgramClass();
             ProgramMethod synthesizingContext =
-                holder.lookupProgramMethod(synthesizingContextReference.asDexMethod());
-            assert synthesizingContext != null;
-            rewrittenContexts.add(synthesizingContext);
+                synthesizingContextHolder.lookupProgramMethod(
+                    synthesizingContextReference.asDexMethod());
+            if (synthesizingContext != null) {
+              rewrittenContexts.add(synthesizingContext);
+            } else {
+              // The synthesizing context no longer exists. It must have been forcefully moved due
+              // to desugaring. For now we report the holder of the synthesizing context as the
+              // origin of the missing class reference.
+              assert synthesizingContextHolder.isInterface();
+              rewrittenContexts.add(synthesizingContextHolder);
+            }
           } else if (synthesizingContextReference.isDexType()) {
             DexProgramClass synthesizingClass =
                 appView.definitionFor(synthesizingContextReference.asDexType()).asProgramClass();
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index eb764c0..822fa38 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -744,8 +744,6 @@
       }
       DexClass clazz = appView.appInfo().definitionForWithoutExistenceAssert(externalType);
       if (clazz != null && isNotSyntheticType(clazz.type)) {
-        assert options.testing.allowConflictingSyntheticTypes
-            : "Unexpected creation of an existing external synthetic type: " + clazz;
         externalType = null;
       }
     } while (externalType == null);
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java
new file mode 100644
index 0000000..b2c4269
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java
@@ -0,0 +1,202 @@
+// 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.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.OutputMode;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+
+/**
+ * Wrapper to make it easy to call D8 mode when compiling a dump file.
+ *
+ * <p>This wrapper will be added to the classpath so it *must* only refer to the public API. See
+ * {@code tools/compiledump.py}.
+ *
+ * <p>It is tempting to have this class share the D8 parser code, but such refactoring would not be
+ * valid on past version of the D8 API. Thus there is little else to do than reimplement the parts
+ * we want to support for reading dumps.
+ */
+public class CompileDumpD8 {
+
+  private static final List<String> VALID_OPTIONS =
+      Arrays.asList(
+          "--classfile",
+          "--debug",
+          "--release",
+          "--enable-missing-library-api-modeling",
+          "--android-platform-build");
+
+  private static final List<String> VALID_OPTIONS_WITH_SINGLE_OPERAND =
+      Arrays.asList(
+          "--output",
+          "--lib",
+          "--classpath",
+          "--min-api",
+          "--main-dex-rules",
+          "--main-dex-list",
+          "--main-dex-list-output",
+          "--desugared-lib",
+          "--threads");
+
+  public static void main(String[] args) throws CompilationFailedException {
+    OutputMode outputMode = OutputMode.DexIndexed;
+    Path outputPath = null;
+    Path desugaredLibJson = null;
+    CompilationMode compilationMode = CompilationMode.RELEASE;
+    List<Path> program = new ArrayList<>();
+    List<Path> library = new ArrayList<>();
+    List<Path> classpath = new ArrayList<>();
+    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)) {
+        switch (option) {
+          case "--classfile":
+            {
+              outputMode = OutputMode.ClassFile;
+              break;
+            }
+          case "--debug":
+            {
+              compilationMode = CompilationMode.DEBUG;
+              break;
+            }
+          case "--release":
+            {
+              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);
+        }
+      } else if (VALID_OPTIONS_WITH_SINGLE_OPERAND.contains(option)) {
+        String operand = args[++i];
+        switch (option) {
+          case "--output":
+            {
+              outputPath = Paths.get(operand);
+              break;
+            }
+          case "--lib":
+            {
+              library.add(Paths.get(operand));
+              break;
+            }
+          case "--classpath":
+            {
+              classpath.add(Paths.get(operand));
+              break;
+            }
+          case "--min-api":
+            {
+              minApi = Integer.parseInt(operand);
+              break;
+            }
+          case "--desugared-lib":
+            {
+              desugaredLibJson = Paths.get(operand);
+              break;
+            }
+          case "--threads":
+            {
+              threads = Integer.parseInt(operand);
+              break;
+            }
+          case "--main-dex-rules":
+            {
+              mainDexRulesFiles.add(Paths.get(operand));
+              break;
+            }
+          default:
+            throw new IllegalArgumentException("Unimplemented option: " + option);
+        }
+      } else {
+        program.add(Paths.get(option));
+      }
+    }
+    D8Command.Builder commandBuilder =
+        D8Command.builder()
+            .addProgramFiles(program)
+            .addLibraryFiles(library)
+            .addClasspathFiles(classpath)
+            .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));
+    }
+    commandBuilder.setMinApiLevel(minApi);
+    D8Command command = commandBuilder.build();
+    if (threads != -1) {
+      ExecutorService executor = Executors.newWorkStealingPool(threads);
+      try {
+        D8.run(command, executor);
+      } finally {
+        executor.shutdown();
+      }
+    } else {
+      D8.run(command);
+    }
+  }
+
+  private static Consumer<Object[]> getReflectiveBuilderMethod(
+      D8Command.Builder builder, String setter, Class<?>... parameters) {
+    try {
+      Method declaredMethod = D8Command.Builder.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) {
+    String content = "";
+
+    try {
+      content = new String(Files.readAllBytes(filePath));
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+
+    return content;
+  }
+}
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 2612127..d338a11 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1621,6 +1621,14 @@
       allowOpenInterfaces = false;
     }
 
+    public OpenClosedInterfacesOptions suppressSingleOpenInterface(ClassReference classReference) {
+      assert !allowOpenInterfaces;
+      suppressions.add(
+          (appView, valueType, openInterface) ->
+              openInterface.getTypeName().equals(classReference.getTypeName()));
+      return this;
+    }
+
     public void suppressAllOpenInterfaces() {
       assert !allowOpenInterfaces;
       suppressions.add((appView, valueType, openInterface) -> true);
@@ -1931,8 +1939,6 @@
     // minified field names instead of original field names.
     public boolean enableRecordModeling = true;
 
-    public boolean allowConflictingSyntheticTypes = false;
-
     // Flag to allow processing of resources in D8. A data resource consumer still needs to be
     // specified.
     public boolean enableD8ResourcesPassThrough = false;
diff --git a/src/main/java/com/android/tools/r8/utils/UnverifiableCfCodeDiagnostic.java b/src/main/java/com/android/tools/r8/utils/UnverifiableCfCodeDiagnostic.java
index e3aac9b..1724acc 100644
--- a/src/main/java/com/android/tools/r8/utils/UnverifiableCfCodeDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/utils/UnverifiableCfCodeDiagnostic.java
@@ -37,12 +37,13 @@
 
   @Override
   public String getDiagnosticMessage() {
-    return "Unverifiable code in `"
-        + MethodReferenceUtils.toSourceString(methodReference)
-        + "` at instruction "
-        + instructionIndex
-        + ": "
-        + message
-        + ".";
+    StringBuilder builder =
+        new StringBuilder("Unverifiable code in `")
+            .append(MethodReferenceUtils.toSourceString(methodReference))
+            .append("`");
+    if (instructionIndex >= 0) {
+      builder.append(" at instruction ").append(instructionIndex);
+    }
+    return builder.append(": ").append(message).append(".").toString();
   }
 }
diff --git a/src/test/examples/shaking1/print-mapping-dex.ref b/src/test/examples/shaking1/print-mapping-dex.ref
index 5a9504a..3635115 100644
--- a/src/test/examples/shaking1/print-mapping-dex.ref
+++ b/src/test/examples/shaking1/print-mapping-dex.ref
@@ -4,5 +4,5 @@
     3:5:void <init>(java.lang.String):13:13 -> <init>
     0:6:void main(java.lang.String[]):8:8 -> main
     7:21:void main(java.lang.String[]):9:9 -> main
-    0:19:java.lang.String method():17:17 -> a
+    0:16:java.lang.String method():17:17 -> a
     java.lang.String name -> a
diff --git a/src/test/examplesJava17/nest/NestLambda.java b/src/test/examplesJava17/nest/NestLambda.java
new file mode 100644
index 0000000..2c690b5
--- /dev/null
+++ b/src/test/examplesJava17/nest/NestLambda.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 nest;
+
+import java.util.function.Consumer;
+
+public class NestLambda {
+
+  private void print(Object o) {
+    System.out.println("printed: " + o);
+  }
+
+  Inner getInner() {
+    return new Inner();
+  }
+
+  class Inner {
+
+    void exec(Consumer<Object> consumer) {
+      consumer.accept("inner");
+    }
+
+    void execLambda() {
+      exec(NestLambda.this::print);
+    }
+  }
+
+  public static void main(String[] args) {
+    new NestLambda().getInner().execLambda();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index a7759e3..7b5d60b 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -131,7 +131,7 @@
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 4, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring"))
         .run();
   }
 
@@ -170,7 +170,7 @@
         .withMinApiLevel(AndroidApiLevel.N)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 4, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring"))
         .run();
   }
 
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 4330b97..69f2167 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -15,7 +15,6 @@
 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;
@@ -123,8 +122,8 @@
 
   public static final String R8_TEST_BUCKET = "r8-test-results";
 
-  public static final String ASM_JAR = BUILD_DIR + "deps/asm-9.2.jar";
-  public static final String ASM_UTIL_JAR = BUILD_DIR + "deps/asm-util-9.2.jar";
+  public static final String ASM_JAR = BUILD_DIR + "deps/asm-9.3.jar";
+  public static final String ASM_UTIL_JAR = BUILD_DIR + "deps/asm-util-9.3.jar";
 
   public static final Path API_SAMPLE_JAR = Paths.get("tests", "r8_api_usage_sample.jar");
 
@@ -180,8 +179,6 @@
   public static final Path DEPS = Paths.get(LIBS_DIR, "deps_all.jar");
   public static final Path R8_RETRACE_JAR = Paths.get(LIBS_DIR, "r8retrace.jar");
 
-  public static final Path DESUGAR_LIB_CONVERSIONS =
-      Paths.get(LIBS_DIR, "library_desugar_conversions.zip");
   public static final String DESUGARED_LIB_RELEASES_DIR =
       OPEN_JDK_DIR + "desugar_jdk_libs_releases/";
   public static final Path DESUGARED_JDK_8_LIB_JAR =
@@ -189,8 +186,10 @@
   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 getDesugarLibConversions(CustomConversionVersion legacy) {
+    return legacy == CustomConversionVersion.LEGACY
+        ? Paths.get(LIBS_DIR, "library_desugar_conversions_legacy.jar")
+        : Paths.get(LIBS_DIR, "library_desugar_conversions.jar");
   }
 
   public static Path getUndesugaredJdk11LibJarForTesting() {
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 f7ce233..b26a1cf 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
@@ -100,11 +100,6 @@
           .setMode(mode)
           .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
           .addKeepRuleFiles(MAIN_KEEP)
-          .addOptionsModification(
-              options ->
-                  options
-                      .getOpenClosedInterfacesOptions()
-                      .suppressZipFileAssignmentsToJavaLangAutoCloseable())
           .compile()
           .apply(c -> FileUtils.writeTextFile(map, c.getProguardMap()))
           .writeToZip(jar);
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/StackMapVerificationNoFrameForHandlerTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/StackMapVerificationNoFrameForHandlerTest.java
index c3c79fb..49d84b1 100644
--- a/src/test/java/com/android/tools/r8/cf/stackmap/StackMapVerificationNoFrameForHandlerTest.java
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/StackMapVerificationNoFrameForHandlerTest.java
@@ -95,7 +95,11 @@
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
         .allowDiagnosticWarningMessages(!includeFrameInHandler)
-        .addOptionsModification(options -> options.testing.readInputStackMaps = true)
+        .addOptionsModification(
+            options -> {
+              options.getCfCodeAnalysisOptions().setEnableUnverifiableCodeReporting(false);
+              options.testing.readInputStackMaps = true;
+            })
         .compileWithExpectedDiagnostics(this::verifyWarningsRegardingStackMap)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
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 efee088..5a05e80 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
@@ -54,7 +54,7 @@
             "JDK8_CL",
             ImmutableSet.of(
                 DESUGARED_JDK_8_LIB_JAR,
-                ToolHelper.getConvertedDesugaredLibConversions(LEGACY),
+                ToolHelper.getDesugarLibConversions(LEGACY),
                 ToolHelper.getCoreLambdaStubs()),
             JDK8.getSpecification(),
             ImmutableSet.of(ToolHelper.getAndroidJar(AndroidApiLevel.O)),
@@ -65,7 +65,7 @@
             "JDK11_CL",
             ImmutableSet.of(
                 ToolHelper.getUndesugaredJdk11LibJarForTesting(),
-                ToolHelper.getConvertedDesugaredLibConversions(LATEST),
+                ToolHelper.getDesugarLibConversions(LATEST),
                 ToolHelper.getCoreLambdaStubs()),
             JDK11.getSpecification(),
             ImmutableSet.of(ToolHelper.getAndroidJar(AndroidApiLevel.R)),
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
deleted file mode 100644
index 746a1d2..0000000
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/ConversionConverter.java
+++ /dev/null
@@ -1,179 +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.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/test/LibraryDesugaringSpecification.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
index 143a52d..b3cc9ab 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
@@ -168,7 +168,7 @@
       CustomConversionVersion legacy) {
     this(
         name,
-        ImmutableSet.of(desugarJdkLibs, ToolHelper.getConvertedDesugaredLibConversions(legacy)),
+        ImmutableSet.of(desugarJdkLibs, ToolHelper.getDesugarLibConversions(legacy)),
         Paths.get("src/library_desugar/" + specificationPath),
         ImmutableSet.of(ToolHelper.getAndroidJar(androidJarLevel)),
         descriptor,
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaNamingConflictTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaNamingConflictTest.java
index 1a6e364..e862e2e 100644
--- a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaNamingConflictTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaNamingConflictTest.java
@@ -58,7 +58,6 @@
         .addProgramClassFileData(getConflictingNameClass())
         .addProgramClassFileData(getTransformedMainClass())
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(o -> o.testing.allowConflictingSyntheticTypes = true)
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED);
   }
@@ -70,7 +69,6 @@
         .addProgramClassFileData(getConflictingNameClass())
         .addProgramClassFileData(getTransformedMainClass())
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(o -> o.testing.allowConflictingSyntheticTypes = true)
         .addKeepMainRule(TestClass.class)
         // Ensure that R8 cannot remove or rename the conflicting name.
         .addKeepClassAndMembersRules(CONFLICTING_NAME.getTypeName())
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestLambdaJava17Test.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestLambdaJava17Test.java
new file mode 100644
index 0000000..94fbe71
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestLambdaJava17Test.java
@@ -0,0 +1,81 @@
+// 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.nestaccesscontrol;
+
+import static com.android.tools.r8.utils.AndroidApiLevel.B;
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NestLambdaJava17Test extends TestBase {
+
+  public NestLambdaJava17Test(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private static final Path JDK17_JAR =
+      Paths.get(ToolHelper.TESTS_BUILD_DIR, "examplesJava17").resolve("nest" + JAR_EXTENSION);
+  private static final String MAIN = "nest.NestLambda";
+  private static final String EXPECTED_RESULT = "printed: inner";
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
+        // The test requires the java.util.function. package.
+        .withDexRuntimesStartingFromIncluding(Version.V7_0_0)
+        .withApiLevel(AndroidApiLevel.N)
+        .enableApiLevelsForCf()
+        .build();
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    Assume.assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramFiles(JDK17_JAR)
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testJavaD8() throws Exception {
+    testForDesugaring(parameters)
+        .addProgramFiles(JDK17_JAR)
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Assume.assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().equals(B));
+    testForR8(parameters.getBackend())
+        .addProgramFiles(JDK17_JAR)
+        .applyIf(
+            parameters.isCfRuntime(),
+            // Alternatively we need to pass Jdk17 as library.
+            b -> b.addKeepRules("-dontwarn java.lang.invoke.StringConcatFactory"))
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(MAIN)
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines(EXPECTED_RESULT);
+  }
+}
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 ddd9bd4..d7878b9 100644
--- a/src/test/java/com/android/tools/r8/internal/ClankDepsTest.java
+++ b/src/test/java/com/android/tools/r8/internal/ClankDepsTest.java
@@ -49,6 +49,8 @@
         .addDontWarn("dalvik.system.VMStack")
         .addDontWarn("zzz.com.facebook.litho.R$id")
         .addDontWarn("com.google.android.libraries.elements.R$id")
+        .addOptionsModification(
+            options -> options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
         .allowUnusedDontWarnPatterns()
         .allowUnusedProguardConfigurationRules()
         .allowUnnecessaryDontWarnWildcards()
diff --git a/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java b/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
index 128f780..eef3dcf 100644
--- a/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
+++ b/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
@@ -93,6 +93,8 @@
         .addClasspathFiles(outDirectory.resolve("classpath.jar"))
         .addLibraryFiles(outDirectory.resolve("library.jar"))
         .addKeepRuleFiles(outDirectory.resolve("proguard.config"))
+        .addOptionsModification(
+            options -> options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
         .setMinApi(AndroidApiLevel.M)
         .allowDiagnosticMessages()
         .allowUnnecessaryDontWarnWildcards()
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java
index e654b89..ce06fd4 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java
@@ -5,6 +5,9 @@
 package com.android.tools.r8.ir.analysis.type;
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticException;
+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.CoreMatchers.equalTo;
 import static org.junit.Assert.assertFalse;
@@ -16,8 +19,11 @@
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic;
 import com.google.common.base.Throwables;
 import java.util.List;
 import org.junit.Test;
@@ -75,16 +81,27 @@
               .setMinApi(parameters.getApiLevel())
               .compileWithExpectedDiagnostics(
                   diagnostics -> {
-                    if (!allowTypeErrors) {
+                    if (allowTypeErrors) {
+                      MethodReference mainMethodReference =
+                          MethodReferenceUtils.mainMethod(TestClass.class);
+                      diagnostics.assertWarningsMatch(
+                          allOf(
+                              diagnosticType(UnverifiableCfCodeDiagnostic.class),
+                              diagnosticMessage(
+                                  containsString(
+                                      "Unverifiable code in `"
+                                          + MethodReferenceUtils.toSourceString(mainMethodReference)
+                                          + "`"))),
+                          diagnosticMessage(
+                              equalTo(
+                                  "The method `"
+                                      + MethodReferenceUtils.toSourceString(mainMethodReference)
+                                      + "` does not type check and will be assumed to "
+                                      + "be unreachable.")));
+                    } else {
                       diagnostics.assertErrorThatMatches(diagnosticException(AssertionError.class));
                     }
-                  })
-              .assertAllWarningMessagesMatch(
-                  equalTo(
-                      "The method `void "
-                          + TestClass.class.getTypeName()
-                          + ".main(java.lang.String[])` does not type check and will be assumed to"
-                          + " be unreachable."));
+                  });
 
       // Compilation fails unless type errors are allowed.
       assertTrue(allowTypeErrors);
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index 4f8c88c..3bf42fa 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -449,10 +449,8 @@
   @Test
   public void joinPrimitiveAndClassArrays() {
     assertEquals(
-        array(3, factory.objectType),
-        join(
-            array(4, factory.intType),
-            array(3, factory.stringType)));
+        array(array(array(element(factory.objectType, maybeNull(), factory.serializableType)))),
+        join(array(4, factory.intType), array(3, factory.stringType)));
   }
 
   @Test
@@ -476,10 +474,8 @@
   @Test
   public void joinDistinctNestingClassArrays() {
     assertEquals(
-        array(3, factory.objectType),
-        join(
-            array(3, factory.stringType),
-            array(4, factory.stringType)));
+        array(array(array(element(factory.objectType, maybeNull(), factory.serializableType)))),
+        join(array(3, factory.stringType), array(4, factory.stringType)));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/StringCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/StringCanonicalizationTest.java
index a8b1557..3aa8509 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/StringCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/StringCanonicalizationTest.java
@@ -6,7 +6,6 @@
 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.Assume.assumeTrue;
 
 import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.NeverInline;
@@ -159,7 +158,7 @@
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
     // CF should not canonicalize strings or lower them. See (r8g/30163) and (r8g/30320).
-    return getTestParameters().withDexRuntimes().build();
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -202,24 +201,25 @@
   }
 
   @Test
-  public void testD8() throws Exception {
-    assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
+  public void testR8Debug() throws Exception {
+    D8TestCompileResult result =
+        testForD8()
+            .debug()
+            .addProgramClassesAndInnerClasses(MAIN)
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+    test(result, 2, 1, 1, 1, 1);
+  }
 
+  @Test
+  public void testD8Release() throws Exception {
     D8TestCompileResult result =
         testForD8()
             .release()
             .addProgramClassesAndInnerClasses(MAIN)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .compile();
-    test(result, 1, 1, 1, 1, 1);
-
-    result =
-        testForD8()
-            .debug()
-            .addProgramClassesAndInnerClasses(MAIN)
-            .setMinApi(parameters.getRuntime())
-            .compile();
-    test(result, 2, 1, 1, 1, 1);
+    test(result, 1, 1, 1, 1, 0);
   }
 
   @Test
@@ -230,9 +230,8 @@
             .enableProguardTestOptions()
             .enableInliningAnnotations()
             .addKeepMainRule(MessageLoader.class)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .compile();
-    test(result, 1, 1, 1, 1, 1);
+    test(result, 1, 1, 1, 1, 0);
   }
-
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java
index 1107d60..1e43777 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java
@@ -8,8 +8,10 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.io.IOException;
@@ -53,15 +55,15 @@
         .addKeepMainRule(Main.class)
         // Keep get() to prevent that we optimize it into having static return type A.
         .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get(...); }")
-        .addNoVerticalClassMergingAnnotations()
+        .addOptionsModification(
+            options ->
+                options
+                    .getOpenClosedInterfacesOptions()
+                    .suppressSingleOpenInterface(Reference.classFromClass(I.class)))
         .applyIf(
-            !enableVerticalClassMerging,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(
-                        options ->
-                            options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
-                    .enableNoVerticalClassMergingAnnotations())
+            enableVerticalClassMerging,
+            R8TestBuilder::addNoVerticalClassMergingAnnotations,
+            R8TestBuilder::enableNoVerticalClassMergingAnnotations)
         .addVerticallyMergedClassesInspector(
             inspector -> {
               if (enableVerticalClassMerging) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/InstancePutToInterfaceWithObjectMergingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/InstancePutToInterfaceWithObjectMergingTest.java
index d294a77..aeca839 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/InstancePutToInterfaceWithObjectMergingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/InstancePutToInterfaceWithObjectMergingTest.java
@@ -8,8 +8,10 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.io.IOException;
@@ -53,15 +55,15 @@
         .addKeepMainRule(Main.class)
         // Keep get() to prevent that we optimize it into having static return type A.
         .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get(...); }")
-        .addNoVerticalClassMergingAnnotations()
+        .addOptionsModification(
+            options ->
+                options
+                    .getOpenClosedInterfacesOptions()
+                    .suppressSingleOpenInterface(Reference.classFromClass(I.class)))
         .applyIf(
-            !enableVerticalClassMerging,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(
-                        options ->
-                            options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
-                    .enableNoVerticalClassMergingAnnotations())
+            enableVerticalClassMerging,
+            R8TestBuilder::addNoVerticalClassMergingAnnotations,
+            R8TestBuilder::enableNoVerticalClassMergingAnnotations)
         .addVerticallyMergedClassesInspector(
             inspector -> {
               if (enableVerticalClassMerging) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ReturnObjectAsInterfaceMergingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ReturnObjectAsInterfaceMergingTest.java
index 87d60b4..f541549 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ReturnObjectAsInterfaceMergingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ReturnObjectAsInterfaceMergingTest.java
@@ -8,8 +8,11 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ir.optimize.classmerger.vertical.StaticPutToInterfaceWithObjectMergingTest.I;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.io.IOException;
@@ -53,15 +56,15 @@
         .addKeepMainRule(Main.class)
         // Keep get() to prevent that we optimize it into having static return type A.
         .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get(...); }")
-        .addNoVerticalClassMergingAnnotations()
+        .addOptionsModification(
+            options ->
+                options
+                    .getOpenClosedInterfacesOptions()
+                    .suppressSingleOpenInterface(Reference.classFromClass(I.class)))
         .applyIf(
-            !enableVerticalClassMerging,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(
-                        options ->
-                            options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
-                    .enableNoVerticalClassMergingAnnotations())
+            enableVerticalClassMerging,
+            R8TestBuilder::addNoVerticalClassMergingAnnotations,
+            R8TestBuilder::enableNoVerticalClassMergingAnnotations)
         .addVerticallyMergedClassesInspector(
             inspector -> {
               if (enableVerticalClassMerging) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/StaticPutToInterfaceWithObjectMergingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/StaticPutToInterfaceWithObjectMergingTest.java
index 072b819..bfc7276 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/StaticPutToInterfaceWithObjectMergingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/StaticPutToInterfaceWithObjectMergingTest.java
@@ -8,8 +8,10 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.io.IOException;
@@ -53,15 +55,15 @@
         .addKeepMainRule(Main.class)
         // Keep get() to prevent that we optimize it into having static return type A.
         .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get(...); }")
-        .addNoVerticalClassMergingAnnotations()
+        .addOptionsModification(
+            options ->
+                options
+                    .getOpenClosedInterfacesOptions()
+                    .suppressSingleOpenInterface(Reference.classFromClass(I.class)))
         .applyIf(
-            !enableVerticalClassMerging,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(
-                        options ->
-                            options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
-                    .enableNoVerticalClassMergingAnnotations())
+            enableVerticalClassMerging,
+            R8TestBuilder::addNoVerticalClassMergingAnnotations,
+            R8TestBuilder::enableNoVerticalClassMergingAnnotations)
         .addVerticallyMergedClassesInspector(
             inspector -> {
               if (enableVerticalClassMerging) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InterfaceInvokeWithObjectReceiverInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InterfaceInvokeWithObjectReceiverInliningTest.java
index d74eb4b..3aaa789 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InterfaceInvokeWithObjectReceiverInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InterfaceInvokeWithObjectReceiverInliningTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -62,11 +63,19 @@
         .addKeepMainRule(Main.class)
         // Keep get() to prevent that we optimize it into having static return type A.
         .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get(...); }")
-        .addInliningAnnotations()
-        .addNoVerticalClassMergingAnnotations()
-        .applyIf(!enableInlining, R8TestBuilder::enableInliningAnnotations)
+        .addOptionsModification(
+            options ->
+                options
+                    .getOpenClosedInterfacesOptions()
+                    .suppressSingleOpenInterface(Reference.classFromClass(I.class)))
         .applyIf(
-            !enableVerticalClassMerging, R8TestBuilder::enableNoVerticalClassMergingAnnotations)
+            enableInlining,
+            R8TestBuilder::addInliningAnnotations,
+            R8TestBuilder::enableInliningAnnotations)
+        .applyIf(
+            enableVerticalClassMerging,
+            R8TestBuilder::addNoVerticalClassMergingAnnotations,
+            R8TestBuilder::enableNoVerticalClassMergingAnnotations)
         .addVerticallyMergedClassesInspector(
             inspector -> {
               if (enableVerticalClassMerging) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/StaticInvokeWithMultipleObjectsForInterfaceTypesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/StaticInvokeWithMultipleObjectsForInterfaceTypesTest.java
index 4dc19d3..7bc991e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/StaticInvokeWithMultipleObjectsForInterfaceTypesTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/StaticInvokeWithMultipleObjectsForInterfaceTypesTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.io.IOException;
@@ -62,16 +63,20 @@
         // Keep getA() and getB() to prevent that we optimize it into having static return type A/B.
         .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get?(...); }")
         .addInliningAnnotations()
-        .addNoVerticalClassMergingAnnotations()
-        .applyIf(!enableInlining, R8TestBuilder::enableInliningAnnotations)
+        .addOptionsModification(
+            options ->
+                options
+                    .getOpenClosedInterfacesOptions()
+                    .suppressSingleOpenInterface(Reference.classFromClass(I.class))
+                    .suppressSingleOpenInterface(Reference.classFromClass(J.class)))
         .applyIf(
-            !enableVerticalClassMerging,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(
-                        options ->
-                            options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
-                    .enableNoVerticalClassMergingAnnotations())
+            enableInlining,
+            R8TestBuilder::addInliningAnnotations,
+            R8TestBuilder::enableInliningAnnotations)
+        .applyIf(
+            enableVerticalClassMerging,
+            R8TestBuilder::addNoVerticalClassMergingAnnotations,
+            R8TestBuilder::enableNoVerticalClassMergingAnnotations)
         .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceCheckCastTest.java b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceCheckCastTest.java
index e8d4e5f..c756f8c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceCheckCastTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceCheckCastTest.java
@@ -41,7 +41,7 @@
         .addProgramClasses(getProgramClasses())
         .addProgramClassFileData(getTransformedMainClass())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines(false));
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   @Test
@@ -52,7 +52,7 @@
         .addProgramClassFileData(getTransformedMainClass())
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines(false));
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   @Test
@@ -68,7 +68,7 @@
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines(true));
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   private List<Class<?>> getProgramClasses() {
@@ -102,11 +102,7 @@
         .transform();
   }
 
-  private List<String> getExpectedOutputLines(boolean isR8) {
-    if (isR8) {
-      // TODO(b/214496607): R8 should not optimize the check-cast instruction since I is open.
-      return ImmutableList.of("OK", "OK");
-    }
+  private List<String> getExpectedOutputLines() {
     if (parameters.isDexRuntime()
         && parameters
             .getDexRuntimeVersion()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInliningTest.java
index e2b4a29..4eaa6a4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInliningTest.java
@@ -71,8 +71,7 @@
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        // TODO(b/214496607): Should succeed with the expected output.
-        .assertFailureWithErrorThatThrows(ClassCastException.class);
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   private List<Class<?>> getProgramClasses() {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInstanceofTest.java b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInstanceofTest.java
index 5a57dc4..dac3848 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInstanceofTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInstanceofTest.java
@@ -41,7 +41,7 @@
         .addProgramClasses(getProgramClasses())
         .addProgramClassFileData(getTransformedMainClass())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines(false));
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   @Test
@@ -52,7 +52,7 @@
         .addProgramClassFileData(getTransformedMainClass())
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines(false));
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   @Test
@@ -68,7 +68,7 @@
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines(true));
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   private List<Class<?>> getProgramClasses() {
@@ -95,11 +95,7 @@
         .transform();
   }
 
-  private List<String> getExpectedOutputLines(boolean isR8) {
-    if (isR8) {
-      // TODO(b/214496607): R8 should not optimize the instanceof instruction since I is open.
-      return ImmutableList.of("true", "true");
-    }
+  private List<String> getExpectedOutputLines() {
     if (parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isEqualTo(Version.V7_0_0)) {
       return ImmutableList.of("true", "true");
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceInstanceofTest.java b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceInstanceofTest.java
index 68c055d..07b765f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceInstanceofTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceInstanceofTest.java
@@ -40,7 +40,7 @@
         .addProgramClasses(getProgramClasses())
         .addProgramClassFileData(getTransformedMainClass())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines(false));
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   @Test
@@ -51,7 +51,7 @@
         .addProgramClassFileData(getTransformedMainClass())
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines(false));
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   @Test
@@ -65,7 +65,7 @@
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines(true));
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   private List<Class<?>> getProgramClasses() {
@@ -92,11 +92,7 @@
         .transform();
   }
 
-  private List<String> getExpectedOutputLines(boolean isR8) {
-    if (isR8) {
-      // TODO(b/214496607): R8 should not optimize the instanceof instruction since I is open.
-      return ImmutableList.of("true");
-    }
+  private List<String> getExpectedOutputLines() {
     if (parameters.isDexRuntime()) {
       if (parameters.getDexRuntimeVersion().isEqualTo(Version.V7_0_0)
           || parameters.getDexRuntimeVersion().isEqualTo(Version.V13_0_0)) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b111893131/B111893131.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b111893131/B111893131.java
index 7ba5906..7873c68 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b111893131/B111893131.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b111893131/B111893131.java
@@ -81,6 +81,7 @@
               // To trigger outliner, set # of expected outline candidate as threshold.
               options.outline.threshold = 2;
               options.inlinerOptions().enableInlining = false;
+              options.enableStringConcatenationOptimization = false;
             });
     ProcessResult result = runOnArtRaw(app, TestClass.class);
     assertEquals(result.toString(), 0, result.exitCode);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisSmaliTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisSmaliTest.java
deleted file mode 100644
index cc582ea..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisSmaliTest.java
+++ /dev/null
@@ -1,190 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.optimize.string;
-
-import static com.android.tools.r8.ir.optimize.string.StringBuilderOptimizerAnalysisTest.checkBuilderState;
-import static com.android.tools.r8.ir.optimize.string.StringBuilderOptimizerAnalysisTest.checkOptimizerStates;
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ir.analysis.AnalysisTestBase;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.optimize.string.StringBuilderOptimizer.BuilderState;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.smali.SmaliBuilder;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.ImmutableList;
-import java.util.Map;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class StringBuilderOptimizerAnalysisSmaliTest extends AnalysisTestBase {
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
-  }
-
-  public StringBuilderOptimizerAnalysisSmaliTest(TestParameters parameters) throws Exception {
-    super(parameters, buildApp(), "TestClass");
-  }
-
-  private static AndroidApp buildApp() throws Exception {
-    SmaliBuilder smaliBuilder = new SmaliBuilder("TestClass");
-
-    {
-      // IR of StringConcatenationTestClass#phiAtInit at DVM older than or equal to 5.1.1
-      String code =
-          StringUtils.lines(
-              "invoke-static {}, Ljava/lang/System;->currentTimeMillis()J",
-              "move-result-wide v0",
-              "const-wide/16 v2, 0",
-              "cmp-long v4, v0, v2",
-              // new-instance is hoisted, but <init> calls are still at each branch
-              "new-instance v0, Ljava/lang/StringBuilder;",
-              "if-lez v4, :cond",
-              "const-string v1, \"Hello\"",
-              "invoke-direct {v0, v1}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V",
-              "goto :merge",
-              ":cond",
-              "const-string v1, \"Hi\"",
-              "invoke-direct {v0, v1}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V",
-              "goto :merge",
-              ":merge",
-              "const-string v1, \", R8\"",
-              "invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append"
-                  + "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
-              "sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream;",
-              "invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;",
-              "move-result-object v1",
-              "invoke-virtual {v2, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
-              "return-void");
-      smaliBuilder.addStaticMethod(
-          "void", "phiAtInit_5_1_1", ImmutableList.of(), 5, code);
-    }
-
-    {
-      // Compiled StringConcatenationTestClass#phiAtInit and modified two new-instance instructions
-      // are flown to the same <init> call.
-      String code =
-          StringUtils.lines(
-              "invoke-static {}, Ljava/lang/System;->currentTimeMillis()J",
-              "move-result-wide v0",
-              "const-wide/16 v2, 0",
-              "cmp-long v4, v0, v2",
-              "if-lez v4, :cond",
-              "new-instance v0, Ljava/lang/StringBuilder;",
-              "const-string v1, \"Hello\"",
-              "goto :merge",
-              ":cond",
-              "new-instance v0, Ljava/lang/StringBuilder;",
-              "const-string v1, \"Hi\"",
-              "goto :merge",
-              ":merge",
-              // Two separate new-instance instructions are flown to the same <init>.
-              "invoke-direct {v0, v1}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V",
-              "const-string v1, \", R8\"",
-              "invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append"
-                  + "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
-              "sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream;",
-              "invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;",
-              "move-result-object v1",
-              "invoke-virtual {v2, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
-              "return-void");
-      smaliBuilder.addStaticMethod(
-          "void", "phiWithDifferentNewInstance", ImmutableList.of(), 5, code);
-    }
-
-    {
-      // IR of StringConcatenationTestClass#phiAtInit at DVM/ART newer than 5.1.1
-      String code =
-          StringUtils.lines(
-              "invoke-static {}, Ljava/lang/System;->currentTimeMillis()J",
-              "move-result-wide v0",
-              "const-wide/16 v2, 0",
-              "cmp-long v4, v0, v2",
-              // new-instance and <init> are in each branch.
-              "if-lez v4, :cond",
-              "new-instance v0, Ljava/lang/StringBuilder;",
-              "const-string v1, \"Hello\"",
-              "invoke-direct {v0, v1}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V",
-              "goto :merge",
-              ":cond",
-              "new-instance v0, Ljava/lang/StringBuilder;",
-              "const-string v1, \"Hi\"",
-              "invoke-direct {v0, v1}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V",
-              "goto :merge",
-              ":merge",
-              "const-string v1, \", R8\"",
-              "invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append"
-                  + "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
-              "sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream;",
-              "invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;",
-              "move-result-object v1",
-              "invoke-virtual {v2, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
-              "return-void");
-      smaliBuilder.addStaticMethod(
-          "void", "phiAtInit", ImmutableList.of(), 5, code);
-    }
-
-    return AndroidApp.builder()
-        .addDexProgramData(smaliBuilder.compile(), Origin.unknown())
-        .addLibraryFile(ToolHelper.getMostRecentAndroidJar())
-        .build();
-  }
-
-  @Test
-  public void testPhiAtInit_5_1_1() {
-    buildAndCheckIR(
-        "phiAtInit_5_1_1",
-        checkOptimizerStates(appView, optimizer -> {
-          assertEquals(1, optimizer.analysis.builderStates.size());
-          for (Value builder : optimizer.analysis.builderStates.keySet()) {
-            Map<Instruction, BuilderState> perBuilderState =
-                optimizer.analysis.builderStates.get(builder);
-            checkBuilderState(optimizer, perBuilderState, null, true);
-          }
-          assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
-        }));
-  }
-
-  @Test
-  public void testPhiWithDifferentNewInstance() {
-    buildAndCheckIR(
-        "phiWithDifferentNewInstance",
-        checkOptimizerStates(
-            appView,
-            optimizer -> {
-              assertEquals(0, optimizer.analysis.builderStates.size());
-              for (Value builder : optimizer.analysis.builderStates.keySet()) {
-                Map<Instruction, BuilderState> perBuilderState =
-                    optimizer.analysis.builderStates.get(builder);
-                checkBuilderState(optimizer, perBuilderState, null, false);
-              }
-              assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
-            }));
-  }
-
-  @Test
-  public void testPhiAtInit() {
-    buildAndCheckIR(
-        "phiAtInit",
-        checkOptimizerStates(
-            appView,
-            optimizer -> {
-              assertEquals(0, optimizer.analysis.builderStates.size());
-              for (Value builder : optimizer.analysis.builderStates.keySet()) {
-                Map<Instruction, BuilderState> perBuilderState =
-                    optimizer.analysis.builderStates.get(builder);
-                checkBuilderState(optimizer, perBuilderState, null, false);
-              }
-              assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
-            }));
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
deleted file mode 100644
index 2a81ef0..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
+++ /dev/null
@@ -1,401 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.optimize.string;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.ir.analysis.AnalysisTestBase;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.optimize.string.StringBuilderOptimizer.BuilderState;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.InternalOptions;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Consumer;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class StringBuilderOptimizerAnalysisTest extends AnalysisTestBase {
-
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
-  }
-
-  public StringBuilderOptimizerAnalysisTest(TestParameters parameters) throws Exception {
-    super(
-        parameters,
-        StringConcatenationTestClass.class.getTypeName(),
-        StringConcatenationTestClass.class);
-  }
-
-  @Override
-  public void configure(InternalOptions options) {}
-
-  @Test
-  public void testUnusedBuilder() {
-    buildAndCheckIR(
-        "unusedBuilder",
-        code -> assertTrue(code.streamInstructions().allMatch(Instruction::isReturn)));
-  }
-
-  @Test
-  public void testTrivialSequence() {
-    buildAndCheckIR(
-        "trivialSequence",
-        checkOptimizerStates(appView, optimizer -> {
-          assertEquals(1, optimizer.analysis.builderStates.size());
-          for (Value builder : optimizer.analysis.builderStates.keySet()) {
-            Map<Instruction, BuilderState> perBuilderState =
-                optimizer.analysis.builderStates.get(builder);
-            checkBuilderState(optimizer, perBuilderState, "xyz", true);
-          }
-          assertEquals(1, optimizer.analysis.simplifiedBuilders.size());
-        }));
-  }
-
-  @Test
-  public void testBuilderWithInitialValue() {
-    buildAndCheckIR(
-        "builderWithInitialValue",
-        checkOptimizerStates(appView, optimizer -> {
-          assertEquals(1, optimizer.analysis.builderStates.size());
-          for (Value builder : optimizer.analysis.builderStates.keySet()) {
-            Map<Instruction, BuilderState> perBuilderState =
-                optimizer.analysis.builderStates.get(builder);
-            checkBuilderState(optimizer, perBuilderState, "Hello,R8", true);
-          }
-          assertEquals(1, optimizer.analysis.simplifiedBuilders.size());
-        }));
-  }
-
-  @Test
-  public void testBuilderWithCapacity() {
-    buildAndCheckIR(
-        "builderWithCapacity",
-        checkOptimizerStates(appView, optimizer -> {
-          assertEquals(1, optimizer.analysis.builderStates.size());
-          for (Value builder : optimizer.analysis.builderStates.keySet()) {
-            Map<Instruction, BuilderState> perBuilderState =
-                optimizer.analysis.builderStates.get(builder);
-            checkBuilderState(optimizer, perBuilderState, "42", true);
-          }
-          assertEquals(1, optimizer.analysis.simplifiedBuilders.size());
-        }));
-  }
-
-  @Test
-  public void testNonStringArgs() {
-    buildAndCheckIR(
-        "nonStringArgs",
-        checkOptimizerStates(appView, optimizer -> {
-          assertEquals(1, optimizer.analysis.builderStates.size());
-          for (Value builder : optimizer.analysis.builderStates.keySet()) {
-            Map<Instruction, BuilderState> perBuilderState =
-                optimizer.analysis.builderStates.get(builder);
-            checkBuilderState(optimizer, perBuilderState, "42", true);
-          }
-          assertEquals(1, optimizer.analysis.simplifiedBuilders.size());
-        }));
-  }
-
-  @Test
-  public void testTypeConversion() {
-    buildAndCheckIR(
-        "typeConversion",
-        checkOptimizerStates(appView, optimizer -> {
-          assertEquals(1, optimizer.analysis.builderStates.size());
-          for (Value builder : optimizer.analysis.builderStates.keySet()) {
-            Map<Instruction, BuilderState> perBuilderState =
-                optimizer.analysis.builderStates.get(builder);
-            checkBuilderState(optimizer, perBuilderState, "0.14 0 false null", true);
-          }
-          assertEquals(1, optimizer.analysis.simplifiedBuilders.size());
-        }));
-  }
-
-  @Test
-  public void testTypeConversion_withPhis() {
-    buildAndCheckIR(
-        "typeConversion_withPhis",
-        checkOptimizerStates(appView, optimizer -> {
-          assertEquals(1, optimizer.analysis.builderStates.size());
-          for (Value builder : optimizer.analysis.builderStates.keySet()) {
-            Map<Instruction, BuilderState> perBuilderState =
-                optimizer.analysis.builderStates.get(builder);
-            checkBuilderState(optimizer, perBuilderState, null, true);
-          }
-          assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
-        }));
-  }
-
-  @Ignore("TODO(b/113859361): passed to another builder should be an eligible case.")
-  @Test
-  public void testNestedBuilders_appendBuilderItself() {
-    buildAndCheckIR(
-        "nestedBuilders_appendBuilderItself",
-        checkOptimizerStates(appView, optimizer -> {
-          assertEquals(1, optimizer.analysis.builderStates.size());
-          for (Value builder : optimizer.analysis.builderStates.keySet()) {
-            Map<Instruction, BuilderState> perBuilderState =
-                optimizer.analysis.builderStates.get(builder);
-            checkBuilderState(optimizer, perBuilderState, "Hello,R8", true);
-          }
-          assertEquals(1, optimizer.analysis.simplifiedBuilders.size());
-          // TODO(b/113859361): check # of merged builders.
-          // assertEquals(2, optimizer.analysis.mergedBuilders.size());
-        }));
-  }
-
-  @Ignore("TODO(b/113859361): merge builder.")
-  @Test
-  public void testNestedBuilders_appendBuilderResult() {
-    buildAndCheckIR(
-        "nestedBuilders_appendBuilderResult",
-        checkOptimizerStates(appView, optimizer -> {
-          assertEquals(1, optimizer.analysis.builderStates.size());
-          for (Value builder : optimizer.analysis.builderStates.keySet()) {
-            Map<Instruction, BuilderState> perBuilderState =
-                optimizer.analysis.builderStates.get(builder);
-            checkBuilderState(optimizer, perBuilderState, "Hello,R8", true);
-          }
-          assertEquals(1, optimizer.analysis.simplifiedBuilders.size());
-          // TODO(b/113859361): check # of merged builders.
-          // assertEquals(2, optimizer.analysis.mergedBuilders.size());
-        }));
-  }
-
-  @Ignore("TODO(b/113859361): merge builder.")
-  @Test
-  public void testNestedBuilders_conditional() {
-    buildAndCheckIR(
-        "nestedBuilders_conditional",
-        checkOptimizerStates(appView, optimizer -> {
-          assertEquals(1, optimizer.analysis.builderStates.size());
-          for (Value builder : optimizer.analysis.builderStates.keySet()) {
-            Map<Instruction, BuilderState> perBuilderState =
-                optimizer.analysis.builderStates.get(builder);
-            checkBuilderState(optimizer, perBuilderState, null, true);
-          }
-          assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
-          // TODO(b/113859361): check # of merged builders.
-          // assertEquals(3, optimizer.analysis.mergedBuilders.size());
-        }));
-  }
-
-  @Ignore("TODO(b/113859361): merge builder.")
-  @Test
-  public void testConcatenatedBuilders_init() {
-    buildAndCheckIR(
-        "concatenatedBuilders_init",
-        checkOptimizerStates(appView, optimizer -> {
-          assertEquals(1, optimizer.analysis.builderStates.size());
-          for (Value builder : optimizer.analysis.builderStates.keySet()) {
-            Map<Instruction, BuilderState> perBuilderState =
-                optimizer.analysis.builderStates.get(builder);
-            checkBuilderState(optimizer, perBuilderState, "Hello,R8", true);
-          }
-          assertEquals(1, optimizer.analysis.simplifiedBuilders.size());
-          // TODO(b/113859361): check # of merged builders.
-          // assertEquals(2, optimizer.analysis.mergedBuilders.size());
-        }));
-  }
-
-  @Ignore("TODO(b/113859361): merge builder.")
-  @Test
-  public void testConcatenatedBuilders_append() {
-    buildAndCheckIR(
-        "concatenatedBuilders_append",
-        checkOptimizerStates(appView, optimizer -> {
-          assertEquals(1, optimizer.analysis.builderStates.size());
-          for (Value builder : optimizer.analysis.builderStates.keySet()) {
-            Map<Instruction, BuilderState> perBuilderState =
-                optimizer.analysis.builderStates.get(builder);
-            checkBuilderState(optimizer, perBuilderState, "Hello,R8", true);
-          }
-          assertEquals(1, optimizer.analysis.simplifiedBuilders.size());
-          // TODO(b/113859361): check # of merged builders.
-          // assertEquals(2, optimizer.analysis.mergedBuilders.size());
-        }));
-  }
-
-  @Ignore("TODO(b/113859361): merge builder.")
-  @Test
-  public void testConcatenatedBuilders_conditional() {
-    final Set<String> expectedConstStrings = new HashSet<>();
-    expectedConstStrings.add("Hello,R8");
-    expectedConstStrings.add("D8");
-    buildAndCheckIR(
-        "concatenatedBuilders_conditional",
-        checkOptimizerStates(appView, optimizer -> {
-          assertEquals(2, optimizer.analysis.builderStates.size());
-          for (Value builder : optimizer.analysis.builderStates.keySet()) {
-            Map<Instruction, BuilderState> perBuilderState =
-                optimizer.analysis.builderStates.get(builder);
-            checkBuilderStates(optimizer, perBuilderState, expectedConstStrings, true);
-          }
-          assertEquals(2, optimizer.analysis.simplifiedBuilders.size());
-          // TODO(b/113859361): check # of merged builders.
-          // assertEquals(2, optimizer.analysis.mergedBuilders.size());
-        }));
-  }
-
-  @Test
-  public void testSimplePhi() {
-    buildAndCheckIR(
-        "simplePhi",
-        checkOptimizerStates(appView, optimizer -> {
-          assertEquals(0, optimizer.analysis.builderStates.size());
-          assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
-        }));
-  }
-
-  @Test
-  public void testPhiAtInit() {
-    int expectedNumOfNewBuilder = 0;
-    boolean expectToMeetToString = false;
-    if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.M)) {
-      expectedNumOfNewBuilder = 1;
-      expectToMeetToString = true;
-    }
-    final int finalExpectedNumOfNewBuilder = expectedNumOfNewBuilder;
-    final boolean finalExpectToMeetToString = expectToMeetToString;
-    buildAndCheckIR(
-        "phiAtInit",
-        checkOptimizerStates(appView, optimizer -> {
-          assertEquals(finalExpectedNumOfNewBuilder, optimizer.analysis.builderStates.size());
-          for (Value builder : optimizer.analysis.builderStates.keySet()) {
-            Map<Instruction, BuilderState> perBuilderState =
-                optimizer.analysis.builderStates.get(builder);
-            checkBuilderState(optimizer, perBuilderState, null, finalExpectToMeetToString);
-          }
-          assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
-        }));
-  }
-
-  @Test
-  public void testPhiWithDifferentInits() {
-    buildAndCheckIR(
-        "phiWithDifferentInits",
-        checkOptimizerStates(
-            appView,
-            optimizer -> {
-              assertEquals(0, optimizer.analysis.builderStates.size());
-              for (Value builder : optimizer.analysis.builderStates.keySet()) {
-                Map<Instruction, BuilderState> perBuilderState =
-                    optimizer.analysis.builderStates.get(builder);
-                checkBuilderState(optimizer, perBuilderState, null, false);
-              }
-              assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
-            }));
-  }
-
-  @Test
-  public void testConditionalPhiWithoutAppend() {
-    buildAndCheckIR(
-        "conditionalPhiWithoutAppend",
-        checkOptimizerStates(
-            appView,
-            optimizer -> {
-              assertEquals(1, optimizer.analysis.builderStates.size());
-              for (Value builder : optimizer.analysis.builderStates.keySet()) {
-                Map<Instruction, BuilderState> perBuilderState =
-                    optimizer.analysis.builderStates.get(builder);
-                checkBuilderState(optimizer, perBuilderState, "initial:suffix", true);
-              }
-              assertEquals(1, optimizer.analysis.simplifiedBuilders.size());
-            }));
-  }
-
-  @Test
-  public void testLoop() {
-    buildAndCheckIR(
-        "loop",
-        checkOptimizerStates(appView, optimizer -> {
-          assertEquals(2, optimizer.analysis.builderStates.size());
-          for (Value builder : optimizer.analysis.builderStates.keySet()) {
-            Map<Instruction, BuilderState> perBuilderState =
-                optimizer.analysis.builderStates.get(builder);
-            checkBuilderState(optimizer, perBuilderState, null, true);
-          }
-          assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
-        }));
-  }
-
-  @Test
-  public void testLoopWithBuilder() {
-    buildAndCheckIR(
-        "loopWithBuilder",
-        checkOptimizerStates(appView, optimizer -> {
-          assertEquals(1, optimizer.analysis.builderStates.size());
-          for (Value builder : optimizer.analysis.builderStates.keySet()) {
-            Map<Instruction, BuilderState> perBuilderState =
-                optimizer.analysis.builderStates.get(builder);
-            checkBuilderState(optimizer, perBuilderState, null, true);
-          }
-          assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
-        }));
-  }
-
-  static Consumer<IRCode> checkOptimizerStates(
-      AppView<?> appView, Consumer<StringBuilderOptimizer> optimizerConsumer) {
-    return code -> {
-      StringBuilderOptimizer optimizer = new StringBuilderOptimizer(appView);
-      optimizer.computeTrivialStringConcatenation(code);
-      optimizerConsumer.accept(optimizer);
-    };
-  }
-
-  static void checkBuilderState(
-      StringBuilderOptimizer optimizer,
-      Map<Instruction, BuilderState> perBuilderState,
-      String expectedConstString,
-      boolean expectToSeeMethodToString) {
-    boolean seenMethodToString = false;
-    for (Map.Entry<Instruction, BuilderState> entry : perBuilderState.entrySet()) {
-      InvokeMethod invoke = entry.getKey().asInvokeMethod();
-      if (invoke != null
-          && optimizer.optimizationConfiguration.isToStringMethod(invoke.getInvokedMethod())) {
-        seenMethodToString = true;
-        Value builder = invoke.getArgument(0).getAliasedValue();
-        assertEquals(
-            expectedConstString, optimizer.analysis.toCompileTimeString(builder, entry.getValue()));
-      }
-    }
-    assertEquals(expectToSeeMethodToString, seenMethodToString);
-  }
-
-  static void checkBuilderStates(
-      StringBuilderOptimizer optimizer,
-      Map<Instruction, BuilderState> perBuilderState,
-      Set<String> expectedConstStrings,
-      boolean expectToSeeMethodToString) {
-    boolean seenMethodToString = false;
-    for (Map.Entry<Instruction, BuilderState> entry : perBuilderState.entrySet()) {
-      InvokeMethod invoke = entry.getKey().asInvokeMethod();
-      if (invoke != null
-          && optimizer.optimizationConfiguration.isToStringMethod(invoke.getInvokedMethod())) {
-        seenMethodToString = true;
-        Value builder = invoke.getArgument(0).getAliasedValue();
-        String computedString = optimizer.analysis.toCompileTimeString(builder, entry.getValue());
-        assertNotNull(computedString);
-        assertTrue(expectedConstStrings.contains(computedString));
-        expectedConstStrings.remove(computedString);
-      }
-    }
-    assertEquals(expectToSeeMethodToString, seenMethodToString);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderTests.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderTests.java
index f908b95..365ced3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderTests.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderTests.java
@@ -37,7 +37,7 @@
   @Parameters(name = "{0}, configuration: {1}")
   public static List<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(), getTestExpectations());
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(), getTestExpectations());
   }
 
   private static class StringBuilderResult {
@@ -94,14 +94,14 @@
         StringBuilderResult.create(
             Main.class.getMethod("materializingWithAdditionalAppend"),
             StringUtils.lines("Hello World", "Hello WorldObservable"),
-            1,
-            3,
-            2),
+            0,
+            0,
+            0),
         StringBuilderResult.create(
             Main.class.getMethod("appendWithNonConstant"),
             StringUtils.lines("Hello World, Hello World"),
             1,
-            3,
+            2,
             1),
         StringBuilderResult.create(
             Main.class.getMethod("simpleLoopTest"),
@@ -109,13 +109,12 @@
             1,
             1,
             1),
-        // TODO(b/222437581): Should not remove StringBuilder
         StringBuilderResult.create(
             Main.class.getMethod("simpleLoopTest2"),
             StringUtils.lines("Hello World", "Hello WorldHello World"),
-            0,
-            0,
-            0),
+            1,
+            1,
+            1),
         StringBuilderResult.create(
             Main.class.getMethod("simpleLoopWithStringBuilderInBodyTest"),
             StringUtils.lines("Hello World"),
@@ -129,27 +128,27 @@
             0,
             0),
         StringBuilderResult.create(
-            Main.class.getMethod("diamondWithUseTest"), StringUtils.lines("Hello World"), 1, 3, 1),
+            Main.class.getMethod("diamondWithUseTest"), StringUtils.lines("Hello World"), 1, 2, 1),
         StringBuilderResult.create(
             Main.class.getMethod("diamondsWithSingleUseTest"),
             StringUtils.lines("Hello World"),
             1,
-            3,
+            2,
             1),
         StringBuilderResult.create(
             Main.class.getMethod("escapeTest"), StringUtils.lines("Hello World"), 2, 2, 1),
         StringBuilderResult.create(
             Main.class.getMethod("intoPhiTest"), StringUtils.lines("Hello World"), 2, 2, 1),
         StringBuilderResult.create(
-            Main.class.getMethod("optimizePartial"), StringUtils.lines("Hello World.."), 1, 4, 1),
+            Main.class.getMethod("optimizePartial"), StringUtils.lines("Hello World.."), 1, 2, 1),
         StringBuilderResult.create(
             Main.class.getMethod("multipleToStrings"),
             StringUtils.lines("Hello World", "Hello World.."),
-            1,
-            4,
-            2),
+            0,
+            0,
+            0),
         StringBuilderResult.create(
-            Main.class.getMethod("changeAppendType"), StringUtils.lines("1 World"), 1, 3, 1),
+            Main.class.getMethod("changeAppendType"), StringUtils.lines("1 World"), 1, 2, 1),
         StringBuilderResult.create(
             Main.class.getMethod("checkCapacity"), StringUtils.lines("true"), 2, 1, 0),
         StringBuilderResult.create(
@@ -157,29 +156,29 @@
         StringBuilderResult.create(
             Main.class.getMethod("stringBuilderWithStringBuilderToString"),
             StringUtils.lines("Hello World"),
-            1,
-            1,
-            1),
+            0,
+            0,
+            0),
         StringBuilderResult.create(
             Main.class.getMethod("stringBuilderWithStringBuilder"),
             StringUtils.lines("Hello World"),
-            2,
-            2,
-            1),
+            0,
+            0,
+            0),
         StringBuilderResult.create(
             Main.class.getMethod("stringBuilderInStringBuilderConstructor"),
             StringUtils.lines("Hello World"),
-            2,
-            1,
-            1),
+            0,
+            0,
+            0),
         StringBuilderResult.create(
             Main.class.getMethod("interDependencyTest"),
             StringUtils.lines("World Hello World "),
-            2,
-            2,
-            1),
+            0,
+            0,
+            0),
         StringBuilderResult.create(
-            Main.class.getMethod("stringBuilderSelfReference"), StringUtils.lines(""), 1, 1, 1),
+            Main.class.getMethod("stringBuilderSelfReference"), StringUtils.lines(""), 0, 0, 0),
         StringBuilderResult.create(
             Main.class.getMethod("unknownStringBuilderInstruction"),
             StringUtils.lines("Hello World"),
@@ -214,8 +213,6 @@
 
   @Test
   public void testR8() throws Exception {
-    boolean hasError =
-        stringBuilderTest.getMethodName().equals("simpleLoopTest2") && parameters.isDexRuntime();
     compilationResults
         .apply(parameters)
         .inspect(
@@ -235,9 +232,7 @@
                   stringBuilderTest.toStrings, countStringBuilderToStrings(foundMethodSubject));
             })
         .run(parameters.getRuntime(), Main.class, stringBuilderTest.getMethodName())
-        // TODO(b/222437581): Incorrect result for string builder inside loop.
-        .assertSuccessWithOutputLinesIf(hasError, "Hello World", "Hello World")
-        .assertSuccessWithOutputIf(!hasError, stringBuilderTest.expected);
+        .assertSuccessWithOutput(stringBuilderTest.expected);
   }
 
   private long countStringBuilderInits(FoundMethodSubject method) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIndirectMutationThroughPhiTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIndirectMutationThroughPhiTest.java
index bd6fffd..88f0cf8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIndirectMutationThroughPhiTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIndirectMutationThroughPhiTest.java
@@ -41,10 +41,8 @@
             .inspect(
                 inspector -> {
                   MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
-                  // TODO(b/114002137): This should be optimized into having only 3 append calls,
-                  //  since append("baz").append("qux") can be optimized into append("bazqux").
                   assertEquals(
-                      4,
+                      parameters.isCfRuntime() ? 4 : 3,
                       mainMethodSubject
                           .streamInstructions()
                           .filter(isInvokeStringBuilderAppendWithString())
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIndirectPhiMutationThroughPhiOperandTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIndirectPhiMutationThroughPhiOperandTest.java
index b553e2d..81fa83b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIndirectPhiMutationThroughPhiOperandTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIndirectPhiMutationThroughPhiOperandTest.java
@@ -41,10 +41,9 @@
             .inspect(
                 inspector -> {
                   MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
-                  // TODO(b/114002137): This should be optimized into having only 3 append calls,
-                  //  since append("baz").append("qux") can be optimized into append("bazqux").
+                  // TODO(b/114002137): Also run for CF
                   assertEquals(
-                      4,
+                      parameters.isCfRuntime() ? 4 : 3,
                       mainMethodSubject
                           .streamInstructions()
                           .filter(isInvokeStringBuilderAppendWithString())
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
index ef8217a..617fd63 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
@@ -9,7 +9,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
@@ -113,27 +112,27 @@
 
     method = mainClass.uniqueMethodWithName("trivialSequence");
     assertThat(method, isPresent());
-    expectedCount = isR8 ? 1 : 3;
+    expectedCount = isReleaseMode ? 1 : 3;
     assertEquals(expectedCount, countConstString(method));
 
     method = mainClass.uniqueMethodWithName("builderWithInitialValue");
     assertThat(method, isPresent());
-    expectedCount = isR8 ? 1 : 3;
+    expectedCount = isReleaseMode ? 1 : 3;
     assertEquals(expectedCount, countConstString(method));
 
     method = mainClass.uniqueMethodWithName("builderWithCapacity");
     assertThat(method, isPresent());
-    expectedCount = isR8 ? 1 : 0;
+    expectedCount = isReleaseMode ? 1 : 0;
     assertEquals(expectedCount, countConstString(method));
 
     method = mainClass.uniqueMethodWithName("nonStringArgs");
     assertThat(method, isPresent());
-    expectedCount = isR8 ? 1 : 0;
+    expectedCount = isReleaseMode ? 1 : 0;
     assertEquals(expectedCount, countConstString(method));
 
     method = mainClass.uniqueMethodWithName("typeConversion");
     assertThat(method, isPresent());
-    expectedCount = isR8 ? 1 : 0;
+    expectedCount = isReleaseMode ? 1 : 0;
     assertEquals(expectedCount, countConstString(method));
 
     method = mainClass.uniqueMethodWithName("typeConversion_withPhis");
@@ -142,41 +141,31 @@
 
     method = mainClass.uniqueMethodWithName("nestedBuilders_appendBuilderItself");
     assertThat(method, isPresent());
-    // TODO(b/113859361): merge builders
-    expectedCount = 3;
-    assertEquals(expectedCount, countConstString(method));
+    assertEquals(isReleaseMode ? 1 : 3, countConstString(method));
 
     method = mainClass.uniqueMethodWithName("nestedBuilders_appendBuilderResult");
     assertThat(method, isPresent());
-    // TODO(b/113859361): merge builders
-    expectedCount = 3;
-    assertEquals(expectedCount, countConstString(method));
+    assertEquals(isReleaseMode ? 1 : 3, countConstString(method));
 
     method = mainClass.uniqueMethodWithName("nestedBuilders_conditional");
     assertThat(method, isPresent());
-    assertEquals(4, countConstString(method));
+    assertEquals(isReleaseMode ? 3 : 4, countConstString(method));
 
     method = mainClass.uniqueMethodWithName("concatenatedBuilders_init");
     assertThat(method, isPresent());
-    // TODO(b/113859361): merge builders
-    expectedCount = 2;
-    assertEquals(expectedCount, countConstString(method));
+    assertEquals(isReleaseMode ? 1 : 2, countConstString(method));
 
     method = mainClass.uniqueMethodWithName("concatenatedBuilders_append");
     assertThat(method, isPresent());
-    // TODO(b/113859361): merge builders
-    expectedCount = 2;
-    assertEquals(expectedCount, countConstString(method));
+    assertEquals(isReleaseMode ? 1 : 2, countConstString(method));
 
     method = mainClass.uniqueMethodWithName("concatenatedBuilders_conditional");
     assertThat(method, isPresent());
-    // TODO(b/113859361): merge builders
-    expectedCount = 4;
-    assertEquals(expectedCount, countConstString(method));
+    assertEquals(isReleaseMode ? 2 : 4, countConstString(method));
 
     method = mainClass.uniqueMethodWithName("simplePhi");
     assertThat(method, isPresent());
-    assertEquals(4, countConstString(method));
+    assertEquals(isReleaseMode ? 3 : 4, countConstString(method));
 
     method = mainClass.uniqueMethodWithName("phiAtInit");
     assertThat(method, isPresent());
@@ -188,7 +177,7 @@
 
     method = mainClass.uniqueMethodWithName("conditionalPhiWithoutAppend");
     assertThat(method, isPresent());
-    assertEquals(3, countConstString(method));
+    assertEquals(isReleaseMode ? 2 : 3, countConstString(method));
 
     method = mainClass.uniqueMethodWithName("loop");
     assertThat(method, isPresent());
@@ -204,26 +193,31 @@
   }
 
   @Test
-  public void testD8() throws Exception {
+  public void testD8Debug() throws Exception {
     assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
-
-    D8TestRunResult result =
+    test(
         testForD8()
             .debug()
             .addProgramClasses(MAIN)
             .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), MAIN)
-            .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, false, false);
+            .assertSuccessWithOutput(JAVA_OUTPUT),
+        false,
+        false);
+  }
 
-    result =
+  @Test
+  public void testD8Release() throws Exception {
+    assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
+    test(
         testForD8()
             .release()
             .addProgramClasses(MAIN)
             .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), MAIN)
-            .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, false, true);
+            .assertSuccessWithOutput(JAVA_OUTPUT),
+        false,
+        true);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java
index 12bc414..8b958fd 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
diff --git a/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java
index 5bbd532..0fa90c1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java
@@ -65,7 +65,7 @@
   public void testR8() throws ExecutionException, CompilationFailedException, IOException {
     testForR8(parameters.getBackend())
         .addProgramFiles(compilationResults.getForConfiguration(kotlinc, targetVersion))
-        .addProgramFiles(buildOnDexRuntime(parameters, kotlinc.getKotlinStdlibJar()))
+        .addProgramFiles(kotlinc.getKotlinStdlibJar())
         .addProgramFiles(kotlinc.getKotlinAnnotationJar())
         .setMinApi(parameters.getApiLevel())
         .allowAccessModification()
diff --git a/src/test/java/com/android/tools/r8/kotlin/stringplus/StringPlusTest.java b/src/test/java/com/android/tools/r8/kotlin/stringplus/StringPlusTest.java
index 170595a..37b20bd 100644
--- a/src/test/java/com/android/tools/r8/kotlin/stringplus/StringPlusTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/stringplus/StringPlusTest.java
@@ -8,6 +8,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.KotlinCompilerTool;
@@ -100,11 +101,18 @@
                 // TODO(b/190489514): We should be able to optimize constant stringPlus calls.
                 assertThat(methodSubject, CodeMatchers.invokesMethodWithName("stringPlus"));
               }
-              // TODO(b/219455761): StringBuilderOptimizer fails to remove constant input.
-              assertThat(
-                  methodSubject,
-                  CodeMatchers.invokesMethodWithHolderAndName(
-                      typeName(StringBuilder.class), "append"));
+              // We cannot remove the <init> -> <append> call since that changes the capacity
+              // and the string builder is escaping into System.out.
+              assertEquals(
+                  1,
+                  methodSubject
+                      .streamInstructions()
+                      .filter(
+                          instructionSubject ->
+                              CodeMatchers.isInvokeWithTarget(
+                                      typeName(StringBuilder.class), "append")
+                                  .test(instructionSubject))
+                      .count());
             })
         .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 2eb6a7f..25ffe36 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -10,77 +10,36 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.D8;
 import com.android.tools.r8.D8Command;
-import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.DiagnosticsMatcher;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DebugLocalInfo;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClassAndMethod;
-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.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.graph.MethodCollection.MethodCollectionFactory;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
-import com.android.tools.r8.ir.code.CatchHandlers;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Phi.RegisterReadType;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.Position.SyntheticPosition;
-import com.android.tools.r8.ir.code.ValueTypeConstraint;
-import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
-import com.android.tools.r8.ir.conversion.SourceCode;
-import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
-import com.android.tools.r8.ir.regalloc.RegisterAllocator;
-import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
-import com.android.tools.r8.utils.AbortException;
-import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.transformers.ClassTransformer;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.AndroidAppConsumers;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.MainDexListParser;
-import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.google.common.collect.ImmutableList;
@@ -89,26 +48,22 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
 
 @RunWith(Parameterized.class)
 public class MainDexListTests extends TestBase {
@@ -154,21 +109,22 @@
 
     // Generates an application with many classes, every even in one package and every odd in
     // another. Keep the number of methods low enough for single dex application.
-    AndroidApp generated = generateApplication(
-        MANY_CLASSES, AndroidApiLevel.getDefault().getLevel(),
-        MANY_CLASSES_SINGLE_DEX_METHODS_PER_CLASS);
-    generated.write(getManyClassesSingleDexAppPath(), OutputMode.DexIndexed);
+    generateApplication(
+        getManyClassesSingleDexAppPath(), MANY_CLASSES, MANY_CLASSES_SINGLE_DEX_METHODS_PER_CLASS);
 
     // Generates an application with many classes, every even in one package and every odd in
     // another. Add enough methods so the application cannot fit into one dex file.
-    generated = generateApplication(
-        MANY_CLASSES, AndroidApiLevel.L.getLevel(), MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS);
-    generated.write(getManyClassesMultiDexAppPath(), OutputMode.DexIndexed);
+    generateApplication(
+        getManyClassesMultiDexAppPath(), MANY_CLASSES, MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS);
 
     // Generates an application with two classes, each with the maximum possible number of methods.
-    generated = generateApplication(TWO_LARGE_CLASSES, AndroidApiLevel.N.getLevel(),
-        MAX_METHOD_COUNT);
-    generated.write(getTwoLargeClassesAppPath(), OutputMode.DexIndexed);
+    generateApplication(getTwoLargeClassesAppPath(), TWO_LARGE_CLASSES, getLargeClassMethodCount());
+  }
+
+  private static int getLargeClassMethodCount() {
+    int otherConstantPoolEntries = 23;
+    int maxMethodCount = MAX_METHOD_COUNT - otherConstantPoolEntries;
+    return maxMethodCount;
   }
 
   private static Path getTwoLargeClassesAppPath() {
@@ -183,17 +139,10 @@
     return generatedApplicationsFolder.getRoot().toPath().resolve("many-classes-stereo.zip");
   }
 
-  private static Path getManyClassesForceMultiDexAppPath() {
-    return generatedApplicationsFolder.getRoot().toPath().resolve("many-classes-stereo-forced.zip");
-  }
-
-  private static Set<DexType> parse(Path path, DexItemFactory itemFactory) throws IOException {
+  private static Set<DexType> parse(Path path, DexItemFactory itemFactory) {
     return MainDexListParser.parseList(StringResource.fromFile(path), itemFactory);
   }
 
-  @Rule
-  public ExpectedException thrown = ExpectedException.none();
-
   @Test
   public void checkGeneratedFileFitInSingleDexFile() {
     assertTrue(MANY_CLASSES_COUNT * MANY_CLASSES_SINGLE_DEX_METHODS_PER_CLASS <= MAX_METHOD_COUNT);
@@ -221,20 +170,23 @@
         getTwoLargeClassesAppPath(),
         false,
         test -> {
-          TestDiagnosticsHandler handler = new TestDiagnosticsHandler();
+          TestDiagnosticMessagesImpl handler = new TestDiagnosticMessagesImpl();
           try {
             test.run(handler);
             fail("Expect to fail, for there are too many classes for the main-dex list.");
           } catch (Throwable e) {
             assert e instanceof CompilationFailedException;
-            assertEquals(1, handler.errors.size());
-            DexFileOverflowDiagnostic overflow = (DexFileOverflowDiagnostic) handler.errors.get(0);
+            handler.assertErrorsMatch(
+                DiagnosticsMatcher.diagnosticType(DexFileOverflowDiagnostic.class));
+            DexFileOverflowDiagnostic overflow =
+                (DexFileOverflowDiagnostic) handler.getErrors().get(0);
             // Make sure {@link MonoDexDistributor} was _not_ used, i.e., a spec was given.
             assertTrue(overflow.hasMainDexSpecification());
             // Make sure what exceeds the limit is the number of methods.
             assertTrue(overflow.getNumberOfMethods() > overflow.getMaximumNumberOfMethods());
             assertEquals(
-                TWO_LARGE_CLASSES.size() * MAX_METHOD_COUNT, overflow.getNumberOfMethods());
+                TWO_LARGE_CLASSES.size() * getLargeClassMethodCount(),
+                overflow.getNumberOfMethods());
           }
         });
   }
@@ -261,7 +213,7 @@
 
   @Test
   public void allClassesInMainDex() throws Throwable {
-    // Degenerated case with an app thats fit into a single dex, and where the main dex list
+    // Degenerated case with an app that fits into a single dex, and where the main dex list
     // contains all classes.
     verifyMainDexContains(MANY_CLASSES, getManyClassesSingleDexAppPath(), true);
   }
@@ -273,14 +225,16 @@
         getManyClassesMultiDexAppPath(),
         false,
         test -> {
-          TestDiagnosticsHandler handler = new TestDiagnosticsHandler();
+          TestDiagnosticMessagesImpl handler = new TestDiagnosticMessagesImpl();
           try {
             test.run(handler);
             fail("Expect to fail, for there are too many classes for the main-dex list.");
           } catch (Throwable e) {
             assert e instanceof CompilationFailedException;
-            assertEquals(1, handler.errors.size());
-            DexFileOverflowDiagnostic overflow = (DexFileOverflowDiagnostic) handler.errors.get(0);
+            handler.assertErrorsMatch(
+                DiagnosticsMatcher.diagnosticType(DexFileOverflowDiagnostic.class));
+            DexFileOverflowDiagnostic overflow =
+                (DexFileOverflowDiagnostic) handler.getErrors().get(0);
             // Make sure {@link MonoDexDistributor} was _not_ used, i.e., a main-dex spec was given.
             assertTrue(overflow.hasMainDexSpecification());
             // Make sure what exceeds the limit is the number of methods.
@@ -552,44 +506,6 @@
             .collect(Collectors.toList()), false);
   }
 
-  @Test
-  public void checkIntermediateMultiDex() throws Exception {
-    // Generates an application with many classes, every even in one package and every odd in
-    // another. Add enough methods so the application cannot fit into one dex file.
-    // Notice that this one allows multidex while using lower API.
-    AndroidApp generated = generateApplication(
-        MANY_CLASSES, AndroidApiLevel.K.getLevel(), true, MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS);
-    generated.write(getManyClassesForceMultiDexAppPath(), OutputMode.DexIndexed);
-    // Make sure the generated app indeed has multiple dex files.
-    assertTrue(generated.getDexProgramResourcesForTesting().size() > 1);
-  }
-
-  @Test
-  public void testMultiDexFailDueToMinApi() throws Exception {
-    // Generates an application with many classes, every even in one package and every odd in
-    // another. Add enough methods so the application cannot fit into one dex file.
-    // Notice that this one fails due to the min API.
-    TestDiagnosticsHandler handler = new TestDiagnosticsHandler();
-    try {
-      generateApplication(
-          MANY_CLASSES,
-          AndroidApiLevel.K.getLevel(),
-          false,
-          MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS,
-          handler);
-      fail("Expect to fail, for there are many classes while multidex is not enabled.");
-    } catch (AbortException e) {
-      assertEquals(1, handler.errors.size());
-      DexFileOverflowDiagnostic overflow = (DexFileOverflowDiagnostic) handler.errors.get(0);
-      // Make sure {@link MonoDexDistributor} was used, i.e., no main-dex specification was given.
-      assertFalse(overflow.hasMainDexSpecification());
-      // Make sure what exceeds the limit is the number of methods.
-      assertEquals(
-          MANY_CLASSES_COUNT * MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS,
-          overflow.getNumberOfMethods());
-    }
-  }
-
   private static String typeToEntry(String type) {
     return type.replace(".", "/") + FileUtils.CLASS_EXTENSION;
   }
@@ -637,7 +553,7 @@
       boolean minimalMainDex,
       MultiDexTestMode testMode,
       DiagnosticsHandler handler)
-      throws IOException, ExecutionException, CompilationFailedException {
+      throws IOException, CompilationFailedException {
     AndroidApp originalApp = AndroidApp.builder().addProgramFiles(app).build();
     CodeInspector originalInspector = new CodeInspector(originalApp);
     for (String clazz : mainDex) {
@@ -663,8 +579,8 @@
         builder.addMainDexListFiles(mainDexList);
         break;
       case MULTIPLE_FILES: {
-        // Partion the main dex list into several files.
-        List<List<String>> partitions = Lists.partition(mainDex, Math.max(mainDex.size() / 3, 1));
+          // Partition the main dex list into several files.
+          List<List<String>> partitions = Lists.partition(mainDex, Math.max(mainDex.size() / 3, 1));
         List<Path> mainDexListFiles = new ArrayList<>();
         for (List<String> partition : partitions) {
           Path partialMainDexList = temp.newFile().toPath();
@@ -725,7 +641,7 @@
           singleDexApp,
           test -> {
             try {
-              test.run(new TestDiagnosticsHandler());
+              test.run(new TestDiagnosticMessagesImpl());
             } catch (Throwable e) {
               throw new RuntimeException(e);
             }
@@ -748,270 +664,46 @@
     }
   }
 
-  public static AndroidApp generateApplication(List<String> classes, int minApi, int methodCount)
-      throws IOException, ExecutionException {
-    return generateApplication(classes, minApi, false, methodCount);
+  private static void generateApplication(Path output, List<String> classes, int methodCount)
+      throws IOException {
+    ArchiveConsumer consumer = new ArchiveConsumer(output);
+    for (String typename : classes) {
+      String descriptor = DescriptorUtils.javaTypeToDescriptor(typename);
+      byte[] bytes =
+          transformer(ClassStub.class)
+              .setClassDescriptor(descriptor)
+              .addClassTransformer(
+                  new ClassTransformer() {
+                    @Override
+                    public MethodVisitor visitMethod(
+                        int access,
+                        String name,
+                        String descriptor,
+                        String signature,
+                        String[] exceptions) {
+                      // This strips <init>() too.
+                      if (name.equals("methodStub")) {
+                        for (int i = 0; i < methodCount; i++) {
+                          MethodVisitor mv =
+                              super.visitMethod(
+                                  access, "method" + i, descriptor, signature, exceptions);
+                          mv.visitCode();
+                          mv.visitInsn(Opcodes.RETURN);
+                          mv.visitMaxs(0, 0);
+                          mv.visitEnd();
+                        }
+                      }
+                      return null;
+                    }
+                  })
+              .transform();
+      consumer.accept(ByteDataView.of(bytes), descriptor, null);
+    }
+    consumer.finished(null);
   }
 
-  private static AndroidApp generateApplication(
-      List<String> classes, int minApi, boolean intermediate, int methodCount)
-      throws IOException, ExecutionException {
-    return generateApplication(
-        classes, minApi, intermediate, methodCount, new DiagnosticsHandler() {});
-  }
-
-  private static AndroidApp generateApplication(
-      List<String> classes,
-      int minApi,
-      boolean intermediate,
-      int methodCount,
-      DiagnosticsHandler diagnosticsHandler)
-      throws IOException, ExecutionException {
-    Timing timing = Timing.empty();
-    InternalOptions options =
-        new InternalOptions(new DexItemFactory(), new Reporter(diagnosticsHandler));
-    options.setMinApiLevel(AndroidApiLevel.getAndroidApiLevel(minApi));
-    options.intermediate = intermediate;
-    DexItemFactory factory = options.itemFactory;
-    AppView<?> appView = AppView.createForR8(DexApplication.builder(options, timing).build());
-    DexApplication.Builder<?> builder = DexApplication.builder(options, timing);
-    for (String clazz : classes) {
-      DexString desc = factory.createString(DescriptorUtils.javaTypeToDescriptor(clazz));
-      DexType type = factory.createType(desc);
-      DexProgramClass programClass =
-          new DexProgramClass(
-              type,
-              null,
-              new SynthesizedOrigin("test", MainDexListTests.class),
-              ClassAccessFlags.fromSharedAccessFlags(0),
-              factory.objectType,
-              DexTypeList.empty(),
-              null,
-              null,
-              Collections.emptyList(),
-              Collections.emptyList(),
-              null,
-              Collections.emptyList(),
-              ClassSignature.noSignature(),
-              DexAnnotationSet.empty(),
-              DexEncodedField.EMPTY_ARRAY,
-              DexEncodedField.EMPTY_ARRAY,
-              MethodCollectionFactory.empty(),
-              false,
-              DexProgramClass::invalidChecksumRequest);
-      DexEncodedMethod[] directMethods = new DexEncodedMethod[methodCount];
-      for (int i = 0; i < methodCount; i++) {
-        MethodAccessFlags access = MethodAccessFlags.fromSharedAccessFlags(0, false);
-        access.setPublic();
-        access.setStatic();
-        DexMethod voidReturnMethod =
-            factory.createMethod(
-                desc,
-                factory.createString("method" + i),
-                factory.voidDescriptor,
-                DexString.EMPTY_ARRAY);
-        Code code =
-            new SynthesizedCode(
-                (ignored, callerPosition) -> new ReturnVoidCode(voidReturnMethod, callerPosition)) {
-              @Override
-              public Consumer<UseRegistry> getRegistryCallback(DexClassAndMethod method) {
-                throw new Unreachable();
-              }
-            };
-        DexEncodedMethod method =
-            DexEncodedMethod.builder()
-                .setMethod(voidReturnMethod)
-                .setAccessFlags(access)
-                .setCode(code)
-                .disableAndroidApiLevelCheck()
-                .build();
-        ProgramMethod programMethod = new ProgramMethod(programClass, method);
-        IRCode ir =
-            code.buildIR(
-                programMethod,
-                appView,
-                Origin.unknown(),
-                new MutableMethodConversionOptions(options));
-        RegisterAllocator allocator = new LinearScanRegisterAllocator(appView, ir);
-        programMethod.setCode(
-            new DexBuilder(ir, BytecodeMetadataProvider.empty(), allocator, options).build(),
-            appView);
-        directMethods[i] = method;
-      }
-      programClass.getMethodCollection().addDirectMethods(Arrays.asList(directMethods));
-      builder.addProgramClass(programClass);
-    }
-    DirectMappedDexApplication application = builder.build().toDirect();
-    ApplicationWriter writer =
-        new ApplicationWriter(
-            AppView.createForD8(
-                AppInfo.createInitialAppInfo(
-                    application, GlobalSyntheticsStrategy.forNonSynthesizing())),
-            null);
-    ExecutorService executor = ThreadUtils.getExecutorService(options);
-    AndroidAppConsumers compatSink = new AndroidAppConsumers(options);
-    try {
-      writer.write(executor);
-    } finally {
-      executor.shutdown();
-    }
-    options.signalFinishedToConsumers();
-    return compatSink.build();
-  }
-
-  // Code stub to generate methods with "return-void" bodies.
-  private static class ReturnVoidCode implements SourceCode {
-
-    private final Position position;
-
-    public ReturnVoidCode(DexMethod method, Position callerPosition) {
-      this.position =
-          SyntheticPosition.builder()
-              .setLine(0)
-              .setMethod(method)
-              .setCallerPosition(callerPosition)
-              .build();
-    }
-
-    @Override
-    public int instructionCount() {
-      return 1;
-    }
-
-    @Override
-    public int instructionIndex(int instructionOffset) {
-      return instructionOffset;
-    }
-
-    @Override
-    public int instructionOffset(int instructionIndex) {
-      return instructionIndex;
-    }
-
-    @Override
-    public DebugLocalInfo getIncomingLocalAtBlock(int register, int blockOffset) {
-      return null;
-    }
-
-    @Override
-    public DexType getPhiTypeForBlock(
-        int register, int blockOffset, ValueTypeConstraint constraint, RegisterReadType readType) {
-      throw new Unreachable("Should never generate a phi");
-    }
-
-    @Override
-    public DebugLocalInfo getIncomingLocal(int register) {
-      return null;
-    }
-
-    @Override
-    public DebugLocalInfo getOutgoingLocal(int register) {
-      return null;
-    }
-
-    @Override
-    public int traceInstruction(int instructionIndex, IRBuilder builder) {
-      return instructionIndex;
-    }
-
-    @Override
-    public void setUp() {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void clear() {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void buildPrelude(IRBuilder builder) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void buildInstruction(
-        IRBuilder builder, int instructionIndex, boolean firstBlockInstruction) {
-      assert instructionIndex == 0;
-      builder.addReturn();
-    }
-
-    @Override
-    public void buildBlockTransfer(
-        IRBuilder builder, int predecessorOffset, int successorOffset, boolean isExceptional) {
-      throw new Unreachable();
-    }
-
-    @Override
-    public void buildPostlude(IRBuilder builder) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void resolveAndBuildSwitch(
-        int value, int fallthroughOffset, int payloadOffset, IRBuilder builder) {
-      throw new Unreachable();
-    }
-
-    @Override
-    public void resolveAndBuildNewArrayFilledData(
-        int arrayRef, int payloadOffset, IRBuilder builder) {
-      throw new Unreachable();
-    }
-
-    @Override
-    public CatchHandlers<Integer> getCurrentCatchHandlers(IRBuilder builder) {
-      return null;
-    }
-
-    @Override
-    public int getMoveExceptionRegister(int instructionIndex) {
-      throw new Unreachable();
-    }
-
-    @Override
-    public Position getCanonicalDebugPositionAtOffset(int offset) {
-      return Position.none();
-    }
-
-    @Override
-    public Position getCurrentPosition() {
-      return position;
-    }
-
-    @Override
-    public boolean verifyRegister(int register) {
-      throw new Unreachable();
-    }
-
-    @Override
-    public boolean verifyCurrentInstructionCanThrow() {
-      throw new Unreachable();
-    }
-
-    @Override
-    public boolean verifyLocalInScope(DebugLocalInfo local) {
-      throw new Unreachable();
-    }
-  }
-
-  private class TestDiagnosticsHandler implements DiagnosticsHandler {
-
-    public List<Diagnostic> errors = new ArrayList<>();
-    public List<Diagnostic> warnings = new ArrayList<>();
-
-    public int numberOfErrorsAndWarnings() {
-      return errors.size() + warnings.size();
-    }
-
-    @Override
-    public void error(Diagnostic error) {
-      errors.add(error);
-    }
-
-    @Override
-    public void warning(Diagnostic warning) {
-      warnings.add(warning);
-    }
+  // Simple stub/template for generating the input classes.
+  public static class ClassStub {
+    public static void methodStub() {}
   }
 }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index 013a15b..f375004 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.references.Reference;
@@ -60,16 +61,15 @@
   private static final String EXAMPLE_SRC_DIR = ToolHelper.EXAMPLES_DIR;
   private static final String EXAMPLE_O_SRC_DIR = ToolHelper.EXAMPLES_ANDROID_O_DIR;
 
-  @Parameters(name = "{0}, {1}")
-  public static List<Object[]> data() {
-    return buildParameters(getTestParameters().withNoneRuntime().build(), Backend.values());
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
   }
 
-  private final Backend backend;
+  private final Backend backend = Backend.CF;
 
-  public MainDexTracingTest(TestParameters parameters, Backend backend) {
+  public MainDexTracingTest(TestParameters parameters) {
     parameters.assertNoneRuntime();
-    this.backend = backend;
   }
 
   private Path getInputJar(Path cfJar) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromCatchHandlerTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromCatchHandlerTest.java
index 11bc862..85e2d31 100644
--- a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromCatchHandlerTest.java
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromCatchHandlerTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.missingclasses;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.diagnostic.DefinitionContext;
@@ -27,19 +28,25 @@
   @Test(expected = CompilationFailedException.class)
   public void testNoRules() throws Exception {
     compileWithExpectedDiagnostics(
-        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom),
+        this::configure);
   }
 
   @Test
   public void testDontWarnMainClass() throws Exception {
     compileWithExpectedDiagnostics(
-        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(Main.class).andThen(this::configure));
   }
 
   @Test
   public void testDontWarnMissingClass() throws Exception {
     compileWithExpectedDiagnostics(
-        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(MissingClass.class));
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(MissingClass.class).andThen(this::configure));
   }
 
   @Test
@@ -47,7 +54,12 @@
     compileWithExpectedDiagnostics(
         Main.class,
         diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
-        addIgnoreWarnings());
+        addIgnoreWarnings().andThen(this::configure));
+  }
+
+  public void configure(R8FullTestBuilder testBuilder) {
+    testBuilder.addOptionsModification(
+        options -> options.getCfCodeAnalysisOptions().setEnableUnverifiableCodeReporting(false));
   }
 
   static class Main {
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/InterfaceInvokeWithNonTrivialButImpreciseStaticTypeTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/InterfaceInvokeWithNonTrivialButImpreciseStaticTypeTest.java
index 836c703..c7ca4d0 100644
--- a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/InterfaceInvokeWithNonTrivialButImpreciseStaticTypeTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/InterfaceInvokeWithNonTrivialButImpreciseStaticTypeTest.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
diff --git a/src/test/java/com/android/tools/r8/regress/b191296688/Regress191296688.java b/src/test/java/com/android/tools/r8/regress/b191296688/Regress191296688.java
index f3b4ad9..81f261c 100644
--- a/src/test/java/com/android/tools/r8/regress/b191296688/Regress191296688.java
+++ b/src/test/java/com/android/tools/r8/regress/b191296688/Regress191296688.java
@@ -67,7 +67,7 @@
             .addProgramClasses(A.class)
             .setMinApi(parameters.getApiLevel())
             .compile()
-            .inspect(this::verifyVirtualCallToPrivate)
+            .inspect(this::verifyDirectCallToPrivate)
             .writeToZip();
     testForD8()
         .addProgramFiles(desugaredJar)
@@ -77,7 +77,7 @@
         .assertSuccessWithOutputLines("hep");
   }
 
-  private void verifyVirtualCallToPrivate(CodeInspector inspector) {
+  private void verifyDirectCallToPrivate(CodeInspector inspector) {
     ClassSubject bClassSubject = inspector.clazz(PKG + ".B");
     MethodSubject proceedMethodSubject = bClassSubject.uniqueMethodWithName("proceed");
     assertThat(proceedMethodSubject, isPresent());
@@ -87,7 +87,7 @@
                 method ->
                     method
                         .streamInstructions()
-                        .filter(InstructionSubject::isInvokeVirtual)
+                        .filter(InstructionSubject::isInvokeSpecialOrDirect)
                         .anyMatch(isInvokeWithTarget(proceedMethodSubject))));
   }
 }
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 077e3c2..7028257 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
@@ -4,7 +4,9 @@
 package com.android.tools.r8.regress.b78493232;
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.startsWith;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.AsmTestBase;
@@ -96,12 +98,14 @@
                 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."))))
+                            allOf(
+                                startsWith(
+                                    "Unverifiable code in `java.lang.String regress78493232.Test."
+                                        + "methodCausingIssue(byte, short, int)`"),
+                                containsString(
+                                    "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/retrace/api/RetraceApiTestCollection.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
index 84f5522..e1e73b8 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
@@ -38,7 +38,12 @@
           RetraceApiOutlineInlineTest.ApiTest.class,
           RetraceApiOutlineInOutlineStackTrace.ApiTest.class,
           RetraceApiInlineInOutlineTest.ApiTest.class,
-          RetraceApiSingleFrameTest.ApiTest.class);
+          RetraceApiSingleFrameTest.ApiTest.class,
+          RetracePartitionStringTest.ApiTest.class,
+          RetracePartitionRoundTripTest.ApiTest.class,
+          RetracePartitionJoinNoMetadataTest.ApiTest.class,
+          RetracePartitionSerializedObfuscatedKeyTest.ApiTest.class,
+          RetracePartitionRoundTripInlineTest.ApiTest.class);
 
   public static List<Class<? extends RetraceApiBinaryTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
       ImmutableList.of();
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionJoinNoMetadataTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionJoinNoMetadataTest.java
new file mode 100644
index 0000000..095eb99
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionJoinNoMetadataTest.java
@@ -0,0 +1,99 @@
+// 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.api;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.naming.MapVersion;
+import com.android.tools.r8.retrace.PartitionMappingSupplier;
+import com.android.tools.r8.retrace.Retrace;
+import com.android.tools.r8.retrace.RetraceCommand;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RetracePartitionJoinNoMetadataTest extends RetraceApiTestBase {
+
+  public RetracePartitionJoinNoMetadataTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  protected Class<? extends RetraceApiBinaryTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  public static class ApiTest implements RetraceApiBinaryTest {
+
+    private final String aMapping =
+        "com.foo.bar.baz -> a:\n"
+            + " # {'id':'sourceFile','fileName':'BarBaz.kt'}\n"
+            + "  int field -> c\n"
+            + "  1:1:void method():42:42 -> d";
+
+    private final String bMapping =
+        "foo -> foo:\n"
+            + " # {'id':'sourceFile','fileName':'Foo-inlinee.kt'}\n"
+            + "com.android.google.R8 -> b:\n"
+            + " # {'id':'sourceFile','fileName':'R8Car.kt'}\n"
+            + "  2:2:int foo.inlinee():43:43 -> f\n"
+            + "  2:2:int otherMethod():44 -> f\n"
+            + "# otherCommentHere";
+
+    private final String exceptionMapping = "com.android.google.exception -> exception:\n";
+
+    private final List<String> stackTrace =
+        Arrays.asList("exception: Hello World!", " at a.d(Unknown:1)", " at b.f(Unknown:2)");
+
+    private final List<String> expectedRetracedStackTrace =
+        Arrays.asList(
+            "com.android.google.exception: Hello World!",
+            " at com.foo.bar.baz.method(BarBaz.kt:42)",
+            " at foo.inlinee(Foo-inlinee.kt:43)",
+            " at com.android.google.R8.otherMethod(R8Car.kt:44)");
+
+    @Test
+    public void test() throws IOException {
+      List<String> preFetch = new ArrayList<>();
+      PartitionMappingSupplier mappingSupplier =
+          PartitionMappingSupplier.noMetadataBuilder(MapVersion.MAP_VERSION_1_0)
+              .setMappingPartitionFromKeySupplier(
+                  key -> {
+                    switch (key) {
+                      case "a":
+                        return aMapping.getBytes();
+                      case "b":
+                        return bMapping.getBytes();
+                      case "exception":
+                        return exceptionMapping.getBytes();
+                      default:
+                        fail();
+                        return null;
+                    }
+                  })
+              .setRegisterMappingPartitionCallback(preFetch::add)
+              .build();
+      Retrace.run(
+          RetraceCommand.builder()
+              .setStackTrace(stackTrace)
+              .setMappingSupplier(mappingSupplier)
+              .setRetracedStackTraceConsumer(
+                  retraced -> assertEquals(expectedRetracedStackTrace, retraced))
+              .build());
+      assertEquals(3, preFetch.size());
+      Set<String> expected = new HashSet<>(Arrays.asList("a", "b", "exception"));
+      assertEquals(expected, new HashSet<>(preFetch));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionRoundTripInlineTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionRoundTripInlineTest.java
new file mode 100644
index 0000000..3bc2eb2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionRoundTripInlineTest.java
@@ -0,0 +1,117 @@
+// 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.api;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.MappingPartitionMetadata;
+import com.android.tools.r8.retrace.PartitionMappingSupplier;
+import com.android.tools.r8.retrace.ProguardMapPartitioner;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.RetraceFrameElement;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.Retracer;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.OptionalInt;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RetracePartitionRoundTripInlineTest extends RetraceApiTestBase {
+
+  public RetracePartitionRoundTripInlineTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  protected Class<? extends RetraceApiBinaryTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  public static class ApiTest implements RetraceApiBinaryTest {
+
+    private final ClassReference callerOriginal = Reference.classFromTypeName("some.Class");
+    private final ClassReference callerRenamed = Reference.classFromTypeName("b");
+
+    private final String mapping =
+        "# { id: 'com.android.tools.r8.mapping', version: '2.0' }\n"
+            + "inlinee.Class -> inlinee.Class:\n"
+            + " # {'id':'sourceFile','fileName':'InlineeClass.kt'}\n"
+            + callerOriginal.getTypeName()
+            + " -> "
+            + callerRenamed.getTypeName()
+            + ":\n"
+            + " # {'id':'sourceFile','fileName':'CallerClass.kt'}\n"
+            + "  1:1:void inlinee.Class.bar():42:42 -> a\n"
+            + "  1:1:void foo():43 -> a\n";
+
+    private int prepareCounter = 0;
+
+    @Test
+    public void test() throws IOException {
+      ProguardMapProducer proguardMapProducer = ProguardMapProducer.fromString(mapping);
+      Map<String, byte[]> partitions = new HashMap<>();
+      MappingPartitionMetadata metadataData =
+          ProguardMapPartitioner.builder(new DiagnosticsHandler() {})
+              .setProguardMapProducer(proguardMapProducer)
+              .setPartitionConsumer(
+                  partition -> partitions.put(partition.getKey(), partition.getPayload()))
+              .build()
+              .run();
+      assertNotNull(metadataData);
+      assertEquals(2, partitions.size());
+
+      Set<String> preFetchedKeys = new LinkedHashSet<>();
+      PartitionMappingSupplier mappingSupplier =
+          PartitionMappingSupplier.builder()
+              .setMetadata(metadataData.getBytes())
+              .setRegisterMappingPartitionCallback(preFetchedKeys::add)
+              .setPrepareMappingPartitionsCallback(() -> prepareCounter++)
+              .setMappingPartitionFromKeySupplier(
+                  key -> {
+                    assertTrue(preFetchedKeys.contains(key));
+                    assertTrue(partitions.containsKey(key));
+                    return partitions.get(key);
+                  })
+              .build();
+      assertEquals(0, prepareCounter);
+      Retracer retracer = Retracer.builder().setMappingSupplier(mappingSupplier).build();
+      List<RetraceFrameElement> callerRetraced =
+          retracer
+              .retraceFrame(
+                  RetraceStackTraceContext.empty(),
+                  OptionalInt.of(1),
+                  Reference.methodFromDescriptor(callerRenamed, "a", "()V"))
+              .stream()
+              .collect(Collectors.toList());
+      // The retrace result should not be ambiguous or empty.
+      assertEquals(1, callerRetraced.size());
+      assertEquals(1, preFetchedKeys.size());
+      assertEquals(1, prepareCounter);
+      RetraceFrameElement retraceFrameElement = callerRetraced.get(0);
+
+      // Check that visiting all frames report all source files.
+      List<String> allSourceFiles =
+          retraceFrameElement.stream()
+              .map(x -> x.getSourceFile().getOrInferSourceFile())
+              .collect(Collectors.toList());
+      assertEquals(Arrays.asList("InlineeClass.kt", "CallerClass.kt"), allSourceFiles);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionRoundTripTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionRoundTripTest.java
new file mode 100644
index 0000000..0eac918
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionRoundTripTest.java
@@ -0,0 +1,155 @@
+// 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.api;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.MappingPartitionMetadata;
+import com.android.tools.r8.retrace.PartitionMappingSupplier;
+import com.android.tools.r8.retrace.ProguardMapPartitioner;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.RetraceFrameElement;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.RetracedMethodReference;
+import com.android.tools.r8.retrace.RetracedSingleFrame;
+import com.android.tools.r8.retrace.Retracer;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.OptionalInt;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RetracePartitionRoundTripTest extends RetraceApiTestBase {
+
+  public RetracePartitionRoundTripTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  protected Class<? extends RetraceApiBinaryTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  public static class ApiTest implements RetraceApiBinaryTest {
+
+    private final ClassReference outlineRenamed = Reference.classFromTypeName("a");
+    private final ClassReference callsiteOriginal = Reference.classFromTypeName("some.Class");
+    private final ClassReference callsiteRenamed = Reference.classFromTypeName("b");
+
+    private final String mapping =
+        "# { id: 'com.android.tools.r8.mapping', version: '2.0' }\n"
+            + "outline.Class -> "
+            + outlineRenamed.getTypeName()
+            + ":\n"
+            + "  1:2:int outline():0:0 -> a\n"
+            + "# { 'id':'com.android.tools.r8.outline' }\n"
+            + callsiteOriginal.getTypeName()
+            + " -> "
+            + callsiteRenamed.getTypeName()
+            + ":\n"
+            + "  1:1:void foo.bar.Baz.qux():42:42 -> s\n"
+            + "  4:4:int foo.bar.baz.outlineCaller(int):98:98 -> s\n"
+            + "  4:4:int outlineCaller(int):24 -> s\n"
+            + "  27:27:int outlineCaller(int):0:0 -> s\n" // This is the actual call to the outline
+            + "# { 'id':'com.android.tools.r8.outlineCallsite', "
+            + "    'positions': { '1': 4, '2': 5 } }\n";
+
+    private int prepareCounter = 0;
+
+    @Test
+    public void test() throws IOException {
+      ProguardMapProducer proguardMapProducer = ProguardMapProducer.fromString(mapping);
+      Map<String, byte[]> partitions = new HashMap<>();
+      MappingPartitionMetadata metadataData =
+          ProguardMapPartitioner.builder(new DiagnosticsHandler() {})
+              .setProguardMapProducer(proguardMapProducer)
+              .setPartitionConsumer(
+                  partition -> partitions.put(partition.getKey(), partition.getPayload()))
+              .build()
+              .run();
+      assertNotNull(metadataData);
+      assertEquals(2, partitions.size());
+
+      Set<String> preFetchedKeys = new LinkedHashSet<>();
+      PartitionMappingSupplier mappingSupplier =
+          PartitionMappingSupplier.builder()
+              .setMetadata(metadataData.getBytes())
+              .setRegisterMappingPartitionCallback(preFetchedKeys::add)
+              .setPrepareMappingPartitionsCallback(() -> prepareCounter++)
+              .setMappingPartitionFromKeySupplier(
+                  key -> {
+                    assertTrue(preFetchedKeys.contains(key));
+                    return partitions.get(key);
+                  })
+              .build();
+      assertEquals(0, prepareCounter);
+      Retracer retracer = Retracer.builder().setMappingSupplier(mappingSupplier).build();
+      List<RetraceFrameElement> outlineRetraced =
+          retracer
+              .retraceFrame(
+                  RetraceStackTraceContext.empty(),
+                  OptionalInt.of(1),
+                  Reference.methodFromDescriptor(outlineRenamed, "a", "()I"))
+              .stream()
+              .collect(Collectors.toList());
+      // The retrace result should not be ambiguous or empty.
+      assertEquals(1, outlineRetraced.size());
+      assertEquals(1, preFetchedKeys.size());
+      assertEquals(1, prepareCounter);
+      RetraceFrameElement retraceFrameElement = outlineRetraced.get(0);
+
+      // Check that visiting all frames report the outline.
+      List<RetracedMethodReference> allMethodReferences =
+          retraceFrameElement.stream()
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
+      assertEquals(1, allMethodReferences.size());
+      assertEquals(0, allMethodReferences.get(0).getOriginalPositionOrDefault(2));
+
+      // Check that visiting rewritten frames will not report anything.
+      List<RetracedMethodReference> rewrittenReferences =
+          retraceFrameElement
+              .streamRewritten(RetraceStackTraceContext.empty())
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
+      assertEquals(0, rewrittenReferences.size());
+
+      // Retrace the outline position
+      RetraceStackTraceContext context = retraceFrameElement.getRetraceStackTraceContext();
+      List<RetraceFrameElement> retraceOutlineCallee =
+          retracer
+              .retraceFrame(
+                  context,
+                  OptionalInt.of(27),
+                  Reference.methodFromDescriptor(callsiteRenamed, "s", "(I)V"))
+              .stream()
+              .collect(Collectors.toList());
+      assertEquals(1, retraceOutlineCallee.size());
+      assertEquals(partitions.keySet(), preFetchedKeys);
+      assertEquals(2, prepareCounter);
+
+      List<RetracedMethodReference> outlineCallSiteFrames =
+          retraceOutlineCallee.get(0).stream()
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
+      assertEquals(2, outlineCallSiteFrames.size());
+      assertEquals(98, outlineCallSiteFrames.get(0).getOriginalPositionOrDefault(27));
+      assertEquals(24, outlineCallSiteFrames.get(1).getOriginalPositionOrDefault(27));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionSerializedObfuscatedKeyTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionSerializedObfuscatedKeyTest.java
new file mode 100644
index 0000000..58b092b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionSerializedObfuscatedKeyTest.java
@@ -0,0 +1,71 @@
+// 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.api;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.retrace.PartitionMappingSupplier;
+import com.android.tools.r8.retrace.Retrace;
+import com.android.tools.r8.retrace.RetraceCommand;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RetracePartitionSerializedObfuscatedKeyTest extends RetraceApiTestBase {
+
+  public RetracePartitionSerializedObfuscatedKeyTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  protected Class<? extends RetraceApiBinaryTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  public static class ApiTest implements RetraceApiBinaryTest {
+
+    private final String mapping = "com.R8 -> a:\n" + "  void m() -> b\n";
+
+    // Serialized data from ObfuscatedTypeNameAsKeyMetadata with Map Version 1.0.
+    private final byte[] metadata = new byte[] {0, 0, 49, 46, 48};
+
+    private static final String minifiedStackTraceLine = " at a.b()";
+
+    private static final String retracedStackTraceLine = " at com.R8.m(R8.java)";
+
+    @Test
+    public void test() throws IOException {
+      PartitionMappingSupplier mappingSupplier =
+          PartitionMappingSupplier.builder()
+              .setMetadata(metadata)
+              .setMappingPartitionFromKeySupplier(
+                  key -> {
+                    assertEquals("a", key);
+                    return mapping.getBytes(StandardCharsets.UTF_8);
+                  })
+              .build();
+
+      List<String> stackTrace = new ArrayList<>();
+      stackTrace.add(minifiedStackTraceLine);
+
+      Retrace.run(
+          RetraceCommand.builder()
+              .setMappingSupplier(mappingSupplier)
+              .setStackTrace(stackTrace)
+              .setRetracedStackTraceConsumer(
+                  retraced -> {
+                    assertEquals(1, retraced.size());
+                    assertEquals(retracedStackTraceLine, retraced.get(0));
+                  })
+              .build());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionStringTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionStringTest.java
new file mode 100644
index 0000000..016cf49
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionStringTest.java
@@ -0,0 +1,73 @@
+// 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.api;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.retrace.MappingPartitionMetadata;
+import com.android.tools.r8.retrace.ProguardMapPartitioner;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RetracePartitionStringTest extends RetraceApiTestBase {
+
+  public RetracePartitionStringTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  protected Class<? extends RetraceApiBinaryTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  public static class ApiTest implements RetraceApiBinaryTest {
+
+    private final String aMapping =
+        "com.foo.bar.baz -> a:\n"
+            + "# someCommentHere\n"
+            + "  int field -> c\n"
+            + "  void method() -> d";
+
+    private final String bMapping =
+        "com.android.google.r8 -> b:\n"
+            + " boolean otherField -> e\n"
+            + "  int otherMethod() -> f\n"
+            + "# otherCommentHere";
+
+    private final String header = "# { id: 'com.android.tools.r8.mapping', version: '2.0' }";
+
+    private final String mapping = header + "\n" + aMapping + "\n" + bMapping;
+
+    @Test
+    public void test() throws IOException {
+      ProguardMapProducer proguardMapProducer = ProguardMapProducer.fromString(mapping);
+      Map<String, String> partitions = new HashMap<>();
+      MappingPartitionMetadata metadataData =
+          ProguardMapPartitioner.builder(new DiagnosticsHandler() {})
+              .setProguardMapProducer(proguardMapProducer)
+              .setPartitionConsumer(
+                  partition ->
+                      partitions.put(
+                          partition.getKey(),
+                          new String(partition.getPayload(), StandardCharsets.UTF_8)))
+              .build()
+              .run();
+      assertNotNull(metadataData);
+      assertEquals(2, partitions.size());
+      assertEquals(aMapping, partitions.get("a"));
+      assertEquals(bMapping, partitions.get("b"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index 369d584..6df7b0a 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -10,18 +10,15 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.R8;
 import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestBuilder;
 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.graph.DexProgramClass;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -211,14 +208,10 @@
             .addKeepMainRule(R8.class)
             .addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
             .addKeepRules("-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
-            .addOptionsModification(
-                options ->
-                    options
-                        .getOpenClosedInterfacesOptions()
-                        .suppressZipFileAssignmentsToJavaLangAutoCloseable())
             .addDontWarnGoogle()
             .addDontWarnJavaxNullableAnnotation()
             .apply(this::configureHorizontalClassMerging)
+            .apply(this::suppressZipFileAssignmentsToJavaLangAutoCloseable)
             .compile()
             .graphInspector();
 
@@ -235,12 +228,8 @@
                     + " *** *(...); }")
             .addDontWarnGoogle()
             .addDontWarnJavaxNullableAnnotation()
-            .addOptionsModification(
-                options ->
-                    options
-                        .getOpenClosedInterfacesOptions()
-                        .suppressZipFileAssignmentsToJavaLangAutoCloseable())
             .apply(this::configureHorizontalClassMerging)
+            .apply(this::suppressZipFileAssignmentsToJavaLangAutoCloseable)
             .compile()
             .graphInspector();
     assertRetainedClassesEqual(referenceInspector, ifThenKeepClassMembersInspector);
@@ -258,12 +247,8 @@
                     + " *** *(...); }")
             .addDontWarnGoogle()
             .addDontWarnJavaxNullableAnnotation()
-            .addOptionsModification(
-                options ->
-                    options
-                        .getOpenClosedInterfacesOptions()
-                        .suppressZipFileAssignmentsToJavaLangAutoCloseable())
             .apply(this::configureHorizontalClassMerging)
+            .apply(this::suppressZipFileAssignmentsToJavaLangAutoCloseable)
             .compile()
             .graphInspector();
     assertRetainedClassesEqual(referenceInspector, ifThenKeepClassesWithMembersInspector);
@@ -283,12 +268,8 @@
                     + " <2> <3>(...); }")
             .addDontWarnGoogle()
             .addDontWarnJavaxNullableAnnotation()
-            .addOptionsModification(
-                options ->
-                    options
-                        .getOpenClosedInterfacesOptions()
-                        .suppressZipFileAssignmentsToJavaLangAutoCloseable())
             .apply(this::configureHorizontalClassMerging)
+            .apply(this::suppressZipFileAssignmentsToJavaLangAutoCloseable)
             .compile()
             .graphInspector();
     assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector);
@@ -334,10 +315,11 @@
         notInConditional);
   }
 
-  private void assertAllClassesAreSynthetics(Set<String> classNames) {
-    for (String className : classNames) {
-      ClassReference classReference = Reference.classFromTypeName(className);
-      assertTrue(className, SyntheticItemsTestUtils.isExternalSynthetic(classReference));
-    }
+  private void suppressZipFileAssignmentsToJavaLangAutoCloseable(R8TestBuilder<?> testBuilder) {
+    testBuilder.addOptionsModification(
+        options ->
+            options
+                .getOpenClosedInterfacesOptions()
+                .suppressZipFileAssignmentsToJavaLangAutoCloseable());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/enums/EnumArrayInAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/enums/EnumArrayInAnnotationTest.java
new file mode 100644
index 0000000..42bda91
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/enums/EnumArrayInAnnotationTest.java
@@ -0,0 +1,197 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.enums;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.ProguardVersion;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.TestState;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EnumArrayInAnnotationTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public boolean useGenericEnumsRule;
+
+  @Parameters(name = "{0}, use generic enums rule {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines("TEST_ONE", "TEST_TWO", "TEST_THREE");
+
+  public static Path r8cf;
+  public static Box<String> minifiedEnumName = new Box<>();
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    r8cf =
+        R8FullTestBuilder.create(new TestState(getStaticTemp()), Backend.CF)
+            .addInnerClasses(EnumArrayInAnnotationTest.class)
+            .addKeepMainRule(Main.class)
+            .addKeepEnumsRule()
+            .addKeepRuntimeVisibleAnnotations()
+            .compile()
+            .inspect(
+                inspector -> {
+                  assertThat(inspector.clazz(Enum.class), isPresentAndRenamed());
+                  assertThat(
+                      inspector.clazz(Enum.class).uniqueFieldWithName("TEST_ONE"),
+                      isPresentAndNotRenamed());
+                  assertThat(
+                      inspector.clazz(Enum.class).uniqueFieldWithName("TEST_TWO"),
+                      isPresentAndNotRenamed());
+                  assertThat(
+                      inspector.clazz(Enum.class).uniqueFieldWithName("TEST_THREE"),
+                      isPresentAndRenamed());
+                  minifiedEnumName.set(inspector.clazz(Enum.class).getFinalName());
+                })
+            .writeToZip();
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    assumeTrue(useGenericEnumsRule);
+    testForRuntime(parameters)
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assumeTrue(!parameters.isDexRuntime() || useGenericEnumsRule);
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .applyIf(
+            parameters.isCfRuntime() && useGenericEnumsRule,
+            TestShrinkerBuilder::addKeepEnumsRule,
+            parameters.isCfRuntime() && !useGenericEnumsRule,
+            builder ->
+                builder.addKeepRules(
+                    "-keepclassmembernames enum "
+                        + EnumArrayInAnnotationTest.Enum.class.getTypeName()
+                        + " { <fields>; }"),
+            builder -> {
+              // Do nothing for DEX.
+              assertTrue(useGenericEnumsRule); // Check not running twice.
+            })
+        .setMinApi(parameters.getApiLevel())
+        .addKeepRuntimeVisibleAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .inspect(
+            inspector -> {
+              assertThat(
+                  inspector.clazz(Enum.class).uniqueFieldWithName("TEST_ONE"),
+                  parameters.isCfRuntime() ? isPresentAndNotRenamed() : isPresentAndRenamed());
+              assertThat(
+                  inspector.clazz(Enum.class).uniqueFieldWithName("TEST_TWO"),
+                  parameters.isCfRuntime() ? isPresentAndNotRenamed() : isPresentAndRenamed());
+              assertThat(
+                  inspector.clazz(Enum.class).uniqueFieldWithName("TEST_THREE"),
+                  useGenericEnumsRule ? isPresentAndRenamed() : isPresentAndNotRenamed());
+            })
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8WithR8Input() throws Exception {
+    assumeTrue(useGenericEnumsRule);
+    testForR8(parameters.getBackend())
+        .addProgramFiles(r8cf)
+        .addKeepMainRule(Main.class)
+        .addKeepEnumsRule()
+        .setMinApi(parameters.getApiLevel())
+        .addKeepRuntimeVisibleAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .inspect(
+            inspector -> {
+              assertThat(
+                  inspector.clazz(minifiedEnumName.get()).uniqueFieldWithName("TEST_ONE"),
+                  parameters.isCfRuntime() ? isPresentAndNotRenamed() : isPresentAndRenamed());
+              assertThat(
+                  inspector.clazz(minifiedEnumName.get()).uniqueFieldWithName("TEST_TWO"),
+                  parameters.isCfRuntime() ? isPresentAndNotRenamed() : isPresentAndRenamed());
+            })
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testProguard() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForProguard(ProguardVersion.V7_0_0)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .applyIf(
+            useGenericEnumsRule,
+            TestShrinkerBuilder::addKeepEnumsRule,
+            builder ->
+                builder.addKeepRules(
+                    "-keepclassmembernames enum " + Enum.class.getTypeName() + " { <fields>; }"))
+        .addDontWarn(getClass())
+        .addKeepRuntimeVisibleAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .applyIf(
+            !useGenericEnumsRule && parameters.asCfRuntime().isOlderThan(CfVm.JDK11),
+            r -> r.assertFailureWithErrorThatThrows(ArrayStoreException.class),
+            !useGenericEnumsRule && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK11),
+            r -> r.assertFailureWithErrorThatThrows(EnumConstantNotPresentException.class),
+            r -> r.assertSuccessWithOutput(EXPECTED_RESULT));
+  }
+
+  public enum Enum {
+    TEST_ONE,
+    TEST_TWO,
+    TEST_THREE
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.TYPE})
+  public @interface MyAnnotation {
+
+    Enum[] value();
+  }
+
+  @MyAnnotation(value = {Enum.TEST_ONE, Enum.TEST_TWO})
+  public static class Main {
+
+    public static void main(String[] args) {
+      for (Enum enm : Main.class.getAnnotation(MyAnnotation.class).value()) {
+        System.out.println(enm);
+      }
+      System.out.println(Enum.TEST_THREE);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java
index 50f1954..5de0ff2 100644
--- a/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java
@@ -4,55 +4,159 @@
 
 package com.android.tools.r8.shaking.enums;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.ProguardVersion;
+import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.TestState;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.StringUtils;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class EnumInAnnotationTest extends TestBase {
 
-  private final TestParameters parameters;
+  @Parameter(0)
+  public TestParameters parameters;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameter(1)
+  public boolean useGenericEnumsRule;
+
+  @Parameters(name = "{0}, use generic enums rule {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
   }
 
-  public EnumInAnnotationTest(TestParameters parameters) {
-    this.parameters = parameters;
+  private static final String EXPECTED_RESULT = StringUtils.lines("TEST_ONE", "TEST_TWO");
+
+  public static Path r8cf;
+  public static Box<String> minifiedEnumName = new Box<>();
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    // Prepare a R8 processed JAR keeping Main with annotations and generic enum rule.
+    r8cf =
+        R8FullTestBuilder.create(new TestState(getStaticTemp()), Backend.CF)
+            .addInnerClasses(EnumInAnnotationTest.class)
+            .addKeepMainRule(Main.class)
+            .addKeepEnumsRule()
+            .addKeepRuntimeVisibleAnnotations()
+            .compile()
+            .inspect(
+                inspector -> {
+                  assertThat(inspector.clazz(Enum.class), isPresentAndRenamed());
+                  assertThat(
+                      inspector.clazz(Enum.class).uniqueFieldWithName("TEST_ONE"),
+                      isPresentAndNotRenamed());
+                  assertThat(
+                      inspector.clazz(Enum.class).uniqueFieldWithName("TEST_TWO"),
+                      isPresentAndRenamed());
+                  minifiedEnumName.set(inspector.clazz(Enum.class).getFinalName());
+                })
+            .writeToZip();
   }
 
   @Test
   public void testRuntime() throws Exception {
+    assumeTrue(useGenericEnumsRule);
     testForRuntime(parameters)
-        .addInnerClasses(EnumInAnnotationTest.class)
+        .addInnerClasses(getClass())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines("TEST_ONE");
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   @Test
   public void testR8() throws Exception {
+    assumeTrue(!parameters.isDexRuntime() || useGenericEnumsRule);
     testForR8(parameters.getBackend())
         .addInnerClasses(EnumInAnnotationTest.class)
         .addKeepMainRule(Main.class)
+        .addKeepEnumsRule()
         .applyIf(
-            parameters.isCfRuntime(),
+            parameters.isCfRuntime() && useGenericEnumsRule,
+            TestShrinkerBuilder::addKeepEnumsRule,
+            parameters.isCfRuntime() && !useGenericEnumsRule,
+            builder ->
+                builder.addKeepRules(
+                    "-keepclassmembernames class "
+                        + EnumInAnnotationTest.Enum.class.getTypeName()
+                        + " { <fields>; }"),
+            builder -> {
+              // Do nothing for DEX.
+              assertTrue(useGenericEnumsRule); // Check not running twice.
+            })
+        .setMinApi(parameters.getApiLevel())
+        .addKeepRuntimeVisibleAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .inspect(
+            inspector -> {
+              assertThat(
+                  inspector.clazz(Enum.class).uniqueFieldWithName("TEST_ONE"),
+                  parameters.isCfRuntime() ? isPresentAndNotRenamed() : isPresentAndRenamed());
+              assertThat(
+                  inspector.clazz(Enum.class).uniqueFieldWithName("TEST_TWO"),
+                  useGenericEnumsRule ? isPresentAndRenamed() : isPresentAndNotRenamed());
+            })
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8WithR8Input() throws Exception {
+    assumeTrue(useGenericEnumsRule);
+    testForR8(parameters.getBackend())
+        .addProgramFiles(r8cf)
+        .addKeepMainRule(Main.class)
+        .addKeepEnumsRule()
+        .setMinApi(parameters.getApiLevel())
+        .addKeepRuntimeVisibleAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .inspect(
+            inspector -> {
+              assertThat(
+                  inspector.clazz(minifiedEnumName.get()).uniqueFieldWithName("TEST_ONE"),
+                  parameters.isCfRuntime() ? isPresentAndNotRenamed() : isPresentAndRenamed());
+            })
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testProguard() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForProguard(ProguardVersion.V7_0_0)
+        .addInnerClasses(EnumInAnnotationTest.class)
+        .addKeepMainRule(Main.class)
+        .addKeepEnumsRule()
+        .applyIf(
+            useGenericEnumsRule,
+            TestShrinkerBuilder::addKeepEnumsRule,
             builder ->
                 builder.addKeepRules(
                     "-keepclassmembernames class " + Enum.class.getTypeName() + " { <fields>; }"))
-        .setMinApi(parameters.getApiLevel())
+        .addDontWarn(getClass())
         .addKeepRuntimeVisibleAnnotations()
         .compile()
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines("TEST_ONE");
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   public enum Enum {
@@ -72,6 +176,7 @@
 
     public static void main(String[] args) {
       System.out.println(Main.class.getAnnotation(MyAnnotation.class).value());
+      System.out.println(Enum.TEST_TWO);
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationWhereAnnotationIsDeadTest.java b/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationWhereAnnotationIsDeadTest.java
new file mode 100644
index 0000000..bc2e7ac
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationWhereAnnotationIsDeadTest.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.enums;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+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;
+
+@RunWith(Parameterized.class)
+public class EnumInAnnotationWhereAnnotationIsDeadTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private static final String EXPECTED_RESULT = StringUtils.lines("TEST_ONE");
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepEnumsRule()
+        .setMinApi(parameters.getApiLevel())
+        .addKeepRuntimeVisibleAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .inspect(
+            inspector -> {
+              assertThat(
+                  inspector.clazz(Enum.class).uniqueFieldWithName("TEST_ONE"),
+                  isPresentAndRenamed());
+              assertThat(inspector.clazz(Enum.class).uniqueFieldWithName("TEST_TWO"), isAbsent());
+            })
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  public enum Enum {
+    TEST_ONE,
+    TEST_TWO
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.TYPE})
+  public @interface MyAnnotation {
+
+    Enum value();
+  }
+
+  @MyAnnotation(value = Enum.TEST_ONE)
+  public static class ClassWithEnumAnnotation {}
+
+  public static class Main {
+
+    private static boolean alwaysFalse() {
+      return false;
+    }
+
+    public static void main(String[] args) {
+      if (alwaysFalse()) {
+        System.out.println(ClassWithEnumAnnotation.class.getAnnotation(MyAnnotation.class).value());
+      }
+      System.out.println(Enum.TEST_ONE);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
index 82d8fff..94bdbf1 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
@@ -47,11 +47,6 @@
         .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
         .addKeepRuleFiles(MAIN_KEEP)
         .addKeepRules(WHY_ARE_YOU_KEEPING_ALL)
-        .addOptionsModification(
-            options ->
-                options
-                    .getOpenClosedInterfacesOptions()
-                    .suppressZipFileAssignmentsToJavaLangAutoCloseable())
         .collectStdout()
         .compile()
         .assertStdoutThatMatches(containsString("referenced in keep rule"))
diff --git a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
index 20fda04..e7b5fe6 100644
--- a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
+++ b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
@@ -20,11 +20,19 @@
 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 JumboStringTest extends SmaliTestBase {
   private static Pair<StringBuilder, StringBuilder> builders;
-  private final TestParameters parameters;
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withOnlyDexRuntimeApiLevel().build();
+  }
 
   @BeforeClass
   public static void createBuilders() {
@@ -49,15 +57,6 @@
     builders = new Pair<>(builder, expectedBuilder);
   }
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().build();
-  }
-
-  public JumboStringTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
   @Test
   public void test() throws Exception {
     SmaliBuilder smaliBuilder = new SmaliBuilder(DEFAULT_CLASS_NAME);
@@ -82,16 +81,21 @@
     testForR8(parameters.getBackend())
         .addProgramDexFileData(smaliBuilder.compile())
         .addKeepMainRule(DEFAULT_CLASS_NAME)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(
+            options -> {
+              options.enableStringConcatenationOptimization = false;
+            })
         .run(parameters.getRuntime(), DEFAULT_CLASS_NAME)
         .assertSuccessWithOutput(builders.getSecond().toString())
-        .inspect(inspector -> {
-          ClassSubject main = inspector.clazz(DEFAULT_CLASS_NAME);
-          assertThat(main, isPresent());
-          MethodSubject method = main.uniqueMethodWithName(DEFAULT_METHOD_NAME);
-          assertThat(method, isPresent());
-          assertTrue(method.streamInstructions().anyMatch(InstructionSubject::isJumboString));
-        });
+        .inspect(
+            inspector -> {
+              ClassSubject main = inspector.clazz(DEFAULT_CLASS_NAME);
+              assertThat(main, isPresent());
+              MethodSubject method = main.uniqueMethodWithName(DEFAULT_METHOD_NAME);
+              assertThat(method, isPresent());
+              assertTrue(method.streamInstructions().anyMatch(InstructionSubject::isJumboString));
+            });
   }
 
   @Test
@@ -128,18 +132,23 @@
         .addProgramDexFileData(smaliBuilder.compile())
         .addKeepMainRule(DEFAULT_CLASS_NAME)
         .addKeepRules("-addconfigurationdebugging")
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(
+            options -> {
+              options.enableStringConcatenationOptimization = false;
+            })
         .run(parameters.getRuntime(), DEFAULT_CLASS_NAME)
         .assertSuccessWithOutput(builders.getSecond().toString())
-        .inspect(inspector -> {
-          ClassSubject main = inspector.clazz(DEFAULT_CLASS_NAME);
-          assertThat(main, isPresent());
-          MethodSubject method = main.uniqueMethodWithName(DEFAULT_METHOD_NAME);
-          assertThat(method, isPresent());
-          assertTrue(method.streamInstructions().anyMatch(InstructionSubject::isJumboString));
-          MethodSubject method2 = main.uniqueMethodWithName(DEFAULT_METHOD_NAME + "2");
-          assertThat(method2, isPresent());
-          assertTrue(method2.streamInstructions().anyMatch(InstructionSubject::isJumboString));
-        });
+        .inspect(
+            inspector -> {
+              ClassSubject main = inspector.clazz(DEFAULT_CLASS_NAME);
+              assertThat(main, isPresent());
+              MethodSubject method = main.uniqueMethodWithName(DEFAULT_METHOD_NAME);
+              assertThat(method, isPresent());
+              assertTrue(method.streamInstructions().anyMatch(InstructionSubject::isJumboString));
+              MethodSubject method2 = main.uniqueMethodWithName(DEFAULT_METHOD_NAME + "2");
+              assertThat(method2, isPresent());
+              assertTrue(method2.streamInstructions().anyMatch(InstructionSubject::isJumboString));
+            });
   }
 }
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index 10d96c1..56435ed 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -950,10 +950,7 @@
     for (int i = 1; i < count; i++) {
       // Build a new application with the Outliner class.
       originalApplication = processedApplication;
-      processedApplication =
-          processApplication(
-              originalApplication,
-              options.andThen(o -> o.testing.allowConflictingSyntheticTypes = true));
+      processedApplication = processApplication(originalApplication, options);
       assertEquals((i + 1) * 3, getNumberOfProgramClasses(processedApplication));
     }
 
diff --git a/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java b/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
deleted file mode 100644
index 67e965d..0000000
--- a/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
+++ /dev/null
@@ -1,176 +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.smali;
-
-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.assertTrue;
-
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexCode;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
-import java.nio.file.Path;
-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 RemoveWriteOfUnusedFieldsTest extends SmaliTestBase {
-
-  private final TestParameters parameters;
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().build();
-  }
-
-  public RemoveWriteOfUnusedFieldsTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  @Test
-  public void unreadStaticFieldsRemoved() throws Exception {
-    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
-
-    // All these static fields are set but never read.
-    builder.addStaticField("booleanField", "Z");
-    builder.addStaticField("byteField", "B");
-    builder.addStaticField("shortField", "S");
-    builder.addStaticField("intField", "I");
-    builder.addStaticField("longField", "J");
-    builder.addStaticField("floatField", "F");
-    builder.addStaticField("doubleField", "D");
-    builder.addStaticField("charField", "C");
-    builder.addStaticField("objectField", "Ljava/lang/Object;");
-    builder.addStaticField("stringField", "Ljava/lang/String;");
-    builder.addStaticField("testField", "LTest;");
-
-    builder.addStaticMethod("void", "test", ImmutableList.of(),
-        2,
-        "const               v0, 0", // single non-float typed zero (ie, int type)
-        "sput-boolean        v0, LTest;->booleanField:Z",
-        "sput-byte           v0, LTest;->byteField:B",
-        "sput-char           v0, LTest;->charField:C",
-        "sput-short          v0, LTest;->shortField:S",
-        "sput                v0, LTest;->intField:I",
-        "const               v0, 0", // float typed zero
-        "sput                v0, LTest;->floatField:F",
-        "const               v0, 0", // reference typed null
-        "sput-object         v0, LTest;->objectField:Ljava/lang/Object;",
-        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
-        "sput-object         v0, LTest;->testField:LTest;",
-        "const-wide          v0, 0", // wide typed long
-        "sput-wide           v0, LTest;->longField:J",
-        "const-wide          v0, 0", // wide typed double
-        "sput-wide           v0, LTest;->doubleField:D",
-        "return-void");
-
-    builder.addMainMethod(
-        0,
-        "    invoke-static       { }, LTest;->test()V",
-        "    return-void                             ");
-
-    AndroidApp input =
-        AndroidApp.builder().addDexProgramData(builder.compile(), Origin.unknown()).build();
-
-    Path inputPath = temp.getRoot().toPath().resolve("input.zip");
-    input.writeToZip(inputPath, OutputMode.DexIndexed);
-    ToolHelper.runArtNoVerificationErrors(inputPath.toString(), DEFAULT_CLASS_NAME);
-
-    testForR8(parameters.getBackend())
-        .addProgramFiles(inputPath)
-        .addKeepMainRule("Test")
-        .setMinApi(parameters.getRuntime())
-        .compile()
-        .inspect(
-            inspector -> {
-              MethodSubject method =
-                  inspector.clazz("Test").method("void", "test", ImmutableList.of());
-              assertThat(
-                  "Expected method to be removed entirely because it does not have side effects",
-                  method,
-                  not(isPresent()));
-            });
-  }
-
-  @Test
-  public void unreadInstanceFieldsRemoved() throws Exception {
-    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
-
-    // All these instance fields are set but never read.
-    builder.addInstanceField("booleanField", "Z");
-    builder.addInstanceField("byteField", "B");
-    builder.addInstanceField("shortField", "S");
-    builder.addInstanceField("intField", "I");
-    builder.addInstanceField("longField", "J");
-    builder.addInstanceField("floatField", "F");
-    builder.addInstanceField("doubleField", "D");
-    builder.addInstanceField("charField", "C");
-    builder.addInstanceField("objectField", "Ljava/lang/Object;");
-    builder.addInstanceField("stringField", "Ljava/lang/String;");
-    builder.addInstanceField("testField", "LTest;");
-
-    builder.addInstanceMethod("void", "test", ImmutableList.of(),
-        2,
-        "const               v0, 0",
-        "iput-boolean        v0, p0, LTest;->booleanField:Z",
-        "iput-byte           v0, p0, LTest;->byteField:B",
-        "iput-char           v0, p0, LTest;->charField:C",
-        "iput-short          v0, p0, LTest;->shortField:S",
-        "iput                v0, p0, LTest;->intField:I",
-        "const               v0, 0",
-        "iput                v0, p0, LTest;->floatField:F",
-        "const               v0, 0",
-        "iput-object         v0, p0, LTest;->objectField:Ljava/lang/Object;",
-        "iput-object         v0, p0, LTest;->stringField:Ljava/lang/String;",
-        "iput-object         v0, p0, LTest;->testField:LTest;",
-        "const-wide          v0, 0",
-        "iput-wide           v0, p0, LTest;->longField:J",
-        "const-wide          v0, 0",
-        "iput-wide           v0, p0, LTest;->doubleField:D",
-        "return-void");
-
-    builder.addInitializer(ImmutableList.of(), 0,
-        "invoke-direct {p0}, Ljava/lang/Object;-><init>()V",
-        "return-void"
-    );
-
-    builder.addMainMethod(
-        1,
-        "    new-instance         v0, LTest;",
-        "    invoke-direct        { v0 }, LTest;-><init>()V",
-        "    invoke-virtual       { v0 }, LTest;->test()V",
-        "    return-void                             ");
-
-    AndroidApp input =
-        AndroidApp.builder().addDexProgramData(builder.compile(), Origin.unknown()).build();
-
-    Path inputPath = temp.getRoot().toPath().resolve("input.zip");
-    input.writeToZip(inputPath, OutputMode.DexIndexed);
-    ToolHelper.runArtNoVerificationErrors(inputPath.toString(), DEFAULT_CLASS_NAME);
-
-    testForR8(parameters.getBackend())
-        .addProgramFiles(inputPath)
-        .addKeepMainRule("Test")
-        .addKeepRules("-keep class Test { void test(); }")
-        .setMinApi(parameters.getRuntime())
-        .compile()
-        .inspect(
-            inspector -> {
-              MethodSubject method =
-                  inspector.clazz("Test").method("void", "test", ImmutableList.of());
-              DexCode code = method.getMethod().getCode().asDexCode();
-              assertTrue(code.isEmptyVoidMethod());
-            });
-  }
-}
diff --git a/third_party/opensource-apps/systemui.tar.gz.sha1 b/third_party/opensource-apps/systemui.tar.gz.sha1
new file mode 100644
index 0000000..9d5b06c
--- /dev/null
+++ b/third_party/opensource-apps/systemui.tar.gz.sha1
@@ -0,0 +1 @@
+19afeb80f13ccaadddd62d9e1f8f2b1e894f99c8
\ 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 abfaa5e..da20d2e 100644
--- a/third_party/retrace/binary_compatibility.tar.gz.sha1
+++ b/third_party/retrace/binary_compatibility.tar.gz.sha1
@@ -1 +1 @@
-29e9654bef01ef4e00b0e44849b9073a02997b8b
\ No newline at end of file
+4408d86adfb21f32c2dcb93c2c8f2e392f0e0de0
\ No newline at end of file
diff --git a/tools/archive_desugar_jdk_libs.py b/tools/archive_desugar_jdk_libs.py
index 2f13541..10b300a 100755
--- a/tools/archive_desugar_jdk_libs.py
+++ b/tools/archive_desugar_jdk_libs.py
@@ -29,7 +29,8 @@
 import sys
 import utils
 
-VERSION_FILE = 'VERSION_JDK11.txt'
+VERSION_FILE_JDK8 = 'VERSION.txt'
+VERSION_FILE_JDK11 = 'VERSION_JDK11.txt'
 LIBRARY_NAME = 'desugar_jdk_libs'
 
 def ParseOptions(argv):
@@ -170,7 +171,10 @@
 
   with utils.TempDir() as checkout_dir:
     CloneDesugaredLibrary(options.github_account, checkout_dir)
-    version = GetVersion(os.path.join(checkout_dir, VERSION_FILE))
+    version = GetVersion(
+      os.path.join(
+        checkout_dir,
+        VERSION_FILE_JDK11 if variant == 'jdk11' else VERSION_FILE_JDK8))
 
     destination = archive.GetVersionDestination(
         'gs://', LIBRARY_NAME + '/' + version, is_main)
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 5962cc0..c6a4a19 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -357,19 +357,33 @@
     return True
   return False
 
-def prepare_wrapper(dist, temp, jdkhome):
-  wrapper_file = os.path.join(
+def prepare_r8_wrapper(dist, temp, jdkhome):
+  compile_with_javac(
+    dist,
+    temp,
+    jdkhome,
+    os.path.join(
       utils.REPO_ROOT,
-      'src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java')
+      'src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java'))
+
+def prepare_d8_wrapper(dist, temp, jdkhome):
+  compile_with_javac(
+    dist,
+    temp,
+    jdkhome,
+    os.path.join(
+      utils.REPO_ROOT,
+      'src/main/java/com/android/tools/r8/utils/CompileDumpD8.java'))
+
+def compile_with_javac(dist, temp, jdkhome, path):
   cmd = [
     jdk.GetJavacExecutable(jdkhome),
-    wrapper_file,
+    path,
     '-d', temp,
     '-cp', dist,
   ]
   utils.PrintCmd(cmd)
   subprocess.check_output(cmd)
-  return temp
 
 def is_hash(version):
   return len(version) == 40
@@ -396,7 +410,8 @@
     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)
-    wrapper_dir = prepare_wrapper(jar, temp, jdkhome)
+    prepare_r8_wrapper(jar, temp, jdkhome)
+    prepare_d8_wrapper(jar, temp, jdkhome)
     cmd = [jdk.GetJavaExecutable(jdkhome)]
     if args.debug_agent:
       if not args.nolib:
@@ -415,9 +430,9 @@
     if hasattr(args, 'properties'):
       cmd.extend(args.properties);
     cmd.extend(determine_properties(build_properties))
-    cmd.extend(['-cp', '%s:%s' % (wrapper_dir, jar)])
+    cmd.extend(['-cp', '%s:%s' % (temp, jar)])
     if compiler == 'd8':
-      cmd.append('com.android.tools.r8.D8')
+      cmd.append('com.android.tools.r8.utils.CompileDumpD8')
     if compiler == 'l8':
       cmd.append('com.android.tools.r8.L8')
     if compiler.startswith('r8'):
diff --git a/tools/git_utils.py b/tools/git_utils.py
index 1e3e7c5..1ce0a0a 100644
--- a/tools/git_utils.py
+++ b/tools/git_utils.py
@@ -16,4 +16,4 @@
   cmd = ['git', 'rev-parse', revision_from]
   utils.PrintCmd(cmd)
   with utils.ChangedWorkingDirectory(checkout_dir):
-    return subprocess.check_output(cmd).strip()
+    return subprocess.check_output(cmd).strip().decode('utf-8')
diff --git a/tools/trigger.py b/tools/trigger.py
index d467a81..d87cad9 100755
--- a/tools/trigger.py
+++ b/tools/trigger.py
@@ -22,7 +22,8 @@
 #   triggers: "BUILDER_NAME"
 TRIGGERS_RE = r'^  triggers: "(\w.*)"'
 
-DESUGAR_BOT = 'lib_desugar-archive'
+DESUGAR_JDK11_BOT = 'lib_desugar-archive-jdk11'
+DESUGAR_JDK8_BOT = 'lib_desugar-archive-jdk8'
 
 def ParseOptions():
   result = optparse.OptionParser()
@@ -33,9 +34,13 @@
                     help='Run the specified cl on the bots. This should be '
                     'the full url, e.g., '
                     'https://r8-review.googlesource.com/c/r8/+/37420/1')
-  result.add_option('--desugar',
-                    help='Run the library desugar and archiving bot.',
+  result.add_option('--desugar-jdk11',
+                    help='Run the jdk11 library desugar and archiving bot.',
                     default=False, action='store_true')
+  result.add_option('--desugar-jdk8',
+                    help='Run the jdk8 library desugar and archiving bot.',
+                    default=False, action='store_true')
+
   result.add_option('--builder', help='Trigger specific builder')
   return result.parse_args()
 
@@ -60,7 +65,8 @@
         else:
           assert 'release' not in builder, builder
           main_builders.append(builder)
-  print('Desugar builder:\n  ' + DESUGAR_BOT)
+  print('Desugar jdk11 builder:\n  ' + DESUGAR_JDK11_BOT)
+  print('Desugar jdk8 builder:\n  ' + DESUGAR_JDK8_BOT)
   print('Main builders:\n  ' + '\n  '.join(main_builders))
   print('Release builders:\n  ' + '\n  '.join(release_builders))
   return (main_builders, release_builders)
@@ -84,7 +90,8 @@
 
 def Main():
   (options, args) = ParseOptions()
-  if len(args) != 1 and not options.cl and not options.desugar:
+  desugar = options.desugar_jdk11 or options.desugar_jdk8
+  if len(args) != 1 and not options.cl and not desugar:
     print('Takes exactly one argument, the commit to run')
     return 1
 
@@ -92,19 +99,20 @@
     print('You can\'t run cls on the release bots')
     return 1
 
-  if options.cl and options.desugar:
+  if options.cl and desugar:
     print('You can\'t run cls on the desugar bot')
     return 1
 
-  commit = None if (options.cl or options.desugar)  else args[0]
+  commit = None if (options.cl or desugar)  else args[0]
   (main_builders, release_builders) = get_builders()
   builders = release_builders if options.release else main_builders
   if options.builder:
     builder = options.builder
     assert builder in main_builders or builder in release_builders
     builders = [options.builder]
-  if options.desugar:
-    builders = [DESUGAR_BOT]
+  if desugar:
+    assert options.desugar_jdk8 or options.desugar_jdk11
+    builders = [DESUGAR_JDK8_BOT if options.desugar_jdk8 else DESUGAR_JDK11_BOT]
     commit = git_utils.GetHeadRevision(utils.REPO_ROOT, use_main=True)
   if options.cl:
     trigger_cl(builders, options.cl)
diff --git a/tools/utils.py b/tools/utils.py
index 363cc18..cd41ad0 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -66,7 +66,7 @@
 R8LIB_TESTS_DEPS_JAR = R8_TESTS_DEPS_JAR
 MAVEN_ZIP = os.path.join(LIBS, 'r8.zip')
 MAVEN_ZIP_LIB = os.path.join(LIBS, 'r8lib.zip')
-LIBRARY_DESUGAR_CONVERSIONS_ZIP = os.path.join(LIBS, 'library_desugar_conversions.zip')
+LIBRARY_DESUGAR_CONVERSIONS_ZIP = os.path.join(LIBS, 'library_desugar_conversions.jar')
 
 DESUGAR_CONFIGURATION = os.path.join(
       'src', 'library_desugar', 'desugar_jdk_libs.json')