Merge commit 'f33d3ca958fdb936e8053353216dde31b682d202' into dev-release
diff --git a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
index 089a293..b02dc00 100644
--- a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
+++ b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
@@ -28,24 +28,23 @@
           "j$/nio/file/spi/FileTypeDetector",
           "j$/nio/file/Path",
           "j$/nio/file/WatchEvent",
-          "j$/nio/file/OpenOption");
+          "j$/nio/file/OpenOption",
+          "j$/nio/file/attribute/FileAttribute");
 
-  static Map<String, String> getJavaWrapConvertOwnerMap() {
-    return computeConvertOwnerMap("$Wrapper");
-  }
-
-  static Map<String, String> getJ$WrapConvertOwnerMap() {
-    return computeConvertOwnerMap("$VivifiedWrapper");
-  }
-
-  private static HashMap<String, String> computeConvertOwnerMap(String suffix) {
+  static Map<String, String> getWrapConvertOwnerMap() {
     HashMap<String, String> map = new HashMap<>();
     for (String theEnum : ENUM_WRAP_CONVERT_OWNER) {
       map.put(theEnum, theEnum + "$EnumConversion");
+      map.put(withJavaPrefix(theEnum), theEnum + "$EnumConversion");
     }
     for (String owner : WRAP_CONVERT_OWNER) {
-      map.put(owner, owner + suffix);
+      map.put(withJavaPrefix(owner), owner + "$Wrapper");
+      map.put(owner, owner + "$VivifiedWrapper");
     }
     return map;
   }
+
+  private static String withJavaPrefix(String descriptor) {
+    return "java" + descriptor.substring(2);
+  }
 }
diff --git a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriter.java b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriter.java
index 0848c09..95a5ecc 100644
--- a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriter.java
+++ b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriter.java
@@ -39,10 +39,8 @@
   }
 
   private final CustomConversionVersion legacy;
-  private final Map<String, String> javaWrapConvertOwnerMap =
-      CustomConversionAsmRewriteDescription.getJavaWrapConvertOwnerMap();
-  private final Map<String, String> j$WrapConvertOwnerMap =
-      CustomConversionAsmRewriteDescription.getJ$WrapConvertOwnerMap();
+  private final Map<String, String> wrapConvertOwnerMap =
+      CustomConversionAsmRewriteDescription.getWrapConvertOwnerMap();
 
   public static void generateJars(Path jar, Path outputDirectory) throws IOException {
     for (CustomConversionVersion version : CustomConversionVersion.values()) {
@@ -148,30 +146,14 @@
 
     private void convertInvoke(int opcode, String owner, String descriptor, boolean isInterface) {
       String firstArg = extractFirstArg(descriptor);
-      assert sameBaseName(firstArg, owner);
-      if (!javaWrapConvertOwnerMap.containsKey(owner)
-          || !j$WrapConvertOwnerMap.containsKey(owner)
+      if (!wrapConvertOwnerMap.containsKey(firstArg)
           || !(firstArg.startsWith("java") || firstArg.startsWith("j$"))) {
         throw new RuntimeException(
             "Cannot transform wrap_convert method for " + firstArg + " (owner: " + owner + ")");
       }
-      if (firstArg.startsWith("java")) {
-        String newOwner = javaWrapConvertOwnerMap.get(owner);
-        super.visitMethodInsn(opcode, newOwner, CONVERT, descriptor, isInterface);
-        return;
-      }
-      assert firstArg.startsWith("j$");
-      String newOwner = j$WrapConvertOwnerMap.get(owner);
+      String newOwner = wrapConvertOwnerMap.get(firstArg);
       super.visitMethodInsn(opcode, newOwner, CONVERT, descriptor, isInterface);
     }
 
-    private boolean sameBaseName(String firstArg, String owner) {
-      assert owner.startsWith("j$");
-      if (firstArg.equals(owner)) {
-        return true;
-      }
-      String javaName = owner.replace("j$", "java");
-      return firstArg.equals(javaName);
-    }
   }
 }
diff --git a/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java b/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java
index 027d74f..42a3f91 100644
--- a/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java
+++ b/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java
@@ -9,8 +9,13 @@
 import java.nio.channels.DesugarChannels;
 import java.nio.channels.FileChannel;
 import java.nio.channels.SeekableByteChannel;
+import java.nio.file.CopyOption;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
 import java.nio.file.attribute.FileAttribute;
 import java.nio.file.spi.FileSystemProvider;
 import java.util.Set;
@@ -28,6 +33,45 @@
   }
 
   @Override
+  public void copy(Path source, Path target, CopyOption... options) throws IOException {
+    if (!containsCopyOption(options, StandardCopyOption.REPLACE_EXISTING) && Files.exists(target)) {
+      throw new FileAlreadyExistsException(target.toString());
+    }
+    if (containsCopyOption(options, StandardCopyOption.ATOMIC_MOVE)) {
+      throw new UnsupportedOperationException("Unsupported copy option");
+    }
+    super.copy(source, target, options);
+  }
+
+  @Override
+  public void move(Path source, Path target, CopyOption... options) throws IOException {
+    if (!containsCopyOption(options, StandardCopyOption.REPLACE_EXISTING) && Files.exists(target)) {
+      throw new FileAlreadyExistsException(target.toString());
+    }
+    if (containsCopyOption(options, StandardCopyOption.COPY_ATTRIBUTES)) {
+      throw new UnsupportedOperationException("Unsupported copy option");
+    }
+    super.move(source, target, options);
+  }
+
+  private boolean containsCopyOption(CopyOption[] options, CopyOption option) {
+    for (CopyOption copyOption : options) {
+      if (copyOption == option) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
+    if (dir.getParent() != null && !Files.exists(dir.getParent())) {
+      throw new NoSuchFileException(dir.toString());
+    }
+    super.createDirectory(dir, attrs);
+  }
+
+  @Override
   public SeekableByteChannel newByteChannel(
       Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
     if (path.toFile().isDirectory()) {
diff --git a/src/library_desugar/java/j$/nio/file/attribute/AclFileAttributeView.java b/src/library_desugar/java/j$/nio/file/attribute/AclFileAttributeView.java
new file mode 100644
index 0000000..1b9da3d
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/attribute/AclFileAttributeView.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package j$.nio.file.attribute;
+
+public class AclFileAttributeView {}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributeView.java b/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributeView.java
index 774cf2c..7d2612f 100644
--- a/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributeView.java
+++ b/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributeView.java
@@ -5,13 +5,4 @@
 package j$.nio.file.attribute;
 
 public class BasicFileAttributeView extends FileAttributeView {
-  public static java.nio.file.attribute.BasicFileAttributeView wrap_convert(
-      j$.nio.file.attribute.BasicFileAttributeView fileAttributeView) {
-    return null;
-  }
-
-  public static j$.nio.file.attribute.BasicFileAttributeView wrap_convert(
-      java.nio.file.attribute.BasicFileAttributeView fileAttributeView) {
-    return null;
-  }
 }
diff --git a/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributes.java b/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributes.java
index dd91a95..69b0ca6 100644
--- a/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributes.java
+++ b/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributes.java
@@ -5,13 +5,4 @@
 package j$.nio.file.attribute;
 
 public class BasicFileAttributes {
-  public static java.nio.file.attribute.BasicFileAttributes wrap_convert(
-      j$.nio.file.attribute.BasicFileAttributes fileAttributes) {
-    return null;
-  }
-
-  public static j$.nio.file.attribute.BasicFileAttributes wrap_convert(
-      java.nio.file.attribute.BasicFileAttributes fileAttributes) {
-    return null;
-  }
 }
diff --git a/src/library_desugar/java/j$/nio/file/attribute/DosFileAttributeView.java b/src/library_desugar/java/j$/nio/file/attribute/DosFileAttributeView.java
new file mode 100644
index 0000000..899c1e0
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/attribute/DosFileAttributeView.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package j$.nio.file.attribute;
+
+public class DosFileAttributeView extends FileAttributeView {}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/DosFileAttributes.java b/src/library_desugar/java/j$/nio/file/attribute/DosFileAttributes.java
new file mode 100644
index 0000000..ae2200c
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/attribute/DosFileAttributes.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package j$.nio.file.attribute;
+
+public class DosFileAttributes {}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/FileAttribute.java b/src/library_desugar/java/j$/nio/file/attribute/FileAttribute.java
new file mode 100644
index 0000000..b971b05
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/attribute/FileAttribute.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package j$.nio.file.attribute;
+
+public interface FileAttribute<T> {
+
+  String name();
+
+  T value();
+}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/FileAttributeView.java b/src/library_desugar/java/j$/nio/file/attribute/FileAttributeView.java
index 34046eb3..df33474 100644
--- a/src/library_desugar/java/j$/nio/file/attribute/FileAttributeView.java
+++ b/src/library_desugar/java/j$/nio/file/attribute/FileAttributeView.java
@@ -5,13 +5,4 @@
 package j$.nio.file.attribute;
 
 public class FileAttributeView {
-  public static java.nio.file.attribute.FileAttributeView wrap_convert(
-      j$.nio.file.attribute.FileAttributeView fileAttributeView) {
-    return null;
-  }
-
-  public static j$.nio.file.attribute.FileAttributeView wrap_convert(
-      java.nio.file.attribute.FileAttributeView fileAttributeView) {
-    return null;
-  }
 }
diff --git a/src/library_desugar/java/j$/nio/file/attribute/FileAttributeWrapperMethods.java b/src/library_desugar/java/j$/nio/file/attribute/FileAttributeWrapperMethods.java
new file mode 100644
index 0000000..dfff944
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/attribute/FileAttributeWrapperMethods.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package j$.nio.file.attribute;
+
+public class FileAttributeWrapperMethods {
+
+  public static java.nio.file.attribute.FileAttribute wrap_convert(
+      j$.nio.file.attribute.FileAttribute fileAttribute) {
+    return null;
+  }
+
+  public static j$.nio.file.attribute.FileAttribute wrap_convert(
+      java.nio.file.attribute.FileAttribute fileAttribute) {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/FileOwnerAttributeView.java b/src/library_desugar/java/j$/nio/file/attribute/FileOwnerAttributeView.java
index 34a637d..cbce1b4 100644
--- a/src/library_desugar/java/j$/nio/file/attribute/FileOwnerAttributeView.java
+++ b/src/library_desugar/java/j$/nio/file/attribute/FileOwnerAttributeView.java
@@ -5,13 +5,4 @@
 package j$.nio.file.attribute;
 
 public class FileOwnerAttributeView extends FileAttributeView {
-  public static java.nio.file.attribute.FileOwnerAttributeView wrap_convert(
-      j$.nio.file.attribute.FileOwnerAttributeView fileAttributeView) {
-    return null;
-  }
-
-  public static j$.nio.file.attribute.FileOwnerAttributeView wrap_convert(
-      java.nio.file.attribute.FileOwnerAttributeView fileAttributeView) {
-    return null;
-  }
 }
diff --git a/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributeView.java b/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributeView.java
index bb24cb7..04f4f04 100644
--- a/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributeView.java
+++ b/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributeView.java
@@ -5,13 +5,4 @@
 package j$.nio.file.attribute;
 
 public class PosixFileAttributeView extends FileAttributeView {
-  public static java.nio.file.attribute.PosixFileAttributeView wrap_convert(
-      j$.nio.file.attribute.PosixFileAttributeView fileAttributeView) {
-    return null;
-  }
-
-  public static j$.nio.file.attribute.PosixFileAttributeView wrap_convert(
-      java.nio.file.attribute.PosixFileAttributeView fileAttributeView) {
-    return null;
-  }
 }
diff --git a/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributes.java b/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributes.java
index 5e61814..d6e9796 100644
--- a/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributes.java
+++ b/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributes.java
@@ -5,13 +5,4 @@
 package j$.nio.file.attribute;
 
 public class PosixFileAttributes extends BasicFileAttributes {
-  public static java.nio.file.attribute.PosixFileAttributes wrap_convert(
-      j$.nio.file.attribute.PosixFileAttributes fileAttributes) {
-    return null;
-  }
-
-  public static j$.nio.file.attribute.PosixFileAttributes wrap_convert(
-      java.nio.file.attribute.PosixFileAttributes fileAttributes) {
-    return null;
-  }
 }
diff --git a/src/library_desugar/java/j$/nio/file/attribute/UserDefinedFileAttributeView.java b/src/library_desugar/java/j$/nio/file/attribute/UserDefinedFileAttributeView.java
new file mode 100644
index 0000000..490fcec
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/attribute/UserDefinedFileAttributeView.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package j$.nio.file.attribute;
+
+public class UserDefinedFileAttributeView {}
diff --git a/src/library_desugar/java/java/nio/file/FileApiFlips.java b/src/library_desugar/java/java/nio/file/FileApiFlips.java
index 9c8a46a..3ad0a61 100644
--- a/src/library_desugar/java/java/nio/file/FileApiFlips.java
+++ b/src/library_desugar/java/java/nio/file/FileApiFlips.java
@@ -23,15 +23,21 @@
     if (attributesClass == j$.nio.file.attribute.BasicFileAttributes.class) {
       return java.nio.file.attribute.BasicFileAttributes.class;
     }
-    if (attributesClass == j$.nio.file.attribute.PosixFileAttributes.class) {
-      return java.nio.file.attribute.PosixFileAttributes.class;
-    }
     if (attributesClass == java.nio.file.attribute.BasicFileAttributes.class) {
       return j$.nio.file.attribute.BasicFileAttributes.class;
     }
+    if (attributesClass == j$.nio.file.attribute.PosixFileAttributes.class) {
+      return java.nio.file.attribute.PosixFileAttributes.class;
+    }
     if (attributesClass == java.nio.file.attribute.PosixFileAttributes.class) {
       return j$.nio.file.attribute.PosixFileAttributes.class;
     }
+    if (attributesClass == java.nio.file.attribute.DosFileAttributes.class) {
+      return j$.nio.file.attribute.DosFileAttributes.class;
+    }
+    if (attributesClass == j$.nio.file.attribute.DosFileAttributes.class) {
+      return java.nio.file.attribute.DosFileAttributes.class;
+    }
     throw exception("java.nio.file.attribute.BasicFileAttributes", attributesClass);
   }
 
@@ -42,21 +48,41 @@
     if (attributeView == j$.nio.file.attribute.BasicFileAttributeView.class) {
       return java.nio.file.attribute.BasicFileAttributeView.class;
     }
-    if (attributeView == j$.nio.file.attribute.PosixFileAttributeView.class) {
-      return java.nio.file.attribute.PosixFileAttributeView.class;
-    }
-    if (attributeView == j$.nio.file.attribute.FileOwnerAttributeView.class) {
-      return java.nio.file.attribute.FileOwnerAttributeView.class;
-    }
     if (attributeView == java.nio.file.attribute.BasicFileAttributeView.class) {
       return j$.nio.file.attribute.BasicFileAttributeView.class;
     }
+    if (attributeView == j$.nio.file.attribute.PosixFileAttributeView.class) {
+      return java.nio.file.attribute.PosixFileAttributeView.class;
+    }
     if (attributeView == java.nio.file.attribute.PosixFileAttributeView.class) {
       return j$.nio.file.attribute.PosixFileAttributeView.class;
     }
+    if (attributeView == j$.nio.file.attribute.FileOwnerAttributeView.class) {
+      return java.nio.file.attribute.FileOwnerAttributeView.class;
+    }
     if (attributeView == java.nio.file.attribute.FileOwnerAttributeView.class) {
       return j$.nio.file.attribute.FileOwnerAttributeView.class;
     }
+    // The following ones don't seem to work on any version in Android, but it's still possible
+    // for generic java code to use them and get null as a result.
+    if (attributeView == java.nio.file.attribute.DosFileAttributeView.class) {
+      return j$.nio.file.attribute.DosFileAttributeView.class;
+    }
+    if (attributeView == j$.nio.file.attribute.DosFileAttributeView.class) {
+      return java.nio.file.attribute.DosFileAttributeView.class;
+    }
+    if (attributeView == java.nio.file.attribute.UserDefinedFileAttributeView.class) {
+      return j$.nio.file.attribute.UserDefinedFileAttributeView.class;
+    }
+    if (attributeView == j$.nio.file.attribute.UserDefinedFileAttributeView.class) {
+      return java.nio.file.attribute.UserDefinedFileAttributeView.class;
+    }
+    if (attributeView == java.nio.file.attribute.AclFileAttributeView.class) {
+      return j$.nio.file.attribute.AclFileAttributeView.class;
+    }
+    if (attributeView == j$.nio.file.attribute.AclFileAttributeView.class) {
+      return java.nio.file.attribute.AclFileAttributeView.class;
+    }
     throw exception("java.nio.file.attribute.FileAttributeView", attributeView);
   }
 
diff --git a/src/library_desugar/java/java/nio/file/PathApiFlips.java b/src/library_desugar/java/java/nio/file/PathApiFlips.java
index 7314b7f..8747fae 100644
--- a/src/library_desugar/java/java/nio/file/PathApiFlips.java
+++ b/src/library_desugar/java/java/nio/file/PathApiFlips.java
@@ -104,11 +104,6 @@
     public void remove() {
       iterator.remove();
     }
-
-    @Override
-    public void forEachRemaining(Consumer<? super T> action) {
-      iterator.forEachRemaining(path -> action.accept(convertPath(path)));
-    }
   }
 
   public static class DirectoryStreamPathWrapper<T> implements DirectoryStream<T> {
diff --git a/src/library_desugar/java/java/nio/file/attribute/FileAttributeConversions.java b/src/library_desugar/java/java/nio/file/attribute/FileAttributeConversions.java
index c895d52..18c575f 100644
--- a/src/library_desugar/java/java/nio/file/attribute/FileAttributeConversions.java
+++ b/src/library_desugar/java/java/nio/file/attribute/FileAttributeConversions.java
@@ -4,6 +4,10 @@
 
 package java.nio.file.attribute;
 
+import java.nio.file.FileApiFlips;
+import java.util.Collections;
+import java.util.Set;
+
 public class FileAttributeConversions {
 
   public static java.nio.file.attribute.FileTime convert(j$.nio.file.attribute.FileTime fileTime) {
@@ -20,4 +24,58 @@
     return j$.nio.file.attribute.FileTime.fromMillis(fileTime.toMillis());
   }
 
+  /** A FileAttribute usually holds an immutable set of non-null posix permissions. */
+  public static java.nio.file.attribute.FileAttribute<?> convert(
+      j$.nio.file.attribute.FileAttribute<?> attribute) {
+    if (attribute == null) {
+      return null;
+    }
+    if (isPosixPermissionAttributes(attribute.value())) {
+      return new java.nio.file.attribute.FileAttribute<Object>() {
+        public String name() {
+          return "posix:permissions";
+        }
+
+        public Object value() {
+          return Collections.unmodifiableSet(
+              FileApiFlips.flipPosixPermissionSet((Set<?>) attribute.value()));
+        }
+      };
+    }
+    return j$.nio.file.attribute.FileAttributeWrapperMethods.wrap_convert(attribute);
+  }
+
+  public static j$.nio.file.attribute.FileAttribute<?> convert(
+      java.nio.file.attribute.FileAttribute<?> attribute) {
+    if (attribute == null) {
+      return null;
+    }
+    if (isPosixPermissionAttributes(attribute.value())) {
+      return new j$.nio.file.attribute.FileAttribute<Object>() {
+        public String name() {
+          return "posix:permissions";
+        }
+
+        public Object value() {
+          return Collections.unmodifiableSet(
+              FileApiFlips.flipPosixPermissionSet((Set<?>) attribute.value()));
+        }
+      };
+    }
+    return j$.nio.file.attribute.FileAttributeWrapperMethods.wrap_convert(attribute);
+  }
+
+  private static boolean isPosixPermissionAttributes(Object value) {
+    if (value instanceof java.util.Set) {
+      Set<?> set = (java.util.Set<?>) value;
+      if (!set.isEmpty()) {
+        Object guineaPig = set.iterator().next();
+        if (guineaPig instanceof java.nio.file.attribute.PosixFilePermission
+            || guineaPig instanceof j$.nio.file.attribute.PosixFilePermission) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
 }
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index 244aa34..f4a3aef 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -233,6 +233,7 @@
         "java.util.Map java.nio.file.spi.FileSystemProvider#readAttributes(java.nio.file.Path, java.lang.String, java.nio.file.LinkOption[])" : [-1, "java.util.Map java.nio.file.FileApiFlips#flipMapWithMaybeFileTimeValues(java.util.Map)"],
         "java.lang.Iterable java.nio.file.FileSystem#getRootDirectories()": [-1, "java.lang.Iterable java.nio.file.PathApiFlips#flipIterablePath(java.lang.Iterable)"],
         "java.util.Iterator java.nio.file.Path#iterator()": [-1, "java.util.Iterator java.nio.file.PathApiFlips#flipIteratorPath(java.util.Iterator)"],
+        "int java.nio.file.Path#compareTo(java.lang.Object)": [0, "java.lang.Object java.nio.file.PathApiFlips#convertPath(java.lang.Object)"],
         "java.nio.file.DirectoryStream java.nio.file.spi.FileSystemProvider#newDirectoryStream(java.nio.file.Path, java.nio.file.DirectoryStream$Filter)": [-1, "java.nio.file.DirectoryStream java.nio.file.PathApiFlips#flipDirectoryStreamPath(java.nio.file.DirectoryStream)", 1, "java.nio.file.DirectoryStream$Filter java.nio.file.PathApiFlips#flipDirectoryStreamFilterPath(java.nio.file.DirectoryStream$Filter)"],
         "void java.nio.file.spi.FileSystemProvider#setAttribute(java.nio.file.Path, java.lang.String, java.lang.Object, java.nio.file.LinkOption[])": [2, "java.lang.Object java.nio.file.FileApiFlips#flipMaybeFileTime(java.lang.Object)"],
         "java.nio.channels.FileChannel java.nio.channels.FileChannel#open(java.nio.file.Path, java.util.Set, java.nio.file.attribute.FileAttribute[])" : [1, "java.util.Set java.nio.file.FileApiFlips#flipOpenOptionSet(java.util.Set)"]
@@ -257,17 +258,21 @@
         "java.nio.file.CopyOption",
         "java.nio.file.StandardCopyOption",
         "java.nio.file.attribute.GroupPrincipal",
-        "java.nio.file.attribute.FileAttribute",
         "java.nio.file.attribute.UserPrincipal",
         "java.nio.file.FileStore",
         "java.nio.file.attribute.FileStoreAttributeView",
         "java.nio.file.attribute.PosixFilePermission",
         "java.nio.file.attribute.BasicFileAttributes",
+        "java.nio.file.attribute.DosFileAttributes",
         "java.nio.file.attribute.PosixFileAttributes",
+        "java.nio.file.attribute.FileAttribute",
         "java.nio.file.attribute.FileAttributeView",
         "java.nio.file.attribute.FileOwnerAttributeView",
+        "java.nio.file.attribute.DosFileAttributeView",
         "java.nio.file.attribute.PosixFileAttributeView",
-        "java.nio.file.attribute.BasicFileAttributeView"
+        "java.nio.file.attribute.BasicFileAttributeView",
+        "java.nio.file.attribute.AclFileAttributeView",
+        "java.nio.file.attribute.UserDefinedFileAttributeView"
       ],
       "wrapper_conversion_excluding": {
         "java.nio.channels.AsynchronousFileChannel": [
@@ -277,7 +282,8 @@
         ]
       },
       "custom_conversion": {
-        "java.nio.file.attribute.FileTime": "java.nio.file.attribute.FileAttributeConversions"
+        "java.nio.file.attribute.FileTime": "java.nio.file.attribute.FileAttributeConversions",
+        "java.nio.file.attribute.FileAttribute": "java.nio.file.attribute.FileAttributeConversions"
       }
     },
     {
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
index c6d8571..4337302 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -6,32 +6,28 @@
 
 import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 
-import com.android.tools.r8.dex.code.CfOrDexInstruction;
 import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
 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.Code;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexCode.TryHandler;
+import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexLibraryClass;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramDefinition;
-import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ThrowExceptionCode;
-import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.CommittedItems;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Sets;
 import java.util.Arrays;
-import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -45,108 +41,16 @@
  */
 public class ApiReferenceStubber {
 
-  private class ReferencesToApiLevelUseRegistry extends UseRegistry<ProgramMethod> {
-
-    public ReferencesToApiLevelUseRegistry(ProgramMethod context) {
-      super(appView, context);
-    }
-
-    @Override
-    public void registerInitClass(DexType type) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void registerInvokeVirtual(DexMethod method) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void registerInvokeDirect(DexMethod method) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void registerInvokeStatic(DexMethod method) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void registerInvokeInterface(DexMethod method) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void registerInvokeSuper(DexMethod method) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void registerInstanceFieldRead(DexField field) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void registerInstanceFieldWrite(DexField field) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void registerStaticFieldRead(DexField field) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void registerStaticFieldWrite(DexField field) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void registerTypeReference(DexType type) {
-      checkReferenceToLibraryClass(type);
-    }
-
-    @Override
-    public void registerInstanceOf(DexType type) {
-      checkReferenceToLibraryClass(type);
-    }
-
-    @Override
-    public void registerConstClass(
-        DexType type,
-        ListIterator<? extends CfOrDexInstruction> iterator,
-        boolean ignoreCompatRules) {
-      checkReferenceToLibraryClass(type);
-    }
-
-    @Override
-    public void registerCheckCast(DexType type, boolean ignoreCompatRules) {
-      checkReferenceToLibraryClass(type);
-    }
-
-    @Override
-    public void registerExceptionGuard(DexType guard) {
-      checkReferenceToLibraryClass(guard);
-    }
-
-    private void checkReferenceToLibraryClass(DexReference reference) {
-      DexType rewrittenType = appView.graphLens().lookupType(reference.getContextType());
-      findReferencedLibraryClasses(rewrittenType, getContext().getContextClass());
-    }
-  }
-
   private final AppView<?> appView;
   private final Map<DexLibraryClass, Set<ProgramDefinition>> referencingContexts =
       new ConcurrentHashMap<>();
   private final Set<DexLibraryClass> libraryClassesToMock = Sets.newConcurrentHashSet();
   private final Set<DexType> seenTypes = Sets.newConcurrentHashSet();
   private final AndroidApiLevelCompute apiLevelCompute;
-  private final DexItemFactory factory;
 
   public ApiReferenceStubber(AppView<?> appView) {
     this.appView = appView;
     apiLevelCompute = appView.apiLevelCompute();
-    factory = appView.dexItemFactory();
   }
 
   public void run(ExecutorService executorService) throws ExecutionException {
@@ -195,7 +99,18 @@
         .forEach(superType -> findReferencedLibraryClasses(superType, clazz));
     clazz.forEachProgramMethodMatching(
         DexEncodedMethod::hasCode,
-        method -> method.registerCodeReferences(new ReferencesToApiLevelUseRegistry(method)));
+        method -> {
+          Code code = method.getDefinition().getCode();
+          if (!code.isDexCode()) {
+            return;
+          }
+          for (TryHandler handler : code.asDexCode().getHandlers()) {
+            for (TypeAddrPair pair : handler.pairs) {
+              DexType rewrittenType = appView.graphLens().lookupType(pair.getType());
+              findReferencedLibraryClasses(rewrittenType, clazz);
+            }
+          }
+        });
   }
 
   private void findReferencedLibraryClasses(DexType type, DexProgramClass context) {
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 293e189..f00f611 100644
--- a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
@@ -100,7 +100,11 @@
 
   public CfCodePrinter() {}
 
-  public List<String> getImports() {
+  public Set<String> getImports() {
+    return imports;
+  }
+
+  public List<String> getImportsSorted() {
     ArrayList<String> sorted = new ArrayList<>(imports);
     sorted.sort(String::compareTo);
     return sorted;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
index e8019d9..6ade9b2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -47,6 +47,16 @@
     this.ignoreCompatRules = ignoreCompatRules;
   }
 
+  @Override
+  public boolean isConstClass() {
+    return true;
+  }
+
+  @Override
+  public CfConstClass asConstClass() {
+    return this;
+  }
+
   public boolean ignoreCompatRules() {
     return ignoreCompatRules;
   }
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 5384772..09ec0f5 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
@@ -48,6 +48,11 @@
   }
 
   @Override
+  public CfInstanceFieldRead asInstanceFieldGet() {
+    return this;
+  }
+
+  @Override
   public CfFieldInstruction createWithField(DexField otherField) {
     return new CfInstanceFieldRead(otherField);
   }
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 f21fc0c..5fbed91 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
@@ -174,6 +174,14 @@
     return false;
   }
 
+  public CfNew asNew() {
+    return null;
+  }
+
+  public boolean isNew() {
+    return false;
+  }
+
   public CfConstString asConstString() {
     return null;
   }
@@ -206,6 +214,10 @@
     return false;
   }
 
+  public CfInstanceFieldRead asInstanceFieldGet() {
+    return null;
+  }
+
   public boolean isStaticFieldGet() {
     return false;
   }
@@ -420,4 +432,12 @@
   public CfCheckCast asCheckCast() {
     return null;
   }
+
+  public boolean isConstClass() {
+    return false;
+  }
+
+  public CfConstClass asConstClass() {
+    return null;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
index bbefd75..4ec3b43 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -56,6 +56,16 @@
   }
 
   @Override
+  public CfNew asNew() {
+    return this;
+  }
+
+  @Override
+  public boolean isNew() {
+    return true;
+  }
+
+  @Override
   public CfTypeInstruction asTypeInstruction() {
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 369e60c..1536cd8 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -191,9 +191,9 @@
    * given type is *not* visited. The function indicates if traversal should continue or break. The
    * result of the traversal is BREAK iff the function returned BREAK.
    */
-  public TraversalContinuation<?, ?> traverseSuperTypes(
+  public <B> TraversalContinuation<B, ?> traverseSuperTypes(
       final DexClass clazz,
-      TriFunction<DexType, DexClass, Boolean, TraversalContinuation<?, ?>> fn) {
+      TriFunction<DexType, DexClass, Boolean, TraversalContinuation<B, ?>> fn) {
     // We do an initial zero-allocation pass over the class super chain as it does not require a
     // worklist/seen-set. Only if the traversal is not aborted and there actually are interfaces,
     // do we continue traversal over the interface types. This is assuming that the second pass
@@ -206,7 +206,7 @@
         if (currentClass.superType == null) {
           break;
         }
-        TraversalContinuation<?, ?> stepResult =
+        TraversalContinuation<B, ?> stepResult =
             fn.apply(currentClass.superType, currentClass, false);
         if (stepResult.shouldBreak()) {
           return stepResult;
@@ -226,7 +226,7 @@
       while (currentClass != null) {
         for (DexType iface : currentClass.interfaces.values) {
           if (seen.add(iface)) {
-            TraversalContinuation<?, ?> stepResult = fn.apply(iface, currentClass, true);
+            TraversalContinuation<B, ?> stepResult = fn.apply(iface, currentClass, true);
             if (stepResult.shouldBreak()) {
               return stepResult;
             }
@@ -246,7 +246,7 @@
       if (definition != null) {
         for (DexType iface : definition.interfaces.values) {
           if (seen.add(iface)) {
-            TraversalContinuation<?, ?> stepResult = fn.apply(iface, definition, true);
+            TraversalContinuation<B, ?> stepResult = fn.apply(iface, definition, true);
             if (stepResult.shouldBreak()) {
               return stepResult;
             }
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 535a891..5b71a42 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -400,18 +400,25 @@
 
   private static class PrunePreambleMethodVisitor extends MethodVisitor {
 
+    private final AppView<?> appView;
     private boolean inPreamble = true;
 
-    public PrunePreambleMethodVisitor(MethodVisitor methodVisitor) {
+    public PrunePreambleMethodVisitor(MethodVisitor methodVisitor, AppView<?> appView) {
       super(InternalOptions.ASM_VERSION, methodVisitor);
+      this.appView = appView;
     }
 
     @Override
     public void visitLineNumber(int line, Label start) {
-      if (!inPreamble || line != 0) {
-        inPreamble = false;
-        super.visitLineNumber(line, start);
+      if (line == 0) {
+        if (inPreamble) {
+          inPreamble = false;
+          return;
+        }
+        // We must be in R8 if inserting a zero-line entry outside the method preamble.
+        assert appView.enableWholeProgramOptimizations();
       }
+      super.visitLineNumber(line, start);
     }
   }
 
@@ -440,7 +447,8 @@
             || (appView.enableWholeProgramOptimizations()
                 && classFileVersion.isEqualTo(CfVersion.V1_6)
                 && !options.shouldKeepStackMapTable());
-    PrunePreambleMethodVisitor prunePreambleVisitor = new PrunePreambleMethodVisitor(visitor);
+    PrunePreambleMethodVisitor prunePreambleVisitor =
+        new PrunePreambleMethodVisitor(visitor, appView);
     for (CfInstruction instruction : instructions) {
       if (discardFrames && instruction instanceof CfFrame) {
         continue;
@@ -911,7 +919,13 @@
                 oldPosition.getPosition().withOutermostCallerPosition(callerPosition)));
       } else {
         if (!instruction.isLabel() && !seenPosition) {
-          newInstructions.add(new CfPosition(firstLabel, callerPosition));
+          Position preamblePosition =
+              SyntheticPosition.builder()
+                  .setMethod(callee)
+                  .setCallerPosition(callerPosition)
+                  .setLine(0)
+                  .build();
+          newInstructions.add(new CfPosition(firstLabel, preamblePosition));
           seenPosition = true;
         }
         newInstructions.add(instruction);
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
index aeab5fe..3463789 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -41,6 +41,11 @@
   }
 
   @Override
+  public LookupTarget toLookupTarget(DexClassAndMethod classAndMethod) {
+    return classAndMethod;
+  }
+
+  @Override
   public MethodAccessFlags getAccessFlags() {
     return getDefinition().getAccessFlags();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 09db76d..048e01d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.dex.code.DexReturnVoid;
 import com.android.tools.r8.dex.code.DexSwitchPayload;
 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
+import com.android.tools.r8.graph.DexDebugEvent.Default;
 import com.android.tools.r8.graph.DexDebugEvent.SetPositionFrame;
 import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
 import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
@@ -22,6 +23,8 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.PositionBuilder;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
 import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.conversion.DexSourceCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -30,6 +33,7 @@
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.ThrowingMethodConversionOptions;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.structural.Equatable;
@@ -290,27 +294,34 @@
       DexMethod caller, DexMethod callee, DexItemFactory factory) {
     Position callerPosition = SyntheticPosition.builder().setLine(0).setMethod(caller).build();
     EventBasedDebugInfo eventBasedInfo = DexDebugInfo.convertToEventBased(this, factory);
-    Position inlinePosition =
-        SyntheticPosition.builder()
-            .setMethod(caller)
-            .setCallerPosition(callerPosition)
-            .disableLineCheck()
-            .build();
     if (eventBasedInfo == null) {
       // If the method has no debug info we generate a preamble position to denote the inlining.
       // This is consistent with the building IR for inlining which will always ensure the method
       // has a position.
+      Position preamblePosition =
+          SyntheticPosition.builder()
+              .setMethod(callee)
+              .setCallerPosition(callerPosition)
+              .setLine(0)
+              .build();
       return new EventBasedDebugInfo(
           0,
           new DexString[callee.getArity()],
-          new DexDebugEvent[] {
-            new SetPositionFrame(inlinePosition), factory.zeroChangeDefaultEvent
-          });
+          new DexDebugEvent[] {new SetPositionFrame(preamblePosition)});
     }
+    // The inline position should match the first actual callee position, so either its actual line
+    // at first instruction or it is a synthetic preamble.
+    int lineAtPcZero = findLineAtPcZero(callee, eventBasedInfo);
+    PositionBuilder<?, ?> frameBuilder =
+        lineAtPcZero == -1
+            ? SyntheticPosition.builder().setLine(0)
+            : SourcePosition.builder().setLine(lineAtPcZero);
     DexDebugEvent[] oldEvents = eventBasedInfo.events;
     DexDebugEvent[] newEvents = new DexDebugEvent[oldEvents.length + 1];
     int i = 0;
-    newEvents[i++] = new SetPositionFrame(inlinePosition);
+    newEvents[i++] =
+        new SetPositionFrame(
+            frameBuilder.setMethod(callee).setCallerPosition(callerPosition).build());
     for (DexDebugEvent event : oldEvents) {
       if (event instanceof SetPositionFrame) {
         SetPositionFrame oldFrame = (SetPositionFrame) event;
@@ -325,6 +336,27 @@
     return new EventBasedDebugInfo(eventBasedInfo.startLine, eventBasedInfo.parameters, newEvents);
   }
 
+  private static int findLineAtPcZero(DexMethod method, EventBasedDebugInfo debugInfo) {
+    IntBox lineAtPcZero = new IntBox(-1);
+    DexDebugPositionState visitor =
+        new DexDebugPositionState(debugInfo.startLine, method) {
+          @Override
+          public void visit(Default defaultEvent) {
+            super.visit(defaultEvent);
+            if (getCurrentPc() == 0) {
+              lineAtPcZero.set(getCurrentLine());
+            }
+          }
+        };
+    for (DexDebugEvent event : debugInfo.events) {
+      event.accept(visitor);
+      if (visitor.getCurrentPc() > 0) {
+        break;
+      }
+    }
+    return lineAtPcZero.get();
+  }
+
   public static int getLargestPrefix(DexItemFactory factory, DexString name) {
     if (name != null && name.endsWith(factory.thisName)) {
       String string = name.toString();
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfoForSingleLineMethod.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfoForSingleLineMethod.java
deleted file mode 100644
index 8d41561..0000000
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfoForSingleLineMethod.java
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.graph;
-
-import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
-
-public class DexDebugInfoForSingleLineMethod extends EventBasedDebugInfo {
-
-  private static final DexDebugInfoForSingleLineMethod INSTANCE =
-      new DexDebugInfoForSingleLineMethod(0, DexString.EMPTY_ARRAY, DexDebugEvent.EMPTY_ARRAY);
-
-  private DexDebugInfoForSingleLineMethod(
-      int startLine, DexString[] parameters, DexDebugEvent[] events) {
-    super(startLine, parameters, events);
-  }
-
-  public static DexDebugInfoForSingleLineMethod getInstance() {
-    return INSTANCE;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 47fabfa..9807531 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -353,7 +353,7 @@
     return new Builder(false);
   }
 
-  private static Builder builder(DexEncodedField from) {
+  public static Builder builder(DexEncodedField from) {
     return new Builder(from.isD8R8Synthesized(), from);
   }
 
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 411e5ee..c1e856e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -75,6 +75,9 @@
   public static final String dalvikAnnotationSignatureString = "Ldalvik/annotation/Signature;";
   public static final String recordTagDescriptorString = "Lcom/android/tools/r8/RecordTag;";
   public static final String recordDescriptorString = "Ljava/lang/Record;";
+  public static final String desugarVarHandleDescriptorString =
+      "Lcom/android/tools/r8/DesugarVarHandle;";
+  public static final String varHandleDescriptorString = "Ljava/lang/invoke/VarHandle;";
   public static final String dalvikAnnotationOptimizationPrefixString =
       "Ldalvik/annotation/optimization/";
 
@@ -266,7 +269,7 @@
   public final DexString stringBuilderDescriptor = createString("Ljava/lang/StringBuilder;");
   public final DexString stringBufferDescriptor = createString("Ljava/lang/StringBuffer;");
 
-  public final DexString varHandleDescriptor = createString("Ljava/lang/invoke/VarHandle;");
+  public final DexString varHandleDescriptor = createString(varHandleDescriptorString);
   public final DexString methodHandleDescriptor = createString("Ljava/lang/invoke/MethodHandle;");
   public final DexString methodTypeDescriptor = createString("Ljava/lang/invoke/MethodType;");
   public final DexString invocationHandlerDescriptor =
@@ -732,6 +735,8 @@
   public final DexType stringConcatFactoryType =
       createStaticallyKnownType("Ljava/lang/invoke/StringConcatFactory;");
   public final DexType unsafeType = createStaticallyKnownType("Lsun/misc/Unsafe;");
+  public final DexType desugarVarHandleType =
+      createStaticallyKnownType(desugarVarHandleDescriptorString);
 
   public final ObjectMethodsMembers objectMethodsMembers = new ObjectMethodsMembers();
   public final ServiceLoaderMethods serviceLoaderMethods = new ServiceLoaderMethods();
diff --git a/src/main/java/com/android/tools/r8/graph/LookupLambdaTarget.java b/src/main/java/com/android/tools/r8/graph/LookupLambdaTarget.java
index e1209e9..3ada6dc 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupLambdaTarget.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupLambdaTarget.java
@@ -29,6 +29,11 @@
   }
 
   @Override
+  public LookupTarget toLookupTarget(DexClassAndMethod classAndMethod) {
+    return new LookupLambdaTarget(lambda, classAndMethod);
+  }
+
+  @Override
   public void accept(
       Consumer<LookupMethodTarget> methodConsumer, Consumer<LookupLambdaTarget> lambdaConsumer) {
     lambdaConsumer.accept(this);
@@ -37,4 +42,9 @@
   public DexClassAndMethod getImplementationMethod() {
     return method;
   }
+
+  @Override
+  public DexClassAndMethod getTargetOrImplementationMethod() {
+    return getImplementationMethod();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/LookupMethodTarget.java b/src/main/java/com/android/tools/r8/graph/LookupMethodTarget.java
index b7a6aa0..6df0bbd 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupMethodTarget.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupMethodTarget.java
@@ -30,4 +30,9 @@
   DexEncodedMethod getDefinition();
 
   DexClassAndMethod getTarget();
+
+  @Override
+  default DexClassAndMethod getTargetOrImplementationMethod() {
+    return getTarget();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/LookupMethodTargetWithAccessOverride.java b/src/main/java/com/android/tools/r8/graph/LookupMethodTargetWithAccessOverride.java
index d69c5b3..c21584e 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupMethodTargetWithAccessOverride.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupMethodTargetWithAccessOverride.java
@@ -15,6 +15,11 @@
   }
 
   @Override
+  public LookupTarget toLookupTarget(DexClassAndMethod classAndMethod) {
+    return new LookupMethodTargetWithAccessOverride(classAndMethod, accessOverride);
+  }
+
+  @Override
   public DexClassAndMethod getAccessOverride() {
     return accessOverride;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/LookupTarget.java b/src/main/java/com/android/tools/r8/graph/LookupTarget.java
index 4530c0f..a77ed1d 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupTarget.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupTarget.java
@@ -26,6 +26,10 @@
     return null;
   }
 
+  LookupTarget toLookupTarget(DexClassAndMethod classAndMethod);
+
   void accept(
       Consumer<LookupMethodTarget> methodConsumer, Consumer<LookupLambdaTarget> lambdaConsumer);
+
+  DexClassAndMethod getTargetOrImplementationMethod();
 }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
index 94bb52d..8c949fe 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -348,9 +348,11 @@
       if (type == null) {
         return null;
       }
-      return appInfo
-          .contextIndependentDefinitionForWithResolutionResult(type)
-          .toSingleClassWithLibraryOverProgram();
+      ClassResolutionResult resolutionResult =
+          appInfo.contextIndependentDefinitionForWithResolutionResult(type);
+      return appInfo.options().lookupLibraryBeforeProgram
+          ? resolutionResult.toSingleClassWithLibraryOverProgram()
+          : resolutionResult.toSingleClassWithProgramOverLibrary();
     }
 
     /**
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 30c3b67..2a543a8 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound;
@@ -29,6 +30,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Value;
@@ -37,6 +39,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 
 public class StaticFieldValueAnalysis extends FieldValueAnalysis {
@@ -210,7 +213,7 @@
     if (value.isPhi()) {
       return null;
     }
-    if (value.definition.isNewArrayEmpty()) {
+    if (value.definition.isNewArrayEmptyOrInvokeNewArray()) {
       return computeSingleEnumFieldValueForValuesArray(value);
     }
     if (value.definition.isNewInstance()) {
@@ -220,7 +223,7 @@
   }
 
   private SingleFieldValue computeSingleEnumFieldValueForValuesArray(Value value) {
-    if (!value.definition.isNewArrayEmpty()) {
+    if (!value.definition.isNewArrayEmptyOrInvokeNewArray()) {
       return null;
     }
     AbstractValue valuesValue = computedValues.get(value);
@@ -241,26 +244,38 @@
   }
 
   private SingleFieldValue internalComputeSingleEnumFieldValueForValuesArray(Value value) {
-    assert value.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty);
-
     NewArrayEmpty newArrayEmpty = value.definition.asNewArrayEmpty();
-    if (newArrayEmpty.type.toBaseType(appView.dexItemFactory()) != context.getHolder().type) {
+    InvokeNewArray invokeNewArray = value.definition.asInvokeNewArray();
+    assert newArrayEmpty != null || invokeNewArray != null;
+
+    DexType arrayType = newArrayEmpty != null ? newArrayEmpty.type : invokeNewArray.getArrayType();
+    if (arrayType.toBaseType(appView.dexItemFactory()) != context.getHolder().type) {
       return null;
     }
     if (value.hasDebugUsers() || value.hasPhiUsers()) {
       return null;
     }
-    if (!newArrayEmpty.size().isConstNumber()) {
-      return null;
-    }
 
-    int valuesSize = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
-    if (valuesSize == 0) {
-      // No need to compute the state of an empty array.
+    int valuesSize = newArrayEmpty != null ? newArrayEmpty.sizeIfConst() : invokeNewArray.size();
+    if (valuesSize < 1) {
+      // Array is empty or non-const size.
       return null;
     }
 
     ObjectState[] valuesState = new ObjectState[valuesSize];
+
+    if (invokeNewArray != null) {
+      // Populate array values from filled-new-array values.
+      List<Value> inValues = invokeNewArray.inValues();
+      for (int i = 0; i < valuesSize; ++i) {
+        if (!updateEnumValueState(valuesState, i, inValues.get(i))) {
+          return null;
+        }
+      }
+    }
+
+    // Populate / update array values from aput-object instructions, and find the static-put
+    // instruction.
     DexEncodedField valuesField = null;
     for (Instruction user : value.aliasedUsers()) {
       switch (user.opcode()) {
@@ -276,18 +291,9 @@
           if (index < 0 || index >= valuesSize) {
             return null;
           }
-          ObjectState objectState = computeEnumInstanceObjectState(arrayPut.value());
-          if (objectState == null || objectState.isEmpty()) {
-            // We need the state of all fields for the analysis to be valuable.
+          if (!updateEnumValueState(valuesState, index, arrayPut.value())) {
             return null;
           }
-          if (!valuesArrayIndexMatchesOrdinal(index, objectState)) {
-            return null;
-          }
-          if (valuesState[index] != null) {
-            return null;
-          }
-          valuesState[index] = objectState;
           break;
 
         case ASSUME:
@@ -328,24 +334,34 @@
         .createSingleFieldValue(valuesField.getReference(), new EnumValuesObjectState(valuesState));
   }
 
-  private ObjectState computeEnumInstanceObjectState(Value value) {
+  private boolean updateEnumValueState(ObjectState[] valuesState, int index, Value value) {
     Value root = value.getAliasedValue();
     if (root.isPhi()) {
-      return ObjectState.empty();
+      return false;
     }
     Instruction definition = root.getDefinition();
-    if (definition.isNewInstance()) {
-      return computeObjectState(definition.outValue());
-    }
     if (definition.isStaticGet()) {
       // Enums with many instance rely on staticGets to set the $VALUES data instead of directly
       // keeping the values in registers, due to the max capacity of the redundant field load
       // elimination. The capacity has already been increased, so that this case is extremely
       // uncommon (very large enums).
       // TODO(b/169050248): We could consider analysing these to answer the object state here.
-      return ObjectState.empty();
+      return false;
     }
-    return ObjectState.empty();
+    ObjectState objectState =
+        definition.isNewInstance() ? computeObjectState(definition.outValue()) : null;
+    if (objectState == null || objectState.isEmpty()) {
+      // We need the state of all fields for the analysis to be valuable.
+      return false;
+    }
+    if (!valuesArrayIndexMatchesOrdinal(index, objectState)) {
+      return false;
+    }
+    if (valuesState[index] != null) {
+      return false;
+    }
+    valuesState[index] = objectState;
+    return true;
   }
 
   private boolean valuesArrayIndexMatchesOrdinal(int ordinal, ObjectState objectState) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index 74a3fb5..104b854 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -33,6 +33,7 @@
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewInstance;
@@ -46,6 +47,7 @@
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -316,17 +318,36 @@
     Value sizeValue =
         instructionIterator.insertConstIntInstruction(code, appView.options(), objects.size());
     Value newObjectsValue = code.createValue(objectArrayType);
-    instructionIterator.add(
-        new NewArrayEmpty(newObjectsValue, sizeValue, appView.dexItemFactory().objectArrayType));
 
     // Populate the `objects` array.
-    for (int i = 0; i < objects.size(); i++) {
-      Value indexValue = instructionIterator.insertConstIntInstruction(code, appView.options(), i);
-      Instruction materializingInstruction = objects.get(i).buildIR(appView, code);
-      instructionIterator.add(materializingInstruction);
+    var rewriteOptions = appView.options().rewriteArrayOptions();
+    if (rewriteOptions.canUseFilledNewArrayOfNonStringObjects()
+        && objects.size() < rewriteOptions.maxSizeForFilledNewArrayOfReferences) {
+      List<Value> arrayValues = new ArrayList<>(objects.size());
+      for (int i = 0; i < objects.size(); i++) {
+        Instruction materializingInstruction = objects.get(i).buildIR(appView, code);
+        instructionIterator.add(materializingInstruction);
+        arrayValues.add(materializingInstruction.outValue());
+      }
       instructionIterator.add(
-          new ArrayPut(
-              MemberType.OBJECT, newObjectsValue, indexValue, materializingInstruction.outValue()));
+          new InvokeNewArray(
+              appView.dexItemFactory().objectArrayType, newObjectsValue, arrayValues));
+    } else {
+      instructionIterator.add(
+          new NewArrayEmpty(newObjectsValue, sizeValue, appView.dexItemFactory().objectArrayType));
+
+      for (int i = 0; i < objects.size(); i++) {
+        Value indexValue =
+            instructionIterator.insertConstIntInstruction(code, appView.options(), i);
+        Instruction materializingInstruction = objects.get(i).buildIR(appView, code);
+        instructionIterator.add(materializingInstruction);
+        instructionIterator.add(
+            new ArrayPut(
+                MemberType.OBJECT,
+                newObjectsValue,
+                indexValue,
+                materializingInstruction.outValue()));
+      }
     }
 
     // Pass the newly created `objects` array to RawMessageInfo.<init>(...) or
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
index fbe3ff5..00182d3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.StaticGet;
@@ -302,18 +303,30 @@
    */
   private static ThrowingIterator<Value, InvalidRawMessageInfoException> createObjectIterator(
       Value objectsValue) throws InvalidRawMessageInfoException {
-    if (objectsValue.isPhi() || !objectsValue.definition.isNewArrayEmpty()) {
+    if (objectsValue.isPhi()) {
       throw new InvalidRawMessageInfoException();
     }
 
     NewArrayEmpty newArrayEmpty = objectsValue.definition.asNewArrayEmpty();
-    int expectedArraySize = objectsValue.uniqueUsers().size() - 1;
+    InvokeNewArray invokeNewArray = objectsValue.definition.asInvokeNewArray();
 
-    // Verify that the size is correct.
+    if (newArrayEmpty == null && invokeNewArray == null) {
+      throw new InvalidRawMessageInfoException();
+    }
+    // Verify that the array is used in only one spot.
+    if (invokeNewArray != null) {
+      if (!objectsValue.hasSingleUniqueUser()) {
+        throw new InvalidRawMessageInfoException();
+      }
+      return ThrowingIterator.fromIterator(invokeNewArray.inValues().iterator());
+    }
+
     Value sizeValue = newArrayEmpty.size().getAliasedValue();
-    if (sizeValue.isPhi()
-        || !sizeValue.definition.isConstNumber()
-        || sizeValue.definition.asConstNumber().getIntValue() != expectedArraySize) {
+    if (sizeValue.isPhi() || !sizeValue.definition.isConstNumber()) {
+      throw new InvalidRawMessageInfoException();
+    }
+    int arraySize = sizeValue.definition.asConstNumber().getIntValue();
+    if (arraySize != objectsValue.uniqueUsers().size() - 1) {
       throw new InvalidRawMessageInfoException();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index a214f7c..0a79887 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -1921,8 +1921,11 @@
       newBlock.getMutableSuccessors().add(this);
       newBlock.getMutablePredecessors().add(predecessor);
       predecessor.replaceSuccessor(this, newBlock);
-      blockIterator.add(newBlock);
-      assert newBlock.getNumber() >= 0 : "Number must be assigned by `onNewBlock`";
+      if (blockIterator == null) {
+        code.blocks.add(newBlock);
+      } else {
+        blockIterator.add(newBlock);
+      }
     }
     // Replace the blocks predecessors with the new ones.
     predecessors.clear();
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 1d40f99..afe8e0b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -129,7 +129,7 @@
   }
 
   @Override
-  public void addThrowingInstructionToPossiblyThrowingBlock(
+  public BasicBlock addThrowingInstructionToPossiblyThrowingBlock(
       IRCode code,
       ListIterator<BasicBlock> blockIterator,
       Instruction instruction,
@@ -140,11 +140,15 @@
       assert !block.hasCatchHandlers();
       assert splitBlock.hasCatchHandlers();
       block.copyCatchHandlers(code, blockIterator, splitBlock, options);
-      while (IteratorUtils.peekPrevious(blockIterator) != splitBlock) {
-        blockIterator.previous();
+      if (blockIterator != null) {
+        while (IteratorUtils.peekPrevious(blockIterator) != splitBlock) {
+          blockIterator.previous();
+        }
       }
+      return splitBlock;
     } else {
       add(instruction);
+      return null;
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index ae400dc..8b519ed 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -207,12 +207,12 @@
   }
 
   @Override
-  public void addThrowingInstructionToPossiblyThrowingBlock(
+  public BasicBlock addThrowingInstructionToPossiblyThrowingBlock(
       IRCode code,
       ListIterator<BasicBlock> blockIterator,
       Instruction instruction,
       InternalOptions options) {
-    instructionIterator.addThrowingInstructionToPossiblyThrowingBlock(
+    return instructionIterator.addThrowingInstructionToPossiblyThrowingBlock(
         code, blockIterator, instruction, options);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
index e06cdbee..99f92eb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
@@ -73,20 +73,25 @@
    * <p>Use with caution!
    */
   public static void removeArrayAndTransitiveInputsIfNotUsed(IRCode code, Instruction definition) {
-    Deque<InstructionOrPhi> worklist = new ArrayDeque<>();
     if (definition.isConstNumber()) {
       // No need to explicitly remove `null`, it will be removed by ordinary dead code elimination
       // anyway.
       assert definition.asConstNumber().isZero();
       return;
     }
-
-    if (definition.isNewArrayEmpty()) {
-      Value arrayValue = definition.outValue();
-      if (arrayValue.hasPhiUsers() || arrayValue.hasDebugUsers()) {
-        return;
-      }
-
+    Value arrayValue = definition.outValue();
+    if (arrayValue.hasPhiUsers() || arrayValue.hasDebugUsers()) {
+      return;
+    }
+    if (!definition.isNewArrayEmptyOrInvokeNewArray()) {
+      assert false;
+      return;
+    }
+    Deque<InstructionOrPhi> worklist = new ArrayDeque<>();
+    InvokeNewArray invokeNewArray = definition.asInvokeNewArray();
+    if (invokeNewArray != null) {
+      worklist.add(definition);
+    } else if (definition.isNewArrayEmpty()) {
       for (Instruction user : arrayValue.uniqueUsers()) {
         // If we encounter an Assume instruction here, we also need to consider indirect users.
         assert !user.isAssume();
@@ -95,11 +100,10 @@
         }
         worklist.add(user);
       }
-      internalRemoveInstructionAndTransitiveInputsIfNotUsed(code, worklist);
-      return;
+    } else {
+      assert false;
     }
-
-    assert false;
+    internalRemoveInstructionAndTransitiveInputsIfNotUsed(code, worklist);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index c99ee11..8892da0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -1038,6 +1038,10 @@
     return false;
   }
 
+  public boolean isNewArrayEmptyOrInvokeNewArray() {
+    return isNewArrayEmpty() || isInvokeNewArray();
+  }
+
   public NewArrayEmpty asNewArrayEmpty() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 224b457..8a69912 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -26,7 +26,7 @@
 public interface InstructionListIterator
     extends InstructionIterator, ListIterator<Instruction>, PreviousUntilIterator<Instruction> {
 
-  void addThrowingInstructionToPossiblyThrowingBlock(
+  BasicBlock addThrowingInstructionToPossiblyThrowingBlock(
       IRCode code,
       ListIterator<BasicBlock> blockIterator,
       Instruction instruction,
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index a2e815f..f0715b5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -221,4 +221,9 @@
   void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
     registry.registerTypeReference(type);
   }
+
+  // Returns the number of elements in the array.
+  public int size() {
+    return inValues.size();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index ed95689..819ba43 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -183,12 +183,12 @@
   }
 
   @Override
-  public void addThrowingInstructionToPossiblyThrowingBlock(
+  public BasicBlock addThrowingInstructionToPossiblyThrowingBlock(
       IRCode code,
       ListIterator<BasicBlock> blockIterator,
       Instruction instruction,
       InternalOptions options) {
-    currentBlockIterator.addThrowingInstructionToPossiblyThrowingBlock(
+    return currentBlockIterator.addThrowingInstructionToPossiblyThrowingBlock(
         code, blockIterator, instruction, options);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index c6d527f..a829247 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -49,10 +49,6 @@
     return super.toString() + " " + type.toString();
   }
 
-  public Value dest() {
-    return outValue;
-  }
-
   public Value size() {
     return inValues.get(0);
   }
@@ -60,7 +56,7 @@
   @Override
   public void buildDex(DexBuilder builder) {
     int size = builder.allocatedRegister(size(), getNumber());
-    int dest = builder.allocatedRegister(dest(), getNumber());
+    int dest = builder.allocatedRegister(outValue, getNumber());
     builder.add(this, new DexNewArray(dest, size, type));
   }
 
@@ -171,4 +167,10 @@
   void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
     registry.registerTypeReference(type);
   }
+
+  // Returns the size of the array if it is known, -1 otherwise.
+  public int sizeIfConst() {
+    Value size = size();
+    return size.isConstNumber() ? size.getConstInstruction().asConstNumber().getIntValue() : -1;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 4a29cb6..a091553 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -148,8 +148,8 @@
 /**
  * Builder object for constructing high-level IR from dex bytecode.
  *
- * <p>The generated IR is in SSA form. The SSA construction is based on the paper
- * "Simple and Efficient Construction of Static Single Assignment Form" available at
+ * <p>The generated IR is in SSA form. The SSA construction is based on the paper "Simple and
+ * Efficient Construction of Static Single Assignment Form" available at
  * http://compilers.cs.uni-saarland.de/papers/bbhlmz13cc.pdf
  */
 public class IRBuilder {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
index 4c3fc63..99eef8c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfConstClass;
 import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfInstanceOf;
 import com.android.tools.r8.cf.code.CfInstruction;
@@ -35,11 +36,14 @@
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.ir.synthetic.CheckCastSourceCode;
+import com.android.tools.r8.ir.synthetic.ConstClassSourceCode;
 import com.android.tools.r8.ir.synthetic.FieldAccessorBuilder;
 import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 import com.android.tools.r8.ir.synthetic.InstanceOfSourceCode;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApiLevelUtils;
+import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
@@ -112,6 +116,8 @@
       reference = instruction.asCheckCast().getType();
     } else if (instruction.isInstanceOf()) {
       reference = instruction.asInstanceOf().getType();
+    } else if (instruction.isConstClass()) {
+      reference = instruction.asConstClass().getType();
     } else {
       return appView.computedMinApiLevel();
     }
@@ -119,23 +125,39 @@
       return appView.computedMinApiLevel();
     }
     DexClass holder = appView.definitionFor(reference.getContextType());
-    if (holder == null || !holder.isLibraryClass()) {
+    if (holder == null) {
       return appView.computedMinApiLevel();
     }
-    ComputedApiLevel referenceApiLevel =
-        apiLevelCompute.computeApiLevelForLibraryReference(reference, ComputedApiLevel.unknown());
+    Pair<DexClass, ComputedApiLevel> classAndApiLevel =
+        reference.isDexType()
+            ? Pair.create(
+                holder,
+                apiLevelCompute.computeApiLevelForLibraryReference(
+                    reference, ComputedApiLevel.unknown()))
+            : AndroidApiLevelUtils.findAndComputeApiLevelForLibraryDefinition(
+                appView, appView.appInfoForDesugaring(), holder, reference.asDexMember());
+    ComputedApiLevel referenceApiLevel = classAndApiLevel.getSecond();
     if (appView.computedMinApiLevel().isGreaterThanOrEqualTo(referenceApiLevel)
         || isApiLevelLessThanOrEqualTo9(referenceApiLevel)
         || referenceApiLevel.isUnknownApiLevel()) {
       return appView.computedMinApiLevel();
     }
+    assert referenceApiLevel.isKnownApiLevel();
+    DexClass firstLibraryClass = classAndApiLevel.getFirst();
+    if (firstLibraryClass == null || !firstLibraryClass.isLibraryClass()) {
+      assert false : "When computed a known api level we should always have a library class";
+      return appView.computedMinApiLevel();
+    }
     // Check for protected or package private access flags before outlining.
-    if (holder.isInterface() || instruction.isCheckCast() || instruction.isInstanceOf()) {
+    if (firstLibraryClass.isInterface()
+        || instruction.isCheckCast()
+        || instruction.isInstanceOf()
+        || instruction.isConstClass()) {
       return referenceApiLevel;
     } else {
       DexEncodedMember<?, ?> definition =
           simpleLookupInClassHierarchy(
-              holder.asLibraryClass(),
+              firstLibraryClass.asLibraryClass(),
               reference.isDexMethod()
                   ? x -> x.lookupMethod(reference.asDexMethod())
                   : x -> x.lookupField(reference.asDexField()));
@@ -181,7 +203,8 @@
     assert instruction.isInvoke()
         || instruction.isFieldInstruction()
         || instruction.isCheckCast()
-        || instruction.isInstanceOf();
+        || instruction.isInstanceOf()
+        || instruction.isConstClass();
     ProgramMethod outlinedMethod =
         ensureOutlineMethod(uniqueContext, instruction, computedApiLevel, factory, context);
     eventConsumer.acceptOutlinedMethod(outlinedMethod, context);
@@ -217,6 +240,8 @@
                 setCodeForCheckCast(syntheticMethodBuilder, instruction.asCheckCast(), factory);
               } else if (instruction.isInstanceOf()) {
                 setCodeForInstanceOf(syntheticMethodBuilder, instruction.asInstanceOf(), factory);
+              } else if (instruction.isConstClass()) {
+                setCodeForConstClass(syntheticMethodBuilder, instruction.asConstClass(), factory);
               } else {
                 assert instruction.isCfInstruction();
                 setCodeForFieldInstruction(
@@ -315,6 +340,18 @@
                     .generateCfCode());
   }
 
+  private void setCodeForConstClass(
+      SyntheticMethodBuilder methodBuilder, CfConstClass instruction, DexItemFactory factory) {
+    DexClass target = appView.definitionFor(instruction.getType());
+    assert target != null;
+    methodBuilder
+        .setProto(factory.createProto(factory.classType))
+        .setCode(
+            m ->
+                ConstClassSourceCode.create(appView, m.getHolderType(), target.getType())
+                    .generateCfCode());
+  }
+
   private boolean verifyLibraryHolderAndInvoke(
       DexClass libraryHolder, DexMethod apiMethod, boolean isVirtualInvoke) {
     DexEncodedMethod libraryApiMethodDefinition = libraryHolder.lookupMethod(apiMethod);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/disabledesugarer/DesugaredLibraryDisableDesugarer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/disabledesugarer/DesugaredLibraryDisableDesugarer.java
index bd4b771..5c956aa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/disabledesugarer/DesugaredLibraryDisableDesugarer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/disabledesugarer/DesugaredLibraryDisableDesugarer.java
@@ -62,6 +62,7 @@
     return rewriteInstruction(instruction, context) != null;
   }
 
+  // TODO(b/261024278): Share this code.
   private CfInstruction rewriteInstruction(CfInstruction instruction, ProgramMethod context) {
     if (!appView.dexItemFactory().multiDexTypes.contains(context.getHolderType())) {
       return null;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
index 314d657..cae75f2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
@@ -15,13 +15,11 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
-import com.google.common.collect.Sets.SetView;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 public class HumanRewritingFlags {
 
@@ -510,7 +508,6 @@
     }
 
     public HumanRewritingFlags build() {
-      validate();
       return new HumanRewritingFlags(
           ImmutableMap.copyOf(rewritePrefix),
           ImmutableSet.copyOf(dontRewritePrefix),
@@ -533,19 +530,5 @@
           ImmutableMap.copyOf(amendLibraryMethod),
           ImmutableMap.copyOf(amendLibraryField));
     }
-
-    private void validate() {
-      SetView<DexType> dups =
-          Sets.intersection(customConversions.keySet(), wrapperConversions.keySet());
-      if (!dups.isEmpty()) {
-        throw reporter.fatalError(
-            new StringDiagnostic(
-                "Invalid desugared library configuration. "
-                    + "Duplicate types in custom conversions and wrapper conversions: "
-                    + String.join(
-                        ", ", dups.stream().map(DexType::toString).collect(Collectors.toSet())),
-                origin));
-      }
-    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java
new file mode 100644
index 0000000..2daf329
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java
@@ -0,0 +1,569 @@
+// 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.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See GenerateVarHandleMethods.java.
+// ***********************************************************************************
+
+package com.android.tools.r8.ir.desugar.varhandle;
+
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfInstanceFieldRead;
+import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.CfCode;
+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.FieldAccessFlags;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
+import com.google.common.collect.ImmutableList;
+
+public final class VarHandleDesugaringMethods {
+
+  public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+    factory.createSynthesizedType("Lcom/android/tools/r8/DesugarVarHandle;");
+    factory.createSynthesizedType("Ljava/lang/reflect/Field;");
+    factory.createSynthesizedType("Lsun/misc/Unsafe;");
+  }
+
+  public static void generateDesugarVarHandleClass(
+      SyntheticProgramClassBuilder builder, DexItemFactory factory) {
+    builder.setInstanceFields(
+        ImmutableList.of(
+            DexEncodedField.syntheticBuilder()
+                .setField(
+                    factory.createField(
+                        builder.getType(),
+                        factory.createType(factory.createString("Lsun/misc/Unsafe;")),
+                        factory.createString("U")))
+                .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedField.syntheticBuilder()
+                .setField(
+                    factory.createField(
+                        builder.getType(),
+                        factory.createType(factory.createString("Ljava/lang/Class;")),
+                        factory.createString("recv")))
+                .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedField.syntheticBuilder()
+                .setField(
+                    factory.createField(
+                        builder.getType(),
+                        factory.createType(factory.createString("Ljava/lang/Class;")),
+                        factory.createString("type")))
+                .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedField.syntheticBuilder()
+                .setField(
+                    factory.createField(
+                        builder.getType(), factory.longType, factory.createString("offset")))
+                .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
+                .disableAndroidApiLevelCheck()
+                .build()));
+    DexMethod set =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(factory.voidType, factory.objectType, factory.objectType),
+            factory.createString("set"));
+    DexMethod get =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(factory.objectType, factory.objectType),
+            factory.createString("get"));
+    DexMethod compareAndSetInt =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(
+                factory.booleanType, factory.objectType, factory.intType, factory.intType),
+            factory.createString("compareAndSet"));
+    DexMethod getInt =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(factory.intType, factory.objectType),
+            factory.createString("get"));
+    DexMethod compareAndSet =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(
+                factory.booleanType, factory.objectType, factory.objectType, factory.objectType),
+            factory.createString("compareAndSet"));
+    DexMethod setInt =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(factory.voidType, factory.objectType, factory.intType),
+            factory.createString("set"));
+    DexMethod getLong =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(factory.longType, factory.objectType),
+            factory.createString("get"));
+    DexMethod constructor_3 =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(
+                factory.voidType,
+                factory.createType(factory.createString("Ljava/lang/Class;")),
+                factory.createType(factory.createString("Ljava/lang/String;")),
+                factory.createType(factory.createString("Ljava/lang/Class;"))),
+            factory.createString("<init>"));
+    DexMethod setLong =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(factory.voidType, factory.objectType, factory.longType),
+            factory.createString("set"));
+    DexMethod compareAndSetLong =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(
+                factory.booleanType, factory.objectType, factory.longType, factory.longType),
+            factory.createString("compareAndSet"));
+    builder.setDirectMethods(
+        ImmutableList.of(
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(constructor_3)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, true))
+                .setCode(DesugarVarHandle_constructor_3(factory, constructor_3))
+                .disableAndroidApiLevelCheck()
+                .build()));
+    builder.setVirtualMethods(
+        ImmutableList.of(
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(set)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_set(factory, set))
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(get)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_get(factory, get))
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(compareAndSetInt)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_compareAndSetInt(factory, compareAndSetInt))
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(getInt)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_getInt(factory, getInt))
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(compareAndSet)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_compareAndSet(factory, compareAndSet))
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(setInt)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_setInt(factory, setInt))
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(getLong)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_getLong(factory, getLong))
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(setLong)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_setLong(factory, setLong))
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(compareAndSetLong)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_compareAndSetLong(factory, compareAndSetLong))
+                .disableAndroidApiLevelCheck()
+                .build()));
+  }
+
+  public static void generateDesugarMethodHandlesLookupClass(
+      SyntheticProgramClassBuilder builder, DexItemFactory factory) {
+    builder.setInstanceFields(ImmutableList.of());
+    DexMethod findVarHandle =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(
+                factory.createType(factory.createString("Lcom/android/tools/r8/DesugarVarHandle;")),
+                factory.createType(factory.createString("Ljava/lang/Class;")),
+                factory.createType(factory.createString("Ljava/lang/String;")),
+                factory.createType(factory.createString("Ljava/lang/Class;"))),
+            factory.createString("findVarHandle"));
+    DexMethod constructor_0 =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(factory.voidType),
+            factory.createString("<init>"));
+    builder.setDirectMethods(
+        ImmutableList.of(
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(constructor_0)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, true))
+                .setCode(DesugarMethodHandlesLookup_constructor_0(factory, constructor_0))
+                .disableAndroidApiLevelCheck()
+                .build()));
+    builder.setVirtualMethods(
+        ImmutableList.of(
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(findVarHandle)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarMethodHandlesLookup_findVarHandle(factory, findVarHandle))
+                .disableAndroidApiLevelCheck()
+                .build()));
+  }
+
+  public static CfCode DesugarMethodHandlesLookup_constructor_0(
+      DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        1,
+        1,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                183,
+                factory.createMethod(
+                    factory.objectType,
+                    factory.createProto(factory.voidType),
+                    factory.createString("<init>")),
+                false),
+            new CfReturnVoid(),
+            label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarMethodHandlesLookup_findVarHandle(
+      DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        5,
+        4,
+        ImmutableList.of(
+            label0,
+            new CfNew(factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfLoad(ValueType.OBJECT, 3),
+            new CfInvoke(
+                183,
+                factory.createMethod(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createProto(
+                        factory.voidType, factory.classType, factory.stringType, factory.classType),
+                    factory.createString("<init>")),
+                false),
+            new CfReturn(ValueType.OBJECT),
+            label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_constructor_3(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    CfLabel label6 = new CfLabel();
+    CfLabel label7 = new CfLabel();
+    CfLabel label8 = new CfLabel();
+    CfLabel label9 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        4,
+        6,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                183,
+                factory.createMethod(
+                    factory.objectType,
+                    factory.createProto(factory.voidType),
+                    factory.createString("<init>")),
+                false),
+            label1,
+            new CfConstClass(factory.createType("Lsun/misc/Unsafe;")),
+            new CfConstString(factory.createString("theUnsafe")),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.classType,
+                    factory.createProto(
+                        factory.createType("Ljava/lang/reflect/Field;"), factory.stringType),
+                    factory.createString("getDeclaredField")),
+                false),
+            new CfStore(ValueType.OBJECT, 4),
+            label2,
+            new CfLoad(ValueType.OBJECT, 4),
+            new CfConstNumber(1, ValueType.INT),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/reflect/Field;"),
+                    factory.createProto(factory.voidType, factory.booleanType),
+                    factory.createString("setAccessible")),
+                false),
+            label3,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 4),
+            new CfConstNull(),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/reflect/Field;"),
+                    factory.createProto(factory.objectType, factory.objectType),
+                    factory.createString("get")),
+                false),
+            new CfCheckCast(factory.createType("Lsun/misc/Unsafe;")),
+            new CfInstanceFieldWrite(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            label4,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfInstanceFieldWrite(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("recv"))),
+            label5,
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.classType,
+                    factory.createProto(
+                        factory.createType("Ljava/lang/reflect/Field;"), factory.stringType),
+                    factory.createString("getDeclaredField")),
+                false),
+            new CfStore(ValueType.OBJECT, 5),
+            label6,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 5),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/reflect/Field;"),
+                    factory.createProto(factory.classType),
+                    factory.createString("getType")),
+                false),
+            new CfInstanceFieldWrite(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("type"))),
+            label7,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.classType,
+                    factory.createProto(
+                        factory.createType("Ljava/lang/reflect/Field;"), factory.stringType),
+                    factory.createString("getDeclaredField")),
+                false),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(
+                        factory.longType, factory.createType("Ljava/lang/reflect/Field;")),
+                    factory.createString("objectFieldOffset")),
+                false),
+            new CfInstanceFieldWrite(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            label8,
+            new CfReturnVoid(),
+            label9),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_compareAndSet(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        1,
+        4,
+        ImmutableList.of(
+            label0, new CfConstNumber(0, ValueType.INT), new CfReturn(ValueType.INT), label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_compareAndSetInt(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        1,
+        4,
+        ImmutableList.of(
+            label0, new CfConstNumber(0, ValueType.INT), new CfReturn(ValueType.INT), label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_compareAndSetLong(
+      DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        1,
+        6,
+        ImmutableList.of(
+            label0, new CfConstNumber(0, ValueType.INT), new CfReturn(ValueType.INT), label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_get(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        1,
+        2,
+        ImmutableList.of(label0, new CfConstNull(), new CfReturn(ValueType.OBJECT), label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_getInt(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        1,
+        2,
+        ImmutableList.of(
+            label0, new CfConstNumber(-1, ValueType.INT), new CfReturn(ValueType.INT), label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_getLong(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        2,
+        2,
+        ImmutableList.of(
+            label0, new CfConstNumber(-1, ValueType.LONG), new CfReturn(ValueType.LONG), label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_set(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        0,
+        3,
+        ImmutableList.of(label0, new CfReturnVoid(), label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_setInt(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        0,
+        3,
+        ImmutableList.of(label0, new CfReturnVoid(), label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_setLong(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        0,
+        4,
+        ImmutableList.of(label0, new CfReturnVoid(), label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+}
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 23f0de9..5002bac 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
@@ -16,7 +16,6 @@
 
 import com.android.tools.r8.algorithms.scc.SCC;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AccessControl;
@@ -55,7 +54,6 @@
 import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.ConstClass;
-import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.DebugLocalWrite;
@@ -84,6 +82,7 @@
 import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.LinearFlowInstructionListIterator;
 import com.android.tools.r8.ir.code.Move;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
@@ -107,9 +106,12 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.RewriteArrayOptions;
 import com.android.tools.r8.utils.InternalOutputMode;
 import com.android.tools.r8.utils.LazyBox;
 import com.android.tools.r8.utils.LongInterval;
+import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.WorkList;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ArrayListMultimap;
@@ -138,10 +140,10 @@
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
@@ -162,7 +164,6 @@
     FALSE
   }
 
-  private static final int MAX_FILL_ARRAY_SIZE = 8 * Constants.KILOBYTE;
   // This constant was determined by experimentation.
   private static final int STOP_SHARED_CONSTANT_THRESHOLD = 50;
 
@@ -2101,16 +2102,21 @@
     }
   }
 
-  private short[] computeArrayFilledData(ConstInstruction[] values, int size, int elementSize) {
-    if (values == null) {
-      return null;
+  private short[] computeArrayFilledData(Value[] values, int size, int elementSize) {
+    for (Value v : values) {
+      if (!v.isConstant()) {
+        return null;
+      }
     }
     if (elementSize == 1) {
       short[] result = new short[(size + 1) / 2];
       for (int i = 0; i < size; i += 2) {
-        short value = (short) (values[i].asConstNumber().getIntValue() & 0xFF);
+        short value =
+            (short) (values[i].getConstInstruction().asConstNumber().getIntValue() & 0xFF);
         if (i + 1 < size) {
-          value |= (short) ((values[i + 1].asConstNumber().getIntValue() & 0xFF) << 8);
+          value |=
+              (short)
+                  ((values[i + 1].getConstInstruction().asConstNumber().getIntValue() & 0xFF) << 8);
         }
         result[i / 2] = value;
       }
@@ -2120,7 +2126,7 @@
     int shortsPerConstant = elementSize / 2;
     short[] result = new short[size * shortsPerConstant];
     for (int i = 0; i < size; i++) {
-      long value = values[i].asConstNumber().getRawValue();
+      long value = values[i].getConstInstruction().asConstNumber().getRawValue();
       for (int part = 0; part < shortsPerConstant; part++) {
         result[i * shortsPerConstant + part] = (short) ((value >> (16 * part)) & 0xFFFFL);
       }
@@ -2128,185 +2134,346 @@
     return result;
   }
 
-  private ConstInstruction[] computeConstantArrayValues(
-      NewArrayEmpty newArray, BasicBlock block, int size) {
-    if (size > MAX_FILL_ARRAY_SIZE) {
-      return null;
+  private static class FilledArrayConversionInfo {
+
+    Value[] values;
+    List<ArrayPut> arrayPutsToRemove;
+    LinearFlowInstructionListIterator lastArrayPutIterator;
+
+    public FilledArrayConversionInfo(int size) {
+      values = new Value[size];
+      arrayPutsToRemove = new ArrayList<>(size);
     }
-    ConstInstruction[] values = new ConstInstruction[size];
+  }
+
+  private FilledArrayConversionInfo computeConversionInfo(
+      FilledArrayCandidate candidate, LinearFlowInstructionListIterator it) {
+    NewArrayEmpty newArrayEmpty = candidate.newArrayEmpty;
+    assert it.peekPrevious() == newArrayEmpty;
+    Value arrayValue = newArrayEmpty.outValue();
+    int size = candidate.size;
+
+    // aput-object allows any object for arrays of interfaces, but new-filled-array fails to verify
+    // if types require a cast.
+    // TODO(b/246971330): Check if adding a checked-cast would have the same observable result. E.g.
+    //   if aput-object throws a ClassCastException if given an object that does not implement the
+    //   desired interface, then we could add check-cast instructions for arguments we're not sure
+    //   about.
+    DexType elementType = newArrayEmpty.type.toDimensionMinusOneType(dexItemFactory);
+    boolean needsTypeCheck =
+        !elementType.isPrimitiveType() && elementType != dexItemFactory.objectType;
+
+    FilledArrayConversionInfo info = new FilledArrayConversionInfo(size);
+    Value[] values = info.values;
     int remaining = size;
-    Set<Instruction> users = newArray.outValue().uniqueUsers();
-    Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
-    // We allow the array instantiations to cross block boundaries as long as it hasn't encountered
-    // an instruction instance that can throw an exception.
-    InstructionIterator it = block.iterator();
-    it.nextUntil(i -> i == newArray);
-    do {
-      visitedBlocks.add(block);
-      while (it.hasNext()) {
-        Instruction instruction = it.next();
-        // If we encounter an instruction that can throw an exception we need to bail out of the
-        // optimization so that we do not transform half-initialized arrays into fully initialized
-        // arrays on exceptional edges. If the block has no handlers it is not observable so
-        // we perform the rewriting.
-        if (block.hasCatchHandlers() && instruction.instructionInstanceCanThrow()) {
+    Set<Instruction> users = newArrayEmpty.outValue().uniqueUsers();
+    while (it.hasNext()) {
+      Instruction instruction = it.next();
+      BasicBlock block = instruction.getBlock();
+      // If we encounter an instruction that can throw an exception we need to bail out of the
+      // optimization so that we do not transform half-initialized arrays into fully initialized
+      // arrays on exceptional edges. If the block has no handlers it is not observable so
+      // we perform the rewriting.
+      if (block.hasCatchHandlers() && instruction.instructionInstanceCanThrow()) {
+        return null;
+      }
+      if (!users.contains(instruction)) {
+        // If any instruction can transfer control between the new-array and the last array put
+        // then it is not safe to move the new array to the point of the last put.
+        if (block.hasCatchHandlers() && instruction.instructionTypeCanThrow()) {
           return null;
         }
-        if (!users.contains(instruction)) {
-          continue;
-        }
-        // If the initialization sequence is broken by another use we cannot use a
-        // fill-array-data instruction.
-        if (!instruction.isArrayPut()) {
+        continue;
+      }
+      ArrayPut arrayPut = instruction.asArrayPut();
+      // If the initialization sequence is broken by another use we cannot use a fill-array-data
+      // instruction.
+      if (arrayPut == null || arrayPut.array() != arrayValue) {
+        return null;
+      }
+      if (!arrayPut.index().isConstNumber()) {
+        return null;
+      }
+      if (arrayPut.instructionInstanceCanThrow()) {
+        assert false;
+        return null;
+      }
+      int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
+      if (index < 0 || index >= values.length) {
+        return null;
+      }
+      if (values[index] != null) {
+        return null;
+      }
+      Value value = arrayPut.value();
+      if (needsTypeCheck && !value.isAlwaysNull(appView)) {
+        DexType valueDexType = value.getType().asReferenceType().toDexType(dexItemFactory);
+        if (elementType.isArrayType()) {
+          if (elementType != valueDexType) {
+            return null;
+          }
+        } else if (valueDexType.isArrayType()) {
+          // isSubtype asserts for this case.
           return null;
-        }
-        ArrayPut arrayPut = instruction.asArrayPut();
-        if (!(arrayPut.value().isConstant() && arrayPut.index().isConstNumber())) {
-          return null;
-        }
-        int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
-        if (index < 0 || index >= values.length) {
-          return null;
-        }
-        if (values[index] != null) {
-          return null;
-        }
-        ConstInstruction value = arrayPut.value().getConstInstruction();
-        values[index] = value;
-        --remaining;
-        if (remaining == 0) {
-          return values;
+        } else if (valueDexType.isNullValueType()) {
+          // Assume instructions can cause value.isAlwaysNull() == false while the DexType is null.
+          // TODO(b/246971330): Figure out how to write a test in SimplifyArrayConstructionTest
+          //   that hits this case.
+        } else {
+          // TODO(b/246971330): When in d8 mode, we might still be able to see if this is true for
+          //   library types (which this helper does not do).
+          if (appView.isSubtype(valueDexType, elementType).isPossiblyFalse()) {
+            return null;
+          }
         }
       }
-      BasicBlock nextBlock = block.exit().isGoto() ? block.exit().asGoto().getTarget() : null;
-      block = nextBlock != null && !visitedBlocks.contains(nextBlock) ? nextBlock : null;
-      it = block != null ? block.iterator() : null;
-    } while (it != null);
+      info.arrayPutsToRemove.add(arrayPut);
+      values[index] = value;
+      --remaining;
+      if (remaining == 0) {
+        info.lastArrayPutIterator = it;
+        return info;
+      }
+    }
     return null;
   }
 
-  private boolean allowNewFilledArrayConstruction(Instruction instruction) {
-    if (!(instruction instanceof NewArrayEmpty)) {
+  private static class FilledArrayCandidate {
+    final NewArrayEmpty newArrayEmpty;
+    final int size;
+    final boolean encodeAsFilledNewArray;
+
+    public FilledArrayCandidate(
+        NewArrayEmpty newArrayEmpty, int size, boolean encodeAsFilledNewArray) {
+      assert size > 0;
+      this.newArrayEmpty = newArrayEmpty;
+      this.size = size;
+      this.encodeAsFilledNewArray = encodeAsFilledNewArray;
+    }
+
+    public boolean useFilledNewArray() {
+      return encodeAsFilledNewArray;
+    }
+
+    public boolean useFilledArrayData() {
+      return !useFilledNewArray();
+    }
+  }
+
+  private FilledArrayCandidate computeFilledArrayCandiate(
+      Instruction instruction, RewriteArrayOptions options) {
+    NewArrayEmpty newArrayEmpty = instruction.asNewArrayEmpty();
+    if (newArrayEmpty == null) {
+      return null;
+    }
+    if (instruction.getLocalInfo() != null) {
+      return null;
+    }
+    if (!newArrayEmpty.size().isConstant()) {
+      return null;
+    }
+    int size = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
+    if (!options.isPotentialSize(size)) {
+      return null;
+    }
+    DexType arrayType = newArrayEmpty.type;
+    boolean encodeAsFilledNewArray = canUseFilledNewArray(arrayType, size, options);
+    if (!encodeAsFilledNewArray && !canUseFilledArrayData(arrayType, size, options)) {
+      return null;
+    }
+    return new FilledArrayCandidate(newArrayEmpty, size, encodeAsFilledNewArray);
+  }
+
+  private boolean canUseFilledNewArray(DexType arrayType, int size, RewriteArrayOptions options) {
+    if (size < options.minSizeForFilledNewArray) {
       return false;
     }
-    NewArrayEmpty newArray = instruction.asNewArrayEmpty();
-    if (!newArray.size().isConstant()) {
-      return false;
-    }
-    assert newArray.size().isConstNumber();
-    int size = newArray.size().getConstInstruction().asConstNumber().getIntValue();
-    if (size < 1) {
-      return false;
-    }
-    if (newArray.type.isPrimitiveArrayType()) {
+    // filled-new-array is implemented only for int[] and Object[].
+    // For int[], using filled-new-array is usually smaller than filled-array-data.
+    // filled-new-array supports up to 5 registers before it's filled-new-array/range.
+    if (!arrayType.isPrimitiveArrayType()) {
+      if (size > options.maxSizeForFilledNewArrayOfReferences) {
+        return false;
+      }
+      if (arrayType == dexItemFactory.stringArrayType) {
+        return options.canUseFilledNewArrayOfStrings();
+      }
+      if (!options.canUseFilledNewArrayOfNonStringObjects()) {
+        return false;
+      }
+      if (!options.canUseFilledNewArrayOfArrays()
+          && arrayType.getNumberOfLeadingSquareBrackets() > 1) {
+        return false;
+      }
       return true;
     }
-    return newArray.type == dexItemFactory.stringArrayType
-        && options.canUseFilledNewArrayOfObjects();
+    if (arrayType == dexItemFactory.intArrayType) {
+      return size <= options.maxSizeForFilledNewArrayOfInts;
+    }
+    return false;
+  }
+
+  private boolean canUseFilledArrayData(DexType arrayType, int size, RewriteArrayOptions options) {
+    // If there is only one element it is typically smaller to generate the array put
+    // instruction instead of fill array data.
+    if (size < options.minSizeForFilledArrayData || size > options.maxSizeForFilledArrayData) {
+      return false;
+    }
+    return arrayType.isPrimitiveArrayType();
   }
 
   /**
-   * Replace new-array followed by stores of constants to all entries with new-array
-   * and fill-array-data / filled-new-array.
+   * Replace new-array followed by stores of constants to all entries with new-array and
+   * fill-array-data / filled-new-array.
+   *
+   * <p>The format of the new-array and its puts must be of the form:
+   *
+   * <pre>
+   *   v0 <- new-array T vSize
+   *   ...
+   *   array-put v0 vValue1 vIndex1
+   *   ...
+   *   array-put v0 vValueN vIndexN
+   * </pre>
+   *
+   * <p>The flow between the array v0 and its puts must be linear with no other uses of v0 besides
+   * the array-put instructions, thus any no intermediate instruction (... above) must use v0 and
+   * also cannot have catch handlers that would transfer out control (those could then have uses of
+   * v0).
+   *
+   * <p>The allocation of the new-array can itself have catch handlers, in which case, those are to
+   * remain active on the translated code. Translated code can have two forms.
+   *
+   * <p>The first is using the original array allocation and filling in its data if it can be
+   * encoded:
+   *
+   * <pre>
+   *   v0 <- new-array T vSize
+   *   filled-array-data v0
+   *   ...
+   *   ...
+   * </pre>
+   *
+   * The data payload is encoded directly in the instruction so no dependencies are needed for
+   * filling the data array. Thus, the fill is inserted at the point of the allocation. If the
+   * allocation has catch handlers its block must be split and the handlers put on the fill
+   * instruction too. This is correct only because there are no exceptional transfers in (...) that
+   * could observe the early initialization of the data.
+   *
+   * <p>The second is using filled-new-array and has the form:
+   *
+   * <pre>
+   * ...
+   * ...
+   * v0 <- filled-new-array T vValue1 ... vValueN
+   * </pre>
+   *
+   * Here the correctness relies on no exceptional transfers in (...) that could observe the missing
+   * allocation of the array. The late allocation ensures that the values are available at
+   * allocation time. If the original allocation has catch handlers then the new allocation needs to
+   * link those too. In general that may require splitting the block twice so that the new
+   * allocation is the single throwing instruction in its block.
    */
   public void simplifyArrayConstruction(IRCode code) {
     if (options.isGeneratingClassFiles()) {
       return;
     }
-    for (BasicBlock block : code.blocks) {
-      // Map from the array value to the number of array put instruction to remove for that value.
-      Map<Value, Instruction> instructionToInsertForArray = new HashMap<>();
-      Map<Value, Integer> storesToRemoveForArray = new HashMap<>();
-      // First pass: identify candidates and insert fill array data instruction.
-      InstructionListIterator it = block.listIterator(code);
-      while (it.hasNext()) {
-        Instruction instruction = it.next();
-        if (instruction.getLocalInfo() != null || !allowNewFilledArrayConstruction(instruction)) {
-          continue;
+    WorkList<BasicBlock> worklist = WorkList.newIdentityWorkList(code.blocks);
+    while (worklist.hasNext()) {
+      BasicBlock block = worklist.next();
+      simplifyArrayConstructionBlock(block, worklist, code, options);
+    }
+  }
+
+  private void simplifyArrayConstructionBlock(
+      BasicBlock block, WorkList<BasicBlock> worklist, IRCode code, InternalOptions options) {
+    RewriteArrayOptions rewriteOptions = options.rewriteArrayOptions();
+    InstructionListIterator it = block.listIterator(code);
+    while (it.hasNext()) {
+      FilledArrayCandidate candidate = computeFilledArrayCandiate(it.next(), rewriteOptions);
+      if (candidate == null) {
+        continue;
+      }
+      FilledArrayConversionInfo info =
+          computeConversionInfo(
+              candidate, new LinearFlowInstructionListIterator(code, block, it.nextIndex()));
+      if (info == null) {
+        continue;
+      }
+
+      Instruction instructionAfterCandidate = it.peekNext();
+      NewArrayEmpty newArrayEmpty = candidate.newArrayEmpty;
+      DexType arrayType = newArrayEmpty.type;
+      int size = candidate.size;
+      Set<Instruction> instructionsToRemove = SetUtils.newIdentityHashSet(size + 1);
+      if (candidate.useFilledNewArray()) {
+        assert newArrayEmpty.getLocalInfo() == null;
+        Instruction lastArrayPut = info.lastArrayPutIterator.peekPrevious();
+        Value invokeValue = code.createValue(newArrayEmpty.getOutType(), null);
+        InvokeNewArray invoke =
+            new InvokeNewArray(arrayType, invokeValue, Arrays.asList(info.values));
+        invoke.setPosition(lastArrayPut.getPosition());
+        for (Value value : newArrayEmpty.inValues()) {
+          value.removeUser(newArrayEmpty);
         }
-        NewArrayEmpty newArray = instruction.asNewArrayEmpty();
-        int size = newArray.size().getConstInstruction().asConstNumber().getIntValue();
-        ConstInstruction[] values = computeConstantArrayValues(newArray, block, size);
-        if (values == null) {
-          continue;
-        }
-        if (newArray.type == dexItemFactory.stringArrayType) {
-          // Don't replace with filled-new-array if it requires more than 200 consecutive registers.
-          if (size > 200) {
-            continue;
-          }
-          List<Value> stringValues = new ArrayList<>(size);
-          for (ConstInstruction value : values) {
-            stringValues.add(value.outValue());
-          }
-          Value invokeValue = code.createValue(newArray.getOutType(), newArray.getLocalInfo());
-          InvokeNewArray invoke =
-              new InvokeNewArray(dexItemFactory.stringArrayType, invokeValue, stringValues);
-          for (Value value : newArray.inValues()) {
-            value.removeUser(newArray);
-          }
-          newArray.outValue().replaceUsers(invokeValue);
-          it.removeOrReplaceByDebugLocalRead();
-          instructionToInsertForArray.put(invokeValue, invoke);
-          storesToRemoveForArray.put(invokeValue, size);
+        newArrayEmpty.outValue().replaceUsers(invokeValue);
+        instructionsToRemove.add(newArrayEmpty);
+
+        boolean originalAllocationPointHasHandlers = block.hasCatchHandlers();
+        boolean insertionPointHasHandlers = lastArrayPut.getBlock().hasCatchHandlers();
+
+        if (!insertionPointHasHandlers && !originalAllocationPointHasHandlers) {
+          info.lastArrayPutIterator.add(invoke);
         } else {
-          // If there is only one element it is typically smaller to generate the array put
-          // instruction instead of fill array data.
-          if (size == 1) {
-            continue;
+          BasicBlock insertionBlock = info.lastArrayPutIterator.split(code);
+          if (originalAllocationPointHasHandlers) {
+            if (!insertionBlock.isTrivialGoto()) {
+              BasicBlock blockAfterInsertion = insertionBlock.listIterator(code).split(code);
+              assert insertionBlock.isTrivialGoto();
+              worklist.addIfNotSeen(blockAfterInsertion);
+            }
+            insertionBlock.moveCatchHandlers(block);
+          } else {
+            worklist.addIfNotSeen(insertionBlock);
           }
-          int elementSize = newArray.type.elementSizeForPrimitiveArrayType();
-          short[] contents = computeArrayFilledData(values, size, elementSize);
-          if (contents == null) {
-            continue;
-          }
-          if (block.hasCatchHandlers()) {
-            continue;
-          }
-          int arraySize = newArray.size().getConstInstruction().asConstNumber().getIntValue();
-          NewArrayFilledData fillArray =
-              new NewArrayFilledData(newArray.outValue(), elementSize, arraySize, contents);
-          fillArray.setPosition(newArray.getPosition());
-          it.add(fillArray);
-          storesToRemoveForArray.put(newArray.outValue(), size);
+          insertionBlock.listIterator(code).add(invoke);
+        }
+      } else {
+        assert candidate.useFilledArrayData();
+        int elementSize = arrayType.elementSizeForPrimitiveArrayType();
+        short[] contents = computeArrayFilledData(info.values, size, elementSize);
+        if (contents == null) {
+          continue;
+        }
+        // fill-array-data requires the new-array-empty instruction to remain, as it does not
+        // itself create an array.
+        NewArrayFilledData fillArray =
+            new NewArrayFilledData(newArrayEmpty.outValue(), elementSize, size, contents);
+        fillArray.setPosition(newArrayEmpty.getPosition());
+        BasicBlock newBlock =
+            it.addThrowingInstructionToPossiblyThrowingBlock(code, null, fillArray, options);
+        if (newBlock != null) {
+          worklist.addIfNotSeen(newBlock);
         }
       }
-      // Second pass: remove all the array put instructions for the array for which we have
-      // inserted a fill array data instruction instead.
-      if (!storesToRemoveForArray.isEmpty()) {
-        Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
-        do {
-          visitedBlocks.add(block);
-          it = block.listIterator(code);
-          while (it.hasNext()) {
-            Instruction instruction = it.next();
-            if (instruction.isArrayPut()) {
-              Value array = instruction.asArrayPut().array();
-              Integer toRemoveCount = storesToRemoveForArray.get(array);
-              if (toRemoveCount != null) {
-                if (toRemoveCount > 0) {
-                  storesToRemoveForArray.put(array, --toRemoveCount);
-                  it.remove();
-                }
-                if (toRemoveCount == 0) {
-                  storesToRemoveForArray.put(array, --toRemoveCount);
-                  Instruction construction = instructionToInsertForArray.get(array);
-                  if (construction != null) {
-                    // Set the position of the new array construction to be the position of the
-                    // last removed put at which point we are now adding the construction.
-                    construction.setPosition(instruction.getPosition());
-                    it.add(construction);
-                  }
-                }
-              }
+
+      instructionsToRemove.addAll(info.arrayPutsToRemove);
+      Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
+      for (Instruction instruction : instructionsToRemove) {
+        BasicBlock ownerBlock = instruction.getBlock();
+        if (visitedBlocks.add(ownerBlock)) {
+          InstructionListIterator removeIt = ownerBlock.listIterator(code);
+          while (removeIt.hasNext()) {
+            if (instructionsToRemove.contains(removeIt.next())) {
+              removeIt.removeOrReplaceByDebugLocalRead();
             }
           }
-          BasicBlock nextBlock = block.exit().isGoto() ? block.exit().asGoto().getTarget() : null;
-          block = nextBlock != null && !visitedBlocks.contains(nextBlock) ? nextBlock : null;
-        } while (block != null);
+        }
       }
+
+      // The above has invalidated the block iterator so reset it and continue.
+      it = block.listIterator(code, instructionAfterCandidate);
     }
-    assert code.isConsistentSSA(appView);
   }
 
   // TODO(mikaelpeltier) Manage that from and to instruction do not belong to the same block.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index a649af6..6fb553c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -18,6 +18,7 @@
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_CUSTOM;
 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_NEW_ARRAY;
 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;
@@ -68,6 +69,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeCustom;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.MemberType;
@@ -1060,6 +1062,10 @@
             instruction.asInstancePut(), code, context, enumClass, enumValue);
       case INVOKE_DIRECT:
       case INVOKE_INTERFACE:
+        return analyzeInvokeUser(instruction.asInvokeMethod(), code, context, enumClass, enumValue);
+      case INVOKE_NEW_ARRAY:
+        return analyzeInvokeNewArrayUser(
+            instruction.asInvokeNewArray(), code, context, enumClass, enumValue);
       case INVOKE_STATIC:
       case INVOKE_SUPER:
       case INVOKE_VIRTUAL:
@@ -1130,6 +1136,40 @@
     return Reason.INVALID_ARRAY_PUT;
   }
 
+  private Reason analyzeInvokeNewArrayUser(
+      InvokeNewArray invokeNewArray,
+      IRCode code,
+      ProgramMethod context,
+      DexProgramClass enumClass,
+      Value enumValue) {
+    // MyEnum[] array = new MyEnum[] { MyEnum.A }; is valid.
+    // We need to prove that the value to put in and the array have correct types.
+    TypeElement arrayType = invokeNewArray.getOutType();
+    assert arrayType.isArrayType();
+
+    ClassTypeElement arrayBaseType = arrayType.asArrayType().getBaseType().asClassType();
+    if (arrayBaseType == null) {
+      assert false;
+      return Reason.INVALID_INVOKE_NEW_ARRAY;
+    }
+    if (arrayBaseType.getClassType() != enumClass.type) {
+      return Reason.INVALID_INVOKE_NEW_ARRAY;
+    }
+
+    for (Value value : invokeNewArray.inValues()) {
+      TypeElement valueBaseType = value.getType();
+      if (valueBaseType.isArrayType()) {
+        assert valueBaseType.asArrayType().getBaseType().isClassType();
+        assert valueBaseType.asArrayType().getNesting() == arrayType.asArrayType().getNesting() - 1;
+        valueBaseType = valueBaseType.asArrayType().getBaseType();
+      }
+      if (!arrayBaseType.equalUpToNullability(valueBaseType)) {
+        return Reason.INVALID_INVOKE_NEW_ARRAY;
+      }
+    }
+    return Reason.ELIGIBLE;
+  }
+
   private Reason analyzeCheckCastUser(
       CheckCast checkCast,
       IRCode code,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
index 3e17a0a..7cb0290 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
@@ -32,6 +32,8 @@
       new StringReason("IMPLICIT_UP_CAST_IN_RETURN");
   public static final Reason INVALID_FIELD_PUT = new StringReason("INVALID_FIELD_PUT");
   public static final Reason INVALID_ARRAY_PUT = new StringReason("INVALID_ARRAY_PUT");
+  public static final Reason INVALID_INVOKE_NEW_ARRAY =
+      new StringReason("INVALID_INVOKE_NEW_ARRAY");
   public static final Reason TYPE_MISMATCH_FIELD_PUT = new StringReason("TYPE_MISMATCH_FIELD_PUT");
   public static final Reason INVALID_IF_TYPES = new StringReason("INVALID_IF_TYPES");
   public static final Reason ASSIGNMENT_OUTSIDE_INIT = new StringReason("ASSIGNMENT_OUTSIDE_INIT");
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ConstClassSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/ConstClassSourceCode.java
new file mode 100644
index 0000000..b1c04e9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ConstClassSourceCode.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.synthetic;
+
+import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.ArrayList;
+import java.util.List;
+
+// Source code representing a simple call to a const class.
+public final class ConstClassSourceCode extends SyntheticCfCodeProvider {
+
+  private final DexType constClassType;
+
+  private ConstClassSourceCode(AppView<?> appView, DexType holder, DexType constClassType) {
+    super(appView, holder);
+    this.constClassType = constClassType;
+  }
+
+  public static ConstClassSourceCode create(
+      AppView<?> appView, DexType holder, DexType checkCastType) {
+    return new ConstClassSourceCode(appView, holder, checkCastType);
+  }
+
+  @Override
+  public CfCode generateCfCode() {
+    List<CfInstruction> instructions = new ArrayList<>();
+    instructions.add(new CfConstClass(constClassType));
+    instructions.add(new CfReturn(ValueType.OBJECT));
+    return standardCfCodeFromInstructions(instructions);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/InstanceOfSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/InstanceOfSourceCode.java
index 57257ff..3f32d83 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/InstanceOfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/InstanceOfSourceCode.java
@@ -15,7 +15,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
-// Source code representing a simple call to CheckCast.
+// Source code representing a simple call to instance-of.
 public final class InstanceOfSourceCode extends SyntheticCfCodeProvider {
 
   private final DexType instanceOfType;
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index 3e4078b..6e14907 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
@@ -424,12 +425,16 @@
 
   // Perform a conservative evaluation of an array content of dex type values from its construction
   // until its use at a given instruction.
-  private static DexType[] evaluateTypeArrayContentFromConstructionToUse(
-      NewArrayEmpty newArray,
-      List<CheckCast> aliases,
-      int size,
-      Instruction user,
-      DexItemFactory factory) {
+  private static DexTypeList evaluateTypeArrayContentFromConstructionToUse(
+      NewArrayEmpty newArray, List<CheckCast> aliases, Instruction user, DexItemFactory factory) {
+    int size = newArray.sizeIfConst();
+    if (size < 0) {
+      return null;
+    } else if (size == 0) {
+      // TODO: We should likely still scan to ensure no ArrayPut instructions exist.
+      return DexTypeList.empty();
+    }
+
     DexType[] values = new DexType[size];
     int remaining = size;
     Set<Instruction> users = Sets.newIdentityHashSet();
@@ -453,7 +458,7 @@
         if (instruction == user) {
           // Return the array content if all elements are known when hitting the user for which
           // the content was requested.
-          return remaining == 0 ? values : null;
+          return remaining == 0 ? new DexTypeList(values) : null;
         }
         // Any other kinds of use besides array-put mean that the array escapes and its content
         // could be altered.
@@ -498,6 +503,21 @@
     return null;
   }
 
+  private static DexTypeList evaluateTypeArrayContent(
+      InvokeNewArray newArray, DexItemFactory factory) {
+    List<Value> arrayValues = newArray.inValues();
+    int size = arrayValues.size();
+    DexType[] values = new DexType[size];
+    for (int i = 0; i < size; ++i) {
+      DexType type = getTypeFromConstClassOrBoxedPrimitive(arrayValues.get(i), factory);
+      if (type == null) {
+        return null;
+      }
+      values[i] = type;
+    }
+    return new DexTypeList(values);
+  }
+
   /**
    * Visits all {@link ArrayPut}'s with the given {@param classListValue} as array and {@link Class}
    * as value. Then collects all corresponding {@link DexType}s so as to determine reflective cases.
@@ -551,30 +571,13 @@
     }
 
     // Make sure this Value refers to a new array.
-    if (!classListValue.definition.isNewArrayEmpty()
-        || !classListValue.definition.asNewArrayEmpty().size().isConstant()) {
+    if (classListValue.definition.isNewArrayEmpty()) {
+      return evaluateTypeArrayContentFromConstructionToUse(
+          classListValue.definition.asNewArrayEmpty(), aliases, invoke, factory);
+    } else if (classListValue.definition.isInvokeNewArray()) {
+      return evaluateTypeArrayContent(classListValue.definition.asInvokeNewArray(), factory);
+    } else {
       return null;
     }
-
-    int size =
-        classListValue
-            .definition
-            .asNewArrayEmpty()
-            .size()
-            .getConstInstruction()
-            .asConstNumber()
-            .getIntValue();
-    if (size == 0) {
-      return DexTypeList.empty();
-    }
-
-    DexType[] arrayContent =
-        evaluateTypeArrayContentFromConstructionToUse(
-            classListValue.definition.asNewArrayEmpty(), aliases, size, invoke, factory);
-
-    if (arrayContent == null) {
-      return null;
-    }
-    return new DexTypeList(arrayContent);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
index e5d00a9..40ee16e 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.InnerClassAttribute;
@@ -135,7 +136,11 @@
       // For fields and methods that cannot be found the chance of recovering by repackaging is
       // pretty slim thus we allow for repackaging the callers.
       if (resolutionResult.isFieldResolutionResult()) {
-        assert resolutionResult.asFieldResolutionResult().isFailedResolution();
+        FieldResolutionResult fieldResolutionResult = resolutionResult.asFieldResolutionResult();
+        assert fieldResolutionResult.isFailedResolution()
+            || (fieldResolutionResult.isMultiFieldResolutionResult()
+                && fieldResolutionResult.getProgramField() != null
+                && fieldResolutionResult.getProgramField().getDefinition().isPrivate());
         return;
       }
       MethodResolutionResult methodResult = resolutionResult.asMethodResolutionResult();
diff --git a/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
index 662f9f4..d779378 100644
--- a/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
@@ -7,7 +7,9 @@
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.dex.code.CfOrDexInstruction;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
@@ -15,11 +17,13 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.utils.AndroidApiLevelUtils;
 import java.util.ListIterator;
 
 public class ComputeApiLevelUseRegistry extends UseRegistry<ProgramMethod> {
 
   protected final AppView<?> appView;
+  private final AppInfoWithClassHierarchy appInfoWithClassHierarchy;
   private final AndroidApiLevelCompute apiLevelCompute;
   private final boolean isEnabled;
   private ComputedApiLevel maxApiReferenceLevel;
@@ -28,6 +32,7 @@
       AppView<?> appView, ProgramMethod context, AndroidApiLevelCompute apiLevelCompute) {
     super(appView, context);
     this.appView = appView;
+    this.appInfoWithClassHierarchy = appView.appInfoForDesugaring();
     this.apiLevelCompute = apiLevelCompute;
     isEnabled = apiLevelCompute.isEnabled();
     maxApiReferenceLevel = appView.computedMinApiLevel();
@@ -148,10 +153,28 @@
 
   private void setMaxApiReferenceLevel(DexReference reference) {
     if (isEnabled) {
-      maxApiReferenceLevel =
-          maxApiReferenceLevel.max(
-              apiLevelCompute.computeApiLevelForLibraryReference(
-                  reference, apiLevelCompute.getPlatformApiLevelOrUnknown(appView)));
+      if (reference.isDexType()) {
+        maxApiReferenceLevel =
+            maxApiReferenceLevel.max(
+                apiLevelCompute.computeApiLevelForLibraryReference(
+                    reference, apiLevelCompute.getPlatformApiLevelOrUnknown(appView)));
+      } else if (!reference.getContextType().isClassType()) {
+        maxApiReferenceLevel = maxApiReferenceLevel.max(appView.computedMinApiLevel());
+      } else {
+        DexClass holder = appView.definitionFor(reference.getContextType());
+        ComputedApiLevel referenceApiLevel = ComputedApiLevel.unknown();
+        if (holder != null) {
+          referenceApiLevel =
+              AndroidApiLevelUtils.findAndComputeApiLevelForLibraryDefinition(
+                      appView, appInfoWithClassHierarchy, holder, reference.asDexMember())
+                  .getSecond();
+        }
+        maxApiReferenceLevel =
+            maxApiReferenceLevel.max(
+                referenceApiLevel.isUnknownApiLevel()
+                    ? apiLevelCompute.getPlatformApiLevelOrUnknown(appView)
+                    : referenceApiLevel);
+      }
     }
   }
 
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 213819d..6c5c07b 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -41,6 +41,7 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexDefinition;
@@ -104,7 +105,9 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
@@ -146,6 +149,7 @@
 import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
 import com.android.tools.r8.synthesis.SyntheticItems.SynthesizingContextOracle;
 import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.OptionalBool;
@@ -1092,15 +1096,24 @@
       DexField field, ProgramMethod context, boolean isRead, boolean isReflective) {
     FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field);
     if (info == null) {
-      DexEncodedField encodedField = resolveField(field, context).getResolvedField();
+      Box<DexClassAndField> seenResult = new Box<>();
+      resolveField(field, context)
+          .forEachSuccessfulFieldResolutionResult(
+              singleResolutionResult -> {
+                DexClassAndField resolutionPair = singleResolutionResult.getResolutionPair();
+                if (!seenResult.isSet() || resolutionPair.isProgramField()) {
+                  seenResult.set(resolutionPair);
+                }
+              });
 
       // If the field does not exist, then record this in the mapping, such that we don't have to
       // resolve the field the next time.
-      if (encodedField == null) {
+      if (!seenResult.isSet()) {
         fieldAccessInfoCollection.extend(field, MISSING_FIELD_ACCESS_INFO);
         return true;
       }
 
+      DexEncodedField encodedField = seenResult.get().getDefinition();
       info = getOrCreateFieldAccessInfo(encodedField);
 
       // If `field` is an indirect reference, then create a mapping for it, such that we don't have
@@ -5079,25 +5092,43 @@
       return;
     }
     Value parametersValue = constructorDefinition.inValues().get(1);
-    if (parametersValue.isPhi() || !parametersValue.definition.isNewArrayEmpty()) {
+    if (parametersValue.isPhi()) {
       // Give up, we can't tell which constructor is being invoked.
       return;
     }
-
-    Value parametersSizeValue = parametersValue.definition.asNewArrayEmpty().size();
-    if (parametersSizeValue.isPhi() || !parametersSizeValue.definition.isConstNumber()) {
-      // Give up, we can't tell which constructor is being invoked.
+    NewArrayEmpty newArrayEmpty = parametersValue.definition.asNewArrayEmpty();
+    InvokeNewArray invokeNewArray = parametersValue.definition.asInvokeNewArray();
+    int parametersSize =
+        newArrayEmpty != null
+            ? newArrayEmpty.sizeIfConst()
+            : invokeNewArray != null ? invokeNewArray.size() : -1;
+    if (parametersSize < 0) {
       return;
     }
 
     ProgramMethod initializer = null;
 
-    int parametersSize = parametersSizeValue.definition.asConstNumber().getIntValue();
     if (parametersSize == 0) {
       initializer = clazz.getProgramDefaultInitializer();
     } else {
       DexType[] parameterTypes = new DexType[parametersSize];
       int missingIndices = parametersSize;
+
+      if (newArrayEmpty != null) {
+        missingIndices = parametersSize;
+      } else {
+        missingIndices = 0;
+        List<Value> values = invokeNewArray.inValues();
+        for (int i = 0; i < parametersSize; ++i) {
+          DexType type =
+              ConstantValueUtils.getDexTypeRepresentedByValueForTracing(values.get(i), appView);
+          if (type == null) {
+            return;
+          }
+          parameterTypes[i] = type;
+        }
+      }
+
       for (Instruction user : parametersValue.uniqueUsers()) {
         if (user.isArrayPut()) {
           ArrayPut arrayPutInstruction = user.asArrayPut();
@@ -5159,20 +5190,31 @@
     }
 
     Value interfacesValue = invoke.arguments().get(1);
-    if (interfacesValue.isPhi() || !interfacesValue.definition.isNewArrayEmpty()) {
+    if (interfacesValue.isPhi()) {
       // Give up, we can't tell which interfaces the proxy implements.
       return;
     }
 
-    WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList();
-    for (Instruction user : interfacesValue.uniqueUsers()) {
-      if (!user.isArrayPut()) {
-        continue;
+    InvokeNewArray invokeNewArray = interfacesValue.definition.asInvokeNewArray();
+    NewArrayEmpty newArrayEmpty = interfacesValue.definition.asNewArrayEmpty();
+    List<Value> values;
+    if (invokeNewArray != null) {
+      values = invokeNewArray.inValues();
+    } else if (newArrayEmpty != null) {
+      values = new ArrayList<>(interfacesValue.uniqueUsers().size());
+      for (Instruction user : interfacesValue.uniqueUsers()) {
+        ArrayPut arrayPut = user.asArrayPut();
+        if (arrayPut != null) {
+          values.add(arrayPut.value());
+        }
       }
+    } else {
+      return;
+    }
 
-      ArrayPut arrayPut = user.asArrayPut();
-      DexType type =
-          ConstantValueUtils.getDexTypeRepresentedByValueForTracing(arrayPut.value(), appView);
+    WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList();
+    for (Value value : values) {
+      DexType type = ConstantValueUtils.getDexTypeRepresentedByValueForTracing(value, appView);
       if (type == null || !type.isClassType()) {
         continue;
       }
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
index 2635b95..00f905e 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
@@ -403,6 +403,7 @@
   InternalOptions getInternalOptions() {
     InternalOptions options = new InternalOptions();
     options.loadAllClassDefinitions = true;
+    options.lookupLibraryBeforeProgram = true;
     TraceReferencesConsumer consumer = getConsumer();
     DumpOptions.Builder builder =
         DumpOptions.builder(Tool.TraceReferences)
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
index 7369593..0a8b899 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
@@ -22,6 +23,9 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
+import com.google.common.collect.Sets;
+import java.util.Collections;
+import java.util.Set;
 
 public class AndroidApiLevelUtils {
 
@@ -191,4 +195,109 @@
     return oldBaseLibraryClass != null
         && isApiSafeForReference(newBaseLibraryClass, oldBaseLibraryClass, appView);
   }
+
+  public static Pair<DexClass, ComputedApiLevel> findAndComputeApiLevelForLibraryDefinition(
+      AppView<?> appView,
+      AppInfoWithClassHierarchy appInfo,
+      DexClass holder,
+      DexMember<?, ?> reference) {
+    AndroidApiLevelCompute apiLevelCompute = appView.apiLevelCompute();
+    if (holder.isLibraryClass()) {
+      return Pair.create(
+          holder,
+          apiLevelCompute.computeApiLevelForLibraryReference(
+              reference, ComputedApiLevel.unknown()));
+    }
+    // The API database do not allow for resolving into it (since that is not stable), and it is
+    // therefore designed in a way where all members of classes can be queried on any sub-type with
+    // the api level for where it is reachable. It is therefore sufficient for us, to figure out if
+    // an instruction is a library call, to either find a program definition or to find the library
+    // frontier.
+    // Scan through the type hierarchy to find the first library class or program definition.
+    DexClass firstClassWithReferenceOrLibraryClass =
+        firstLibraryClassOrProgramClassWithDefinition(appInfo, holder, reference);
+    if (firstClassWithReferenceOrLibraryClass == null) {
+      return Pair.create(null, ComputedApiLevel.unknown());
+    }
+    if (!firstClassWithReferenceOrLibraryClass.isLibraryClass()) {
+      return Pair.create(firstClassWithReferenceOrLibraryClass, appView.computedMinApiLevel());
+    }
+    ComputedApiLevel apiLevel =
+        apiLevelCompute.computeApiLevelForLibraryReference(
+            reference.withHolder(
+                firstClassWithReferenceOrLibraryClass.getType(), appView.dexItemFactory()),
+            ComputedApiLevel.unknown());
+    if (apiLevel.isKnownApiLevel()) {
+      return Pair.create(firstClassWithReferenceOrLibraryClass, apiLevel);
+    }
+    // We were unable to find a definition in the class hierarchy, check all interfaces for a
+    // definition or the library interfaces for the first interface definition.
+    Set<DexClass> firstLibraryInterfaces =
+        findAllFirstLibraryInterfacesOrProgramClassWithDefinition(appInfo, holder, reference);
+    if (firstLibraryInterfaces.size() == 1) {
+      DexClass firstClass = firstLibraryInterfaces.iterator().next();
+      if (!firstClass.isLibraryClass()) {
+        return Pair.create(firstClass, appView.computedMinApiLevel());
+      }
+    }
+    DexClass foundClass = null;
+    ComputedApiLevel minApiLevel = ComputedApiLevel.unknown();
+    for (DexClass libraryInterface : firstLibraryInterfaces) {
+      assert libraryInterface.isLibraryClass();
+      ComputedApiLevel libraryIfaceApiLevel =
+          apiLevelCompute.computeApiLevelForLibraryReference(
+              reference.withHolder(
+                  firstClassWithReferenceOrLibraryClass.getType(), appView.dexItemFactory()),
+              ComputedApiLevel.unknown());
+      if (minApiLevel.isGreaterThan(libraryIfaceApiLevel)) {
+        minApiLevel = libraryIfaceApiLevel;
+        foundClass = libraryInterface;
+      }
+    }
+    return Pair.create(foundClass, minApiLevel);
+  }
+
+  private static DexClass firstLibraryClassOrProgramClassWithDefinition(
+      AppInfoWithClassHierarchy appInfo, DexClass originalClass, DexMember<?, ?> reference) {
+    if (originalClass.isLibraryClass()) {
+      return originalClass;
+    }
+    WorkList<DexClass> workList = WorkList.newIdentityWorkList(originalClass);
+    while (workList.hasNext()) {
+      DexClass clazz = workList.next();
+      if (clazz.isLibraryClass()) {
+        return clazz;
+      } else if (clazz.lookupMember(reference) != null) {
+        return clazz;
+      } else if (clazz.getSuperType() != null) {
+        appInfo
+            .contextIndependentDefinitionForWithResolutionResult(clazz.getSuperType())
+            .forEachClassResolutionResult(workList::addIfNotSeen);
+      }
+    }
+    return null;
+  }
+
+  private static Set<DexClass> findAllFirstLibraryInterfacesOrProgramClassWithDefinition(
+      AppInfoWithClassHierarchy appInfo, DexClass originalClass, DexMember<?, ?> reference) {
+    Set<DexClass> interfaces = Sets.newLinkedHashSet();
+    WorkList<DexClass> workList = WorkList.newIdentityWorkList(originalClass);
+    while (workList.hasNext()) {
+      DexClass clazz = workList.next();
+      if (clazz.isLibraryClass()) {
+        if (clazz.isInterface()) {
+          interfaces.add(clazz);
+        }
+      } else if (clazz.lookupMember(reference) != null) {
+        return Collections.singleton(clazz);
+      } else {
+        clazz.forEachImmediateSupertype(
+            superType ->
+                appInfo
+                    .contextIndependentDefinitionForWithResolutionResult(superType)
+                    .forEachClassResolutionResult(workList::addIfNotSeen));
+      }
+    }
+    return interfaces;
+  }
 }
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 3b87985..4f8669b 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -816,6 +816,7 @@
 
   public boolean debug = false;
 
+  private final RewriteArrayOptions rewriteArrayOptions = new RewriteArrayOptions();
   private final CallSiteOptimizationOptions callSiteOptimizationOptions =
       new CallSiteOptimizationOptions();
   private final CfCodeAnalysisOptions cfCodeAnalysisOptions = new CfCodeAnalysisOptions();
@@ -850,6 +851,10 @@
 
   public LineNumberOptimization lineNumberOptimization = LineNumberOptimization.ON;
 
+  public RewriteArrayOptions rewriteArrayOptions() {
+    return rewriteArrayOptions;
+  }
+
   public CallSiteOptimizationOptions callSiteOptimizationOptions() {
     return callSiteOptimizationOptions;
   }
@@ -1383,6 +1388,51 @@
         System.getProperty("com.android.tools.r8.lambdaClassFieldsNotFinal") == null;
   }
 
+  public class RewriteArrayOptions {
+    // Arbitrary limit of number of inputs to new-filled-array/range.
+    // The technical limit is 255 (Constants.U8BIT_MAX).
+    public int minSizeForFilledNewArray = 1;
+    public int maxSizeForFilledNewArrayOfReferences = 200;
+    public int maxSizeForFilledNewArrayOfInts = 5;
+
+    // Arbitrary limits of number of inputs to fill-array-data.
+    public int minSizeForFilledArrayData = 2;
+    public int maxSizeForFilledArrayData = 8 * 1024;
+
+    // Check the most relaxed size range allowed.
+    public boolean isPotentialSize(int size) {
+      return minSizeForFilledNewArray <= size && size <= maxSizeForFilledArrayData;
+    }
+
+    // Dalvik x86-atom backend had a bug that made it crash on filled-new-array instructions for
+    // arrays of objects. This is unfortunate, since this never hits arm devices, but we have
+    // to disallow filled-new-array of objects for dalvik until kitkat. The buggy code was
+    // removed during the jelly-bean release cycle and is not there from kitkat.
+    //
+    // Buggy code that accidentally call code that only works on primitives arrays.
+    //
+    // https://android.googlesource.com/platform/dalvik/+/ics-mr0/vm/mterp/out/InterpAsm-x86-atom.S#25106
+    public boolean canUseFilledNewArrayOfStrings() {
+      assert isGeneratingDex();
+      return hasFeaturePresentFrom(AndroidApiLevel.K);
+    }
+
+    // When adding support for emitting new-filled-array for non-String types, ART 6.0.1 had issues.
+    // https://ci.chromium.org/ui/p/r8/builders/ci/linux-android-6.0.1/6507/overview
+    // It somehow had a new-array-filled return null.
+    public boolean canUseFilledNewArrayOfNonStringObjects() {
+      assert isGeneratingDex();
+      return hasFeaturePresentFrom(AndroidApiLevel.N);
+    }
+
+    // Dalvik doesn't handle new-filled-array with arrays as values. It fails with:
+    // W(629880) VFY: [Ljava/lang/Integer; is not instance of Ljava/lang/Integer;  (dalvikvm)
+    public boolean canUseFilledNewArrayOfArrays() {
+      assert isGeneratingDex();
+      return hasFeaturePresentFrom(AndroidApiLevel.L);
+    }
+  }
+
   public class CallSiteOptimizationOptions {
 
     private boolean enabled = true;
@@ -2424,19 +2474,6 @@
     return hasFeaturePresentFrom(AndroidApiLevel.J);
   }
 
-  // Dalvik x86-atom backend had a bug that made it crash on filled-new-array instructions for
-  // arrays of objects. This is unfortunate, since this never hits arm devices, but we have
-  // to disallow filled-new-array of objects for dalvik until kitkat. The buggy code was
-  // removed during the jelly-bean release cycle and is not there from kitkat.
-  //
-  // Buggy code that accidentally call code that only works on primitives arrays.
-  //
-  // https://android.googlesource.com/platform/dalvik/+/ics-mr0/vm/mterp/out/InterpAsm-x86-atom.S#25106
-  public boolean canUseFilledNewArrayOfObjects() {
-    assert isGeneratingDex();
-    return hasFeaturePresentFrom(AndroidApiLevel.K);
-  }
-
   // Art had a bug (b/68761724) for Android N and O in the arm32 interpreter
   // where an aget-wide instruction using the same register for the array
   // and the first register of the result could lead to the wrong exception
diff --git a/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java b/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java
index 50d23c9..7d41ae1 100644
--- a/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java
+++ b/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.utils;
 
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 import java.util.NoSuchElementException;
 
@@ -32,4 +33,18 @@
     }
     return result;
   }
+
+  public static <T, E extends Exception> ThrowingIterator<T, E> fromIterator(Iterator<T> it) {
+    return new ThrowingIterator<>() {
+      @Override
+      public boolean hasNext() {
+        return it.hasNext();
+      }
+
+      @Override
+      public T next() throws E {
+        return it.next();
+      }
+    };
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
index d83d7e0..6e540b8 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
@@ -14,12 +14,12 @@
 import com.android.tools.r8.graph.DexDebugEvent.RestartLocal;
 import com.android.tools.r8.graph.DexDebugEvent.SetEpilogueBegin;
 import com.android.tools.r8.graph.DexDebugEvent.SetFile;
+import com.android.tools.r8.graph.DexDebugEvent.SetPositionFrame;
 import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
 import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
 import com.android.tools.r8.graph.DexDebugEventBuilder;
 import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
-import com.android.tools.r8.graph.DexDebugInfoForSingleLineMethod;
 import com.android.tools.r8.graph.DexDebugPositionState;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -55,6 +55,10 @@
       this.processedEvents = processedEvents;
     }
 
+    public boolean didEmitLineEvents() {
+      return startLine != -1;
+    }
+
     private void emitAdvancePc(int pc) {
       processedEvents.add(new AdvancePC(pc - previousPc));
       previousPc = pc;
@@ -183,24 +187,25 @@
           }
         };
 
-    for (DexDebugEvent event : debugInfo.events) {
-      event.accept(visitor);
-    }
-
-    // If we only have one line event we can always retrace back uniquely.
-    if (mappedPositions.size() <= 1
-        && !hasOverloads
-        && !appView.options().debug
-        && appView.options().lineNumberOptimization != LineNumberOptimization.OFF
-        && appView.options().allowDiscardingResidualDebugInfo()
-        && (mappedPositions.isEmpty() || !mappedPositions.get(0).isOutlineCaller())) {
-      dexCode.setDebugInfo(DexDebugInfoForSingleLineMethod.getInstance());
-      return mappedPositions;
+    DexDebugEvent[] events = debugInfo.events;
+    if (events.length > 0) {
+      SetPositionFrame preambleFrame = getAsPreambleFrame(events[0]);
+      if (preambleFrame != null) {
+        // The preamble is specially identified here as it is active at method entry and thus not
+        // part of the instruction stream events.
+        Position position = preambleFrame.getPosition();
+        Position newPosition =
+            PositionUtils.remapAndAdd(position, positionRemapper, mappedPositions);
+        processedEvents.add(appView.dexItemFactory().createPositionFrame(newPosition));
+      }
+      for (int i = (preambleFrame == null) ? 0 : 1; i < events.length; i++) {
+        events[i].accept(visitor);
+      }
     }
 
     EventBasedDebugInfo optimizedDebugInfo =
         new EventBasedDebugInfo(
-            positionEventEmitter.getStartLine(),
+            positionEventEmitter.didEmitLineEvents() ? positionEventEmitter.getStartLine() : 0,
             debugInfo.parameters,
             processedEvents.toArray(DexDebugEvent.EMPTY_ARRAY));
 
@@ -212,6 +217,16 @@
     return mappedPositions;
   }
 
+  private SetPositionFrame getAsPreambleFrame(DexDebugEvent event) {
+    SetPositionFrame positionFrame = event.asSetPositionFrame();
+    if (positionFrame != null
+        && positionFrame.getPosition().isSyntheticPosition()
+        && positionFrame.getPosition().getLine() == 0) {
+      return positionFrame;
+    }
+    return null;
+  }
+
   // This conversion *always* creates an event based debug info encoding as any non-info will
   // be created as an implicit PC encoding.
   private static EventBasedDebugInfo getEventBasedDebugInfo(
diff --git a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
index f29f9cb..890cb80 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.utils.positions;
 
 import com.android.tools.r8.debuginfo.DebugRepresentation;
-import com.android.tools.r8.dex.code.DexInstruction;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexDebugEvent;
@@ -13,7 +12,6 @@
 import com.android.tools.r8.graph.DexDebugEventVisitor;
 import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
-import com.android.tools.r8.graph.DexDebugInfoForSingleLineMethod;
 import com.android.tools.r8.graph.DexDebugPositionState;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -23,7 +21,6 @@
 import com.android.tools.r8.ir.code.Position.OutlinePosition;
 import com.android.tools.r8.ir.code.Position.PositionBuilder;
 import com.android.tools.r8.ir.code.Position.SourcePosition;
-import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.positions.PositionToMappedRangeMapper.PcBasedDebugInfoRecorder;
@@ -49,7 +46,6 @@
     EventBasedDebugInfo debugInfo =
         getEventBasedDebugInfo(method.getDefinition(), dexCode, appView);
     IntBox firstDefaultEventPc = new IntBox(-1);
-    BooleanBox singleOriginalLine = new BooleanBox(true);
     Pair<Integer, Position> lastPosition = new Pair<>();
     DexDebugEventVisitor visitor =
         new DexDebugPositionState(
@@ -64,10 +60,6 @@
             }
             Position currentPosition = getPositionFromPositionState(this);
             if (lastPosition.getSecond() != null) {
-              if (singleOriginalLine.isTrue()
-                  && !currentPosition.equals(lastPosition.getSecond())) {
-                singleOriginalLine.set(false);
-              }
               remapAndAddForPc(
                   pcBasedDebugInfo,
                   lastPosition.getFirst(),
@@ -86,20 +78,6 @@
       event.accept(visitor);
     }
 
-    // If the method has a single non-preamble line, check that the preamble is not active on any
-    // throwing instruction before the single line becomes active.
-    if (singleOriginalLine.isTrue() && firstDefaultEventPc.get() > 0) {
-      for (DexInstruction instruction : dexCode.instructions) {
-        if (instruction.getOffset() < firstDefaultEventPc.get()) {
-          if (instruction.canThrow()) {
-            singleOriginalLine.set(false);
-          }
-        } else {
-          break;
-        }
-      }
-    }
-
     int lastInstructionPc = DebugRepresentation.getLastExecutableInstruction(dexCode).getOffset();
     if (lastPosition.getSecond() != null) {
       remapAndAddForPc(
@@ -112,14 +90,7 @@
     }
 
     assert !mappedPositions.isEmpty() || dexCode.instructions.length == 1;
-    if (singleOriginalLine.isTrue()
-        && lastPosition.getSecond() != null
-        && (mappedPositions.isEmpty() || !mappedPositions.get(0).isOutlineCaller())) {
-      dexCode.setDebugInfo(DexDebugInfoForSingleLineMethod.getInstance());
-      pcBasedDebugInfo.recordSingleLineFor(method, pcEncodingCutoff);
-    } else {
-      pcBasedDebugInfo.recordPcMappingFor(method, pcEncodingCutoff);
-    }
+    pcBasedDebugInfo.recordPcMappingFor(method, pcEncodingCutoff);
     return mappedPositions;
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
index ae0eb0e..13f013e 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexDebugInfoForSingleLineMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
@@ -291,17 +290,10 @@
             lastPosition = currentPosition;
           }
         }
-        Range obfuscatedRange;
-        if (definition.getCode().isDexCode()
-            && definition.getCode().asDexCode().getDebugInfo()
-                == DexDebugInfoForSingleLineMethod.getInstance()) {
-          assert firstPosition.getOriginalLine() == lastPosition.getOriginalLine();
-          obfuscatedRange = nonCardinalRangeCache.get(0, MAX_LINE_NUMBER);
-        } else {
-          obfuscatedRange =
-              nonCardinalRangeCache.get(
-                  firstPosition.getObfuscatedLine(), lastPosition.getObfuscatedLine());
-        }
+        Range obfuscatedRange =
+            nonCardinalRangeCache.get(
+                firstPosition.getObfuscatedLine(), lastPosition.getObfuscatedLine());
+
         MappedRange lastMappedRange =
             getMappedRangesForPosition(
                 appView,
diff --git a/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java
index e4cb801..914b174 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java
@@ -8,8 +8,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexDebugInfo;
-import com.android.tools.r8.graph.DexDebugInfoForSingleLineMethod;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.ProgramMethod;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -45,7 +43,7 @@
       pcBasedDebugInfoRecorder =
           appView.options().canUseNativeDexPcInsteadOfDebugInfo()
               ? new NativePcSupport()
-              : new Pc2PcMappingSupport(appView.options().allowDiscardingResidualDebugInfo());
+              : new Pc2PcMappingSupport();
       noPcMapper = new DexPositionToNoPcMappedRangeMapper(appView);
       pcMapper = new DexPositionToPcMappedRangeMapper(appView, pcBasedDebugInfoRecorder);
     }
@@ -57,17 +55,9 @@
         boolean hasOverloads,
         boolean canUseDexPc,
         int pcEncodingCutoff) {
-      List<MappedPosition> mappedPositions =
-          canUseDexPc
-              ? pcMapper.optimizeDexCodePositionsForPc(method, positionRemapper, pcEncodingCutoff)
-              : noPcMapper.optimizeDexCodePositions(method, positionRemapper, hasOverloads);
-      DexEncodedMethod definition = method.getDefinition();
-      if (definition.getCode().isDexCode()
-          && definition.getCode().asDexCode().getDebugInfo()
-              == DexDebugInfoForSingleLineMethod.getInstance()) {
-        pcBasedDebugInfoRecorder.recordSingleLineFor(method, pcEncodingCutoff);
-      }
-      return mappedPositions;
+      return canUseDexPc
+          ? pcMapper.optimizeDexCodePositionsForPc(method, positionRemapper, pcEncodingCutoff)
+          : noPcMapper.optimizeDexCodePositions(method, positionRemapper, hasOverloads);
     }
 
     @Override
@@ -80,9 +70,6 @@
     /** Callback to record a code object with a given max instruction PC and parameter count. */
     void recordPcMappingFor(ProgramMethod method, int maxEncodingPc);
 
-    /** Callback to record a code object with only a single "line". */
-    void recordSingleLineFor(ProgramMethod method, int maxEncodingPc);
-
     /**
      * Install the correct debug info objects.
      *
@@ -124,29 +111,12 @@
 
     private final List<UpdateInfo> codesToUpdate = new ArrayList<>();
 
-    // We can only drop single-line debug info if it is OK to lose the source-file info.
-    // This list is null if we must retain single-line entries.
-    private final List<DexCode> singleLineCodesToClear;
-
-    public Pc2PcMappingSupport(boolean allowDiscardingSourceFile) {
-      singleLineCodesToClear = allowDiscardingSourceFile ? new ArrayList<>() : null;
-    }
-
     @Override
     public int getPcEncoding(int pc) {
       assert pc >= 0;
       return pc + 1;
     }
 
-    private boolean cantAddToClearSet(ProgramMethod method) {
-      assert method.getDefinition().getCode().isDexCode();
-      if (singleLineCodesToClear == null) {
-        return true;
-      }
-      singleLineCodesToClear.add(method.getDefinition().getCode().asDexCode());
-      return false;
-    }
-
     @Override
     public void recordPcMappingFor(ProgramMethod method, int maxEncodingPc) {
       assert method.getDefinition().getCode().isDexCode();
@@ -157,13 +127,6 @@
     }
 
     @Override
-    public void recordSingleLineFor(ProgramMethod method, int maxEncodingPc) {
-      if (cantAddToClearSet(method)) {
-        recordPcMappingFor(method, maxEncodingPc);
-      }
-    }
-
-    @Override
     public void updateDebugInfoInCodeObjects() {
       Map<UpdateInfo, DexDebugInfo> debugInfos = new HashMap<>();
       codesToUpdate.forEach(
@@ -175,9 +138,6 @@
             assert debugInfo.asPcBasedInfo().getMaxPc() == entry.maxEncodingPc;
             entry.code.setDebugInfo(debugInfo);
           });
-      if (singleLineCodesToClear != null) {
-        singleLineCodesToClear.forEach(c -> c.setDebugInfo(null));
-      }
     }
 
     private static DexDebugInfo buildPc2PcDebugInfo(UpdateInfo info) {
@@ -204,11 +164,6 @@
     }
 
     @Override
-    public void recordSingleLineFor(ProgramMethod method, int maxEncodingPc) {
-      clearDebugInfo(method);
-    }
-
-    @Override
     public void updateDebugInfoInCodeObjects() {
       // Already null out the info so nothing to do.
     }
diff --git a/src/test/examplesJava9/varhandle/ArrayOfInt.java b/src/test/examplesJava9/varhandle/ArrayOfInt.java
new file mode 100644
index 0000000..dc5ea71
--- /dev/null
+++ b/src/test/examplesJava9/varhandle/ArrayOfInt.java
@@ -0,0 +1,29 @@
+// 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 varhandle;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+
+public class ArrayOfInt {
+
+  public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
+    VarHandle arrayVarHandle = MethodHandles.arrayElementVarHandle(int[].class);
+    int[] array = new int[2];
+    arrayVarHandle.set(array, 0, 1);
+    System.out.println((int) arrayVarHandle.get(array, 0));
+    System.out.println((int) arrayVarHandle.get(array, 1));
+    arrayVarHandle.compareAndSet(array, 1, 1, 3);
+    System.out.println((int) arrayVarHandle.get(array, 0));
+    System.out.println((int) arrayVarHandle.get(array, 1));
+    arrayVarHandle.compareAndSet(array, 1, 0, 2);
+    System.out.println((int) arrayVarHandle.get(array, 0));
+    System.out.println((int) arrayVarHandle.get(array, 1));
+    // TODO(sgjesse): Handle boxed.
+    // arrayVarHandle.compareAndSet(array, 1, 2, box(3));
+    arrayVarHandle.compareAndSet(array, 1, 2, 3);
+    System.out.println((int) arrayVarHandle.get(array, 0));
+    System.out.println((int) arrayVarHandle.get(array, 1));
+  }
+}
diff --git a/src/test/examplesJava9/varhandle/ArrayOfLong.java b/src/test/examplesJava9/varhandle/ArrayOfLong.java
new file mode 100644
index 0000000..5d3e550
--- /dev/null
+++ b/src/test/examplesJava9/varhandle/ArrayOfLong.java
@@ -0,0 +1,29 @@
+// 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 varhandle;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+
+public class ArrayOfLong {
+
+  public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
+    VarHandle arrayVarHandle = MethodHandles.arrayElementVarHandle(long[].class);
+    long[] array = new long[2];
+    arrayVarHandle.set(array, 0, 1L);
+    System.out.println((long) arrayVarHandle.get(array, 0));
+    System.out.println((long) arrayVarHandle.get(array, 1));
+    arrayVarHandle.compareAndSet(array, 1, 1L, 3L);
+    System.out.println((long) arrayVarHandle.get(array, 0));
+    System.out.println((long) arrayVarHandle.get(array, 1));
+    arrayVarHandle.compareAndSet(array, 1, 0L, 2L);
+    System.out.println((long) arrayVarHandle.get(array, 0));
+    System.out.println((long) arrayVarHandle.get(array, 1));
+    // TODO(sgjesse): Handle boxed.
+    // arrayVarHandle.compareAndSet(array, 1, 2, box(3));
+    arrayVarHandle.compareAndSet(array, 1, 2L, 3L);
+    System.out.println((long) arrayVarHandle.get(array, 0));
+    System.out.println((long) arrayVarHandle.get(array, 1));
+  }
+}
diff --git a/src/test/examplesJava9/varhandle/InstanceIntField.java b/src/test/examplesJava9/varhandle/InstanceIntField.java
new file mode 100644
index 0000000..0320030
--- /dev/null
+++ b/src/test/examplesJava9/varhandle/InstanceIntField.java
@@ -0,0 +1,223 @@
+// 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 varhandle;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+
+public class InstanceIntField {
+
+  private int field;
+
+  private static void checkJavaLangInvokeWrongMethodTypeException(RuntimeException e) {
+    if (e.getClass().getCanonicalName().equals("java.lang.invoke.WrongMethodTypeException")
+        || e.getMessage().equals("java.lang.invoke.WrongMethodTypeException")) {
+      return;
+    }
+    throw e;
+  }
+
+  public static void testSet(VarHandle varHandle) {
+    System.out.println("testGet");
+
+    InstanceIntField instance = new InstanceIntField();
+    System.out.println((int) varHandle.get(instance));
+
+    // int and Integer values.
+    varHandle.set(instance, (int) 1);
+    System.out.println((int) varHandle.get(instance));
+    varHandle.set(instance, Integer.valueOf(2));
+    System.out.println(varHandle.get(instance));
+
+    // int and Integer compatible values.
+    varHandle.set(instance, (byte) 3);
+    System.out.println((int) varHandle.get(instance));
+    varHandle.set(instance, Byte.valueOf((byte) 4));
+    System.out.println((int) varHandle.get(instance));
+    varHandle.set(instance, '0');
+    System.out.println((int) varHandle.get(instance));
+    varHandle.set(instance, Character.valueOf('1'));
+    System.out.println((int) varHandle.get(instance));
+    varHandle.set(instance, (short) 5);
+    System.out.println((int) varHandle.get(instance));
+    varHandle.set(instance, Short.valueOf((short) 6));
+    System.out.println((int) varHandle.get(instance));
+
+    // int and Integer non-compatible values.
+    try {
+      varHandle.set(instance, true);
+    } catch (RuntimeException e) {
+      checkJavaLangInvokeWrongMethodTypeException(e);
+      System.out.println(varHandle.get(instance));
+    }
+    try {
+      varHandle.set(instance, 3L);
+    } catch (RuntimeException e) {
+      checkJavaLangInvokeWrongMethodTypeException(e);
+      System.out.println(varHandle.get(instance));
+    }
+    try {
+      varHandle.set(instance, Long.valueOf(3));
+    } catch (RuntimeException e) {
+      checkJavaLangInvokeWrongMethodTypeException(e);
+      System.out.println(varHandle.get(instance));
+    }
+    try {
+      varHandle.set(instance, "3");
+    } catch (RuntimeException e) {
+      checkJavaLangInvokeWrongMethodTypeException(e);
+      System.out.println(varHandle.get(instance));
+    }
+    try {
+      varHandle.set(instance, 3.0f);
+    } catch (RuntimeException e) {
+      checkJavaLangInvokeWrongMethodTypeException(e);
+      System.out.println(varHandle.get(instance));
+    }
+    try {
+      varHandle.set(instance, 3.0);
+    } catch (RuntimeException e) {
+      checkJavaLangInvokeWrongMethodTypeException(e);
+      System.out.println(varHandle.get(instance));
+    }
+  }
+
+  public static void testCompareAndSet(VarHandle varHandle) {
+    System.out.println("testCompareAndSet");
+
+    InstanceIntField instance = new InstanceIntField();
+
+    // int and Integer values.
+    varHandle.compareAndSet(instance, 1, 2);
+    System.out.println((int) varHandle.get(instance));
+    varHandle.compareAndSet(instance, 0, 1);
+    System.out.println((int) varHandle.get(instance));
+    varHandle.compareAndSet(instance, Integer.valueOf(1), 2);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, 2, Integer.valueOf(3));
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, Integer.valueOf(3), Integer.valueOf(4));
+    System.out.println(varHandle.get(instance));
+
+    // int and Integer compatible values.
+    varHandle.compareAndSet(instance, (byte) 4, 5);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, 5, (byte) 6);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, (byte) 6, (byte) 7);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, Byte.valueOf((byte) 7), (byte) 8);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, (byte) 8, Byte.valueOf((byte) 9));
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, Byte.valueOf((byte) 9), Byte.valueOf((byte) 10));
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, 10, '0');
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, '0', 49);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, '1', '2');
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, (byte) 50, '3');
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, '3', (byte) 52);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, '4', '5');
+    System.out.println(varHandle.get(instance));
+
+    // int and Integer non-compatible values.
+    try {
+      varHandle.compareAndSet(instance, 6L, 7L);
+    } catch (RuntimeException e) {
+      checkJavaLangInvokeWrongMethodTypeException(e);
+      System.out.println(varHandle.get(instance));
+    }
+    try {
+      varHandle.compareAndSet(instance, 6L, 7);
+    } catch (RuntimeException e) {
+      checkJavaLangInvokeWrongMethodTypeException(e);
+      System.out.println(varHandle.get(instance));
+    }
+    try {
+      varHandle.compareAndSet(instance, 6, Long.valueOf(7));
+    } catch (RuntimeException e) {
+      checkJavaLangInvokeWrongMethodTypeException(e);
+      System.out.println(varHandle.get(instance));
+    }
+    try {
+      varHandle.compareAndSet(instance, Long.valueOf(6), 7);
+    } catch (RuntimeException e) {
+      checkJavaLangInvokeWrongMethodTypeException(e);
+      System.out.println(varHandle.get(instance));
+    }
+    try {
+      varHandle.compareAndSet(instance, Long.valueOf(6), Long.valueOf(7));
+    } catch (RuntimeException e) {
+      checkJavaLangInvokeWrongMethodTypeException(e);
+      System.out.println(varHandle.get(instance));
+    }
+    try {
+      varHandle.compareAndSet(instance, 6, 7.0f);
+    } catch (RuntimeException e) {
+      checkJavaLangInvokeWrongMethodTypeException(e);
+      System.out.println(varHandle.get(instance));
+    }
+    try {
+      varHandle.compareAndSet(instance, 6.0f, 7);
+    } catch (RuntimeException e) {
+      checkJavaLangInvokeWrongMethodTypeException(e);
+      System.out.println(varHandle.get(instance));
+    }
+    try {
+      varHandle.compareAndSet(instance, 6.0f, 7.0f);
+    } catch (RuntimeException e) {
+      checkJavaLangInvokeWrongMethodTypeException(e);
+      System.out.println(varHandle.get(instance));
+    }
+    try {
+      varHandle.compareAndSet(instance, 6, 7.0);
+    } catch (RuntimeException e) {
+      checkJavaLangInvokeWrongMethodTypeException(e);
+      System.out.println(varHandle.get(instance));
+    }
+    try {
+      varHandle.compareAndSet(instance, 6.0, 7);
+    } catch (RuntimeException e) {
+      checkJavaLangInvokeWrongMethodTypeException(e);
+      System.out.println(varHandle.get(instance));
+    }
+    try {
+      varHandle.compareAndSet(instance, 6.0, 7.0);
+    } catch (RuntimeException e) {
+      checkJavaLangInvokeWrongMethodTypeException(e);
+      System.out.println(varHandle.get(instance));
+    }
+
+    try {
+      varHandle.compareAndSet(instance, 6, "7");
+    } catch (RuntimeException e) {
+      checkJavaLangInvokeWrongMethodTypeException(e);
+      System.out.println(varHandle.get(instance));
+    }
+    try {
+      varHandle.compareAndSet(instance, "6", 7);
+    } catch (RuntimeException e) {
+      checkJavaLangInvokeWrongMethodTypeException(e);
+      System.out.println(varHandle.get(instance));
+    }
+    try {
+      varHandle.compareAndSet(instance, "6", "7");
+    } catch (RuntimeException e) {
+      checkJavaLangInvokeWrongMethodTypeException(e);
+      System.out.println(varHandle.get(instance));
+    }
+  }
+
+  public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
+    VarHandle varHandle =
+        MethodHandles.lookup().findVarHandle(InstanceIntField.class, "field", int.class);
+    testSet(varHandle);
+    testCompareAndSet(varHandle);
+  }
+}
diff --git a/src/test/examplesJava9/varhandle/InstanceLongField.java b/src/test/examplesJava9/varhandle/InstanceLongField.java
new file mode 100644
index 0000000..dcca70e
--- /dev/null
+++ b/src/test/examplesJava9/varhandle/InstanceLongField.java
@@ -0,0 +1,40 @@
+// 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 varhandle;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+
+public class InstanceLongField {
+
+  private long field;
+
+  public static void testSet(VarHandle varHandle) {
+    System.out.println("testGet");
+
+    InstanceLongField instance = new InstanceLongField();
+
+    System.out.println(varHandle.get(instance));
+    varHandle.set(instance, 1);
+    System.out.println(varHandle.get(instance));
+  }
+
+  public static void testCompareAndSet(VarHandle varHandle) {
+    System.out.println("testCompareAndSet");
+
+    InstanceLongField instance = new InstanceLongField();
+
+    varHandle.compareAndSet(instance, 1, 2);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, 0, 1);
+    System.out.println(varHandle.get(instance));
+  }
+
+  public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
+    VarHandle varHandle =
+        MethodHandles.lookup().findVarHandle(InstanceLongField.class, "field", long.class);
+    testSet(varHandle);
+    testCompareAndSet(varHandle);
+  }
+}
diff --git a/src/test/examplesJava9/varhandle/InstanceObjectField.java b/src/test/examplesJava9/varhandle/InstanceObjectField.java
new file mode 100644
index 0000000..e7b4970
--- /dev/null
+++ b/src/test/examplesJava9/varhandle/InstanceObjectField.java
@@ -0,0 +1,40 @@
+// 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 varhandle;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+
+public class InstanceObjectField {
+
+  private Object field;
+
+  public static void testSet(VarHandle varHandle) {
+    System.out.println("testGet");
+
+    InstanceObjectField instance = new InstanceObjectField();
+
+    System.out.println(varHandle.get(instance));
+    varHandle.set(instance, 1);
+    System.out.println(varHandle.get(instance));
+  }
+
+  public static void testCompareAndSet(VarHandle varHandle) {
+    System.out.println("testCompareAndSet");
+
+    InstanceObjectField instance = new InstanceObjectField();
+
+    varHandle.compareAndSet(instance, 0, 1);
+    System.out.println(varHandle.get(instance));
+    varHandle.compareAndSet(instance, null, 1);
+    System.out.println(varHandle.get(instance));
+  }
+
+  public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
+    VarHandle varHandle =
+        MethodHandles.lookup().findVarHandle(InstanceObjectField.class, "field", Object.class);
+    testSet(varHandle);
+    testCompareAndSet(varHandle);
+  }
+}
diff --git a/src/test/examplesJava9/varhandle/InstanceStringField.java b/src/test/examplesJava9/varhandle/InstanceStringField.java
new file mode 100644
index 0000000..1be6f67
--- /dev/null
+++ b/src/test/examplesJava9/varhandle/InstanceStringField.java
@@ -0,0 +1,45 @@
+// 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 varhandle;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+
+public class InstanceStringField {
+
+  private Object field;
+
+  private static void println(String s) {
+    System.out.println(s);
+  }
+
+  public static void testSet(VarHandle varHandle) {
+    System.out.println("testGet");
+
+    InstanceStringField instance = new InstanceStringField();
+
+    // Then polymorphic invoke will remove the cast and make that as the return type of the get.
+    println((String) varHandle.get(instance));
+    varHandle.set(instance, "1");
+    println((String) varHandle.get(instance));
+  }
+
+  public static void testCompareAndSet(VarHandle varHandle) {
+    System.out.println("testCompareAndSet");
+
+    InstanceStringField instance = new InstanceStringField();
+
+    varHandle.compareAndSet(instance, 0, "1");
+    println((String) varHandle.get(instance));
+    varHandle.compareAndSet(instance, null, "1");
+    println((String) varHandle.get(instance));
+  }
+
+  public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
+    VarHandle varHandle =
+        MethodHandles.lookup().findVarHandle(InstanceStringField.class, "field", Object.class);
+    testSet(varHandle);
+    testCompareAndSet(varHandle);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index dd84b92..c62a34c 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -153,13 +153,6 @@
     return withDexRuntimeFilter(vm -> vm == runtime);
   }
 
-  /** Add all available CF runtimes between {@param startInclusive} and {@param endInclusive}. */
-  public TestParametersBuilder withDexRuntimes(
-      DexVm.Version startInclusive, DexVm.Version endInclusive) {
-    return withDexRuntimeFilter(
-        vm -> startInclusive.isOlderThanOrEqual(vm) && vm.isOlderThanOrEqual(endInclusive));
-  }
-
   /** Add all available DEX runtimes that support native multidex. */
   public TestParametersBuilder withNativeMultidexDexRuntimes() {
     return withDexRuntimesStartingFromIncluding(DexVm.Version.V5_1_1);
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java
index f859e30..3c2923d 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.apimodel.ApiModelingTestHelper.ApiModelingMethodVerificationHelper;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -103,7 +104,7 @@
         .apply(this::setupTestBuilder)
         .compile()
         .apply(this::setupRunEnvironment)
-        .inspect(this::inspect)
+        .inspect(inspector -> inspect(inspector, false))
         .run(parameters.getRuntime(), Main.class)
         .apply(result -> checkOutput(result, false));
   }
@@ -115,7 +116,7 @@
         .apply(this::setupTestBuilder)
         .compile()
         .apply(this::setupRunEnvironment)
-        .inspect(this::inspect)
+        .inspect(inspector -> inspect(inspector, false))
         .run(parameters.getRuntime(), Main.class)
         .apply(result -> checkOutput(result, false));
   }
@@ -127,7 +128,7 @@
         .addKeepMainRule(Main.class)
         .addKeepClassAndMembersRules(ProgramJoiner.class)
         .compile()
-        .inspect(this::inspect)
+        .inspect(inspector -> inspect(inspector, true))
         .apply(this::setupRunEnvironment)
         .run(parameters.getRuntime(), Main.class)
         .apply(result -> checkOutput(result, true));
@@ -155,17 +156,25 @@
         result -> result.assertStderrMatches(not(containsString("This dex file is invalid"))));
   }
 
-  private void inspect(CodeInspector inspector) throws Exception {
-    // TODO(b/254510678): We should outline the call to ProgramJoiner.foo.
-    verifyThat(
+  private void inspect(CodeInspector inspector, boolean isR8) throws Exception {
+    ApiModelingMethodVerificationHelper verifyHelper =
+        verifyThat(
             inspector,
             parameters,
             Reference.method(
-                Reference.classFromClass(ProgramJoiner.class),
+                // TODO(b/254510678): Due to member rebinding, we rebind ProgramJoiner.foo() to
+                //  LibraryClass.foo().
+                Reference.classFromClass(isR8 ? LibraryClass.class : ProgramJoiner.class),
                 "foo",
                 Collections.emptyList(),
-                null))
-        .isNotOutlinedFrom(Main.class.getDeclaredMethod("main", String[].class));
+                null));
+    if (isR8 && parameters.isCfRuntime()) {
+      verifyHelper.isOutlinedFromUntil(
+          Main.class.getDeclaredMethod("main", String[].class), classMethodApiLevel);
+    } else {
+      verifyHelper.isOutlinedFromUntilAlsoForCf(
+          Main.class.getDeclaredMethod("main", String[].class), classMethodApiLevel);
+    }
   }
 
   // Only present from api level 23.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java
index a0cc571..f6a0a49 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.apimodel.ApiModelingTestHelper.ApiModelingMethodVerificationHelper;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -93,7 +94,7 @@
         .apply(this::setupRunEnvironment)
         .run(parameters.getRuntime(), Main.class)
         .apply(result -> checkOutput(result, false))
-        .inspect(this::inspect);
+        .inspect(inspector -> inspect(inspector, false));
   }
 
   @Test
@@ -105,7 +106,7 @@
         .apply(this::setupRunEnvironment)
         .run(parameters.getRuntime(), Main.class)
         .apply(result -> checkOutput(result, false))
-        .inspect(this::inspect);
+        .inspect(inspector -> inspect(inspector, false));
   }
 
   @Test
@@ -115,7 +116,7 @@
         .addKeepMainRule(Main.class)
         .addKeepClassAndMembersRules(ProgramJoiner.class)
         .compile()
-        .inspect(this::inspect)
+        .inspect(inspector -> inspect(inspector, true))
         .apply(this::setupRunEnvironment)
         .run(parameters.getRuntime(), Main.class)
         .apply(result -> checkOutput(result, true));
@@ -137,17 +138,25 @@
         result -> result.assertStderrMatches(not(containsString("This dex file is invalid"))));
   }
 
-  private void inspect(CodeInspector inspector) throws Exception {
-    // TODO(b/254510678): We should outline the call to ProgramClass.foo.
-    verifyThat(
+  private void inspect(CodeInspector inspector, boolean isR8) throws Exception {
+    ApiModelingMethodVerificationHelper verifyHelper =
+        verifyThat(
             inspector,
             parameters,
             Reference.method(
-                Reference.classFromClass(ProgramJoiner.class),
+                // TODO(b/254510678): Due to member rebinding, we rebind ProgramJoiner.foo() to
+                //  LibraryClass.foo().
+                Reference.classFromClass(isR8 ? LibraryClass.class : ProgramJoiner.class),
                 "foo",
                 Collections.emptyList(),
-                null))
-        .isNotOutlinedFrom(Main.class.getDeclaredMethod("main", String[].class));
+                null));
+    if (isR8 && parameters.isCfRuntime()) {
+      verifyHelper.isOutlinedFromUntil(
+          Main.class.getDeclaredMethod("main", String[].class), mockApiLevel);
+    } else {
+      verifyHelper.isOutlinedFromUntilAlsoForCf(
+          Main.class.getDeclaredMethod("main", String[].class), mockApiLevel);
+    }
   }
 
   // Only present from api level 23.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineConstClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineConstClassTest.java
new file mode 100644
index 0000000..64a3180
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineConstClassTest.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.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.SingleTestRunResult;
+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.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+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 ApiModelOutlineConstClassTest extends TestBase {
+
+  private static final AndroidApiLevel classApiLevel = AndroidApiLevel.M;
+
+  private static final String[] EXPECTED = new String[] {"class " + typeName(LibraryClass.class)};
+
+  @Parameter public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) {
+    testBuilder
+        .addLibraryClasses(LibraryClass.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .addProgramClasses(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .addAndroidBuildVersion(getApiLevelForRuntime())
+        .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+        .apply(ApiModelingTestHelper::enableStubbingOfClasses);
+  }
+
+  public AndroidApiLevel getApiLevelForRuntime() {
+    return parameters.isCfRuntime()
+        ? AndroidApiLevel.B
+        : parameters.getRuntime().asDex().maxSupportedApiLevel();
+  }
+
+  public boolean addToBootClasspath() {
+    return getApiLevelForRuntime().isGreaterThanOrEqualTo(classApiLevel);
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    assumeTrue(parameters.isCfRuntime() && parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+    testForJvm()
+        .addProgramClasses(Main.class)
+        .addAndroidBuildVersion(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Debug() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .inspect(this::inspect)
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .inspect(this::inspect)
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .apply(this::setupTestBuilder)
+        .addKeepMainRule(Main.class)
+        .compile()
+        .inspect(this::inspect)
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  private void inspect(CodeInspector inspector) throws Exception {
+    verifyThat(inspector, parameters, LibraryClass.class)
+        .hasConstClassOutlinedFromUntil(
+            Main.class.getMethod("main", String[].class), classApiLevel);
+  }
+
+  private void checkOutput(SingleTestRunResult<?> runResult) {
+    if (addToBootClasspath()) {
+      runResult.assertSuccessWithOutputLines(EXPECTED);
+    } else {
+      runResult.assertSuccessWithOutputLines("Not checking instance of");
+    }
+  }
+
+  // Only present from api 23.
+  public static class LibraryClass {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      if (AndroidBuildVersion.VERSION >= 23) {
+        System.out.println(LibraryClass.class);
+      } else {
+        System.out.println("Not checking instance of");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineSubTypeStaticReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineSubTypeStaticReferenceTest.java
index 3e56179..19971c3 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineSubTypeStaticReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineSubTypeStaticReferenceTest.java
@@ -7,9 +7,6 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
-import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -23,8 +20,8 @@
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.lang.reflect.Method;
+import java.util.Collections;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -133,13 +130,14 @@
     Method otherMethod = Sub.class.getMethod("otherMethod");
     Method libraryMethod = LibraryClass.class.getMethod("foo");
     // TODO(b/254510678): R8 should not member-rebind to a potential non-existing method.
-    if (isR8) {
-      MethodSubject method = inspector.method(otherMethod);
-      assertThat(method, isPresent());
-      assertThat(method, invokesMethod(Reference.methodFromMethod(libraryMethod)));
-    }
-    // TODO(b/254510678): We should outline this up until library api level.
-    verifyThat(inspector, parameters, libraryMethod).isNotOutlinedFrom(otherMethod);
+    verifyThat(
+            inspector,
+            parameters,
+            isR8
+                ? Reference.methodFromMethod(libraryMethod)
+                : Reference.method(
+                    Reference.classFromClass(Sub.class), "foo", Collections.emptyList(), null))
+        .isOutlinedFromUntil(Sub.class.getDeclaredMethod("otherMethod"), libraryApiLevel);
   }
 
   private void checkResultOnBootClassPath(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
index e437943..b487caf 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -5,8 +5,6 @@
 package com.android.tools.r8.apimodel;
 
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.accessesField;
-import static com.android.tools.r8.utils.codeinspector.CodeMatchers.containsCheckCast;
-import static com.android.tools.r8.utils.codeinspector.CodeMatchers.containsInstanceOf;
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithHolderAndName;
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
@@ -39,7 +37,9 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.function.BiConsumer;
+import java.util.function.Function;
 import java.util.stream.Collectors;
+import org.hamcrest.Matcher;
 
 public abstract class ApiModelingTestHelper {
 
@@ -272,35 +272,11 @@
     }
 
     public void hasCheckCastOutlinedFrom(Method method) {
-      // Check that we call is in a synthetic class with a check cast
-      ClassReference classOfInterestReference = Reference.classFromClass(classOfInterest);
-      List<FoundMethodSubject> outlinedMethod =
-          inspector.allClasses().stream()
-              .filter(
-                  clazz ->
-                      clazz
-                          .getOriginalName()
-                          .startsWith(
-                              SyntheticItemsTestUtils.syntheticApiOutlineClassPrefix(
-                                  method.getDeclaringClass())))
-              .flatMap(clazz -> clazz.allMethods().stream())
-              .filter(
-                  methodSubject ->
-                      methodSubject.isSynthetic()
-                          && containsCheckCast(classOfInterestReference).matches(methodSubject))
-              .collect(Collectors.toList());
-      assertFalse(outlinedMethod.isEmpty());
-      // Assert that method invokes the outline
-      MethodSubject caller = inspector.method(method);
-      assertThat(caller, isPresent());
-      assertThat(caller, invokesMethod(outlinedMethod.get(0)));
+      hasOutlinedInstructionWithClassReference(method, CodeMatchers::containsCheckCast);
     }
 
     public void hasNotCheckCastOutlinedFrom(Method method) {
-      ClassReference classOfInterestReference = Reference.classFromClass(classOfInterest);
-      MethodSubject caller = inspector.method(method);
-      assertThat(caller, isPresent());
-      assertThat(caller, containsCheckCast(classOfInterestReference));
+      hasNotOulinedInstructionWithClassReference(method, CodeMatchers::containsCheckCast);
     }
 
     void hasInstanceOfOutlinedFromUntil(Method method, AndroidApiLevel apiLevel) {
@@ -312,8 +288,34 @@
     }
 
     public void hasInstanceOfOutlinedFrom(Method method) {
+      hasOutlinedInstructionWithClassReference(method, CodeMatchers::containsInstanceOf);
+    }
+
+    public void hasNotInstanceOfOutlinedFrom(Method method) {
+      hasNotOulinedInstructionWithClassReference(method, CodeMatchers::containsInstanceOf);
+    }
+
+    void hasConstClassOutlinedFromUntil(Method method, AndroidApiLevel apiLevel) {
+      if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(apiLevel)) {
+        hasConstClassOutlinedFrom(method);
+      } else {
+        hasNotConstClassOutlinedFrom(method);
+      }
+    }
+
+    public void hasConstClassOutlinedFrom(Method method) {
+      hasOutlinedInstructionWithClassReference(method, CodeMatchers::containsConstClass);
+    }
+
+    public void hasNotConstClassOutlinedFrom(Method method) {
+      hasNotOulinedInstructionWithClassReference(method, CodeMatchers::containsConstClass);
+    }
+
+    private void hasOutlinedInstructionWithClassReference(
+        Method method, Function<ClassReference, Matcher<? super MethodSubject>> matcher) {
       // Check that we call is in a synthetic class with an instance of.
       ClassReference classOfInterestReference = Reference.classFromClass(classOfInterest);
+      Matcher<? super MethodSubject> instructionMatcher = matcher.apply(classOfInterestReference);
       List<FoundMethodSubject> outlinedMethod =
           inspector.allClasses().stream()
               .filter(
@@ -326,8 +328,7 @@
               .flatMap(clazz -> clazz.allMethods().stream())
               .filter(
                   methodSubject ->
-                      methodSubject.isSynthetic()
-                          && containsInstanceOf(classOfInterestReference).matches(methodSubject))
+                      methodSubject.isSynthetic() && instructionMatcher.matches(methodSubject))
               .collect(Collectors.toList());
       assertFalse(outlinedMethod.isEmpty());
       // Assert that method invokes the outline
@@ -336,11 +337,12 @@
       assertThat(caller, invokesMethod(outlinedMethod.get(0)));
     }
 
-    public void hasNotInstanceOfOutlinedFrom(Method method) {
+    private void hasNotOulinedInstructionWithClassReference(
+        Method method, Function<ClassReference, Matcher<? super MethodSubject>> matcher) {
       ClassReference classOfInterestReference = Reference.classFromClass(classOfInterest);
       MethodSubject caller = inspector.method(method);
       assertThat(caller, isPresent());
-      assertThat(caller, containsInstanceOf(classOfInterestReference));
+      assertThat(caller, matcher.apply(classOfInterestReference));
     }
   }
 
@@ -447,6 +449,14 @@
       }
     }
 
+    void isOutlinedFromUntilAlsoForCf(Executable method, AndroidApiLevel apiLevel) {
+      if (parameters.getApiLevel().isLessThan(apiLevel)) {
+        isOutlinedFrom(method);
+      } else {
+        isNotOutlinedFrom(method);
+      }
+    }
+
     void isOutlinedFrom(Executable method) {
       // Check that the call is in a synthetic class.
       List<FoundMethodSubject> outlinedMethod =
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfIntTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfIntTest.java
new file mode 100644
index 0000000..af62516
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfIntTest.java
@@ -0,0 +1,34 @@
+// 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.varhandle;
+
+import com.android.tools.r8.examples.jdk9.VarHandle;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class VarHandleDesugaringArrayOfIntTest extends VarHandleDesugaringTestBase {
+
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines("1", "0", "1", "0", "1", "2", "1", "3");
+  private static final String MAIN_CLASS = VarHandle.ArrayOfInt.typeName();
+  private static final String JAR_ENTRY = "varhandle/ArrayOfInt.class";
+
+  @Override
+  protected String getMainClass() {
+    return MAIN_CLASS;
+  }
+
+  @Override
+  protected String getJarEntry() {
+    return JAR_ENTRY;
+  }
+
+  @Override
+  protected String getExpectedOutput() {
+    return EXPECTED_OUTPUT;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfLongTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfLongTest.java
new file mode 100644
index 0000000..46e130f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfLongTest.java
@@ -0,0 +1,34 @@
+// 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.varhandle;
+
+import com.android.tools.r8.examples.jdk9.VarHandle;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class VarHandleDesugaringArrayOfLongTest extends VarHandleDesugaringTestBase {
+
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines("1", "0", "1", "0", "1", "2", "1", "3");
+  private static final String MAIN_CLASS = VarHandle.ArrayOfLong.typeName();
+  private static final String JAR_ENTRY = "varhandle/ArrayOfLong.class";
+
+  @Override
+  protected String getMainClass() {
+    return MAIN_CLASS;
+  }
+
+  @Override
+  protected String getJarEntry() {
+    return JAR_ENTRY;
+  }
+
+  @Override
+  protected String getExpectedOutput() {
+    return EXPECTED_OUTPUT;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceIntFieldTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceIntFieldTest.java
new file mode 100644
index 0000000..4d15a0a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceIntFieldTest.java
@@ -0,0 +1,87 @@
+// 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.varhandle;
+
+import com.android.tools.r8.examples.jdk9.VarHandle;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class VarHandleDesugaringInstanceIntFieldTest extends VarHandleDesugaringTestBase {
+
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines(
+          "testGet",
+          "0",
+          "1",
+          "2",
+          "3",
+          "4",
+          "48",
+          "49",
+          "5",
+          "6",
+          "6",
+          "6",
+          "6",
+          "6",
+          "6",
+          "6",
+          "testCompareAndSet",
+          "0",
+          "1",
+          "2",
+          "3",
+          "4",
+          "5",
+          "6",
+          "7",
+          "8",
+          "9",
+          "10",
+          "48",
+          "49",
+          "50",
+          "51",
+          "52",
+          "53",
+          "53",
+          "53",
+          "53",
+          "53",
+          "53",
+          "53",
+          "53",
+          "53",
+          "53",
+          "53",
+          "53",
+          "53",
+          "53",
+          "53");
+  private static final String MAIN_CLASS = VarHandle.InstanceIntField.typeName();
+  private static final String JAR_ENTRY = "varhandle/InstanceIntField.class";
+
+  @Override
+  protected String getMainClass() {
+    return MAIN_CLASS;
+  }
+
+  @Override
+  protected String getKeepRules() {
+    return "-keep class " + getMainClass() + "{ <fields>; }";
+  }
+
+  @Override
+  protected String getJarEntry() {
+    return JAR_ENTRY;
+  }
+
+  @Override
+  protected String getExpectedOutput() {
+    return EXPECTED_OUTPUT;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceLongFieldTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceLongFieldTest.java
new file mode 100644
index 0000000..0584f1c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceLongFieldTest.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.varhandle;
+
+import com.android.tools.r8.examples.jdk9.VarHandle;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class VarHandleDesugaringInstanceLongFieldTest extends VarHandleDesugaringTestBase {
+
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines("testGet", "0", "1", "testCompareAndSet", "0", "1");
+  private static final String MAIN_CLASS = VarHandle.InstanceLongField.typeName();
+  private static final String JAR_ENTRY = "varhandle/InstanceLongField.class";
+
+  @Override
+  protected String getMainClass() {
+    return MAIN_CLASS;
+  }
+
+  @Override
+  protected String getKeepRules() {
+    return "-keep class " + getMainClass() + "{ <fields>; }";
+  }
+
+  @Override
+  protected String getJarEntry() {
+    return JAR_ENTRY;
+  }
+
+  @Override
+  protected String getExpectedOutput() {
+    return EXPECTED_OUTPUT;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceObjectFieldTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceObjectFieldTest.java
new file mode 100644
index 0000000..b9b2b8e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceObjectFieldTest.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.varhandle;
+
+import com.android.tools.r8.examples.jdk9.VarHandle;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class VarHandleDesugaringInstanceObjectFieldTest extends VarHandleDesugaringTestBase {
+
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines("testGet", "null", "1", "testCompareAndSet", "null", "1");
+  private static final String MAIN_CLASS = VarHandle.InstanceObjectField.typeName();
+  private static final String JAR_ENTRY = "varhandle/InstanceObjectField.class";
+
+  @Override
+  protected String getMainClass() {
+    return MAIN_CLASS;
+  }
+
+  @Override
+  protected String getKeepRules() {
+    return "-keep class " + getMainClass() + "{ <fields>; }";
+  }
+
+  @Override
+  protected String getJarEntry() {
+    return JAR_ENTRY;
+  }
+
+  @Override
+  protected String getExpectedOutput() {
+    return EXPECTED_OUTPUT;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceStringFieldTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceStringFieldTest.java
new file mode 100644
index 0000000..dfd32cf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceStringFieldTest.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.varhandle;
+
+import com.android.tools.r8.examples.jdk9.VarHandle;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class VarHandleDesugaringInstanceStringFieldTest extends VarHandleDesugaringTestBase {
+
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines("testGet", "null", "1", "testCompareAndSet", "null", "1");
+  private static final String MAIN_CLASS = VarHandle.InstanceStringField.typeName();
+  private static final String JAR_ENTRY = "varhandle/InstanceStringField.class";
+
+  @Override
+  protected String getMainClass() {
+    return MAIN_CLASS;
+  }
+
+  @Override
+  protected String getKeepRules() {
+    return "-keep class " + getMainClass() + "{ <fields>; }";
+  }
+
+  @Override
+  protected String getJarEntry() {
+    return JAR_ENTRY;
+  }
+
+  @Override
+  protected String getExpectedOutput() {
+    return EXPECTED_OUTPUT;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java
new file mode 100644
index 0000000..8a5bb2c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java
@@ -0,0 +1,108 @@
+// 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.varhandle;
+
+import static org.junit.Assume.assumeTrue;
+
+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.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.examples.jdk9.VarHandle;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ZipUtils;
+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 abstract class VarHandleDesugaringTestBase extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK9)
+        .withDexRuntimes()
+        .withAllApiLevels()
+        .build();
+  }
+
+  protected abstract String getMainClass();
+
+  protected String getKeepRules() {
+    return "";
+  }
+
+  protected abstract String getJarEntry();
+
+  protected abstract String getExpectedOutput();
+
+  @Test
+  public void testReference() throws Throwable {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramFiles(VarHandle.jar())
+        .run(parameters.getRuntime(), getMainClass())
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  @Test
+  public void testD8() throws Throwable {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        // Use android.jar from Android T to get the VarHandle type. This is not strictly needed
+        // to D8 as it does not fail on missing types.
+        // TODO(b/247076137): With desugaring removing VarHandle the type should not be needed in
+        //  the library and any android.jar should work.
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addProgramClassFileData(ZipUtils.readSingleEntry(VarHandle.jar(), getJarEntry()))
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), getMainClass())
+        // TODO(b/247076137): Test should pass on all platforms with desugaring implemented.
+        .applyIf(
+            // VarHandle is available from Android 9, even though it was not a public API until 13.
+            parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V7_0_0),
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.P)
+                || parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V8_1_0),
+            r -> r.assertFailure(),
+            r -> r.assertSuccessWithOutput(getExpectedOutput()));
+  }
+
+  @Test
+  public void testR8() throws Throwable {
+    testForR8(parameters.getBackend())
+        // Use android.jar from Android T to get the VarHandle type.
+        // TODO(b/247076137): With desugaring removing VarHandle the type should not be needed in
+        //  the library and any android.jar should work.
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addProgramClassFileData(ZipUtils.readSingleEntry(VarHandle.jar(), getJarEntry()))
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(getMainClass())
+        .addKeepRules(getKeepRules())
+        .applyIf(
+            parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+            R8TestBuilder::allowDiagnosticWarningMessages)
+        .run(parameters.getRuntime(), getMainClass())
+        .applyIf(
+            // VarHandle is available from Android 9, even though it was not a public API until 13.
+            parameters.isDexRuntime()
+                && parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V7_0_0),
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+            parameters.isDexRuntime()
+                && (parameters.getApiLevel().isLessThan(AndroidApiLevel.P)
+                    || parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V8_1_0)),
+            r -> r.assertFailure(),
+            r -> r.assertSuccessWithOutput(getExpectedOutput()));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/CfClassGenerator.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/CfClassGenerator.java
index c706f50..0630571 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/CfClassGenerator.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/CfClassGenerator.java
@@ -363,7 +363,7 @@
       codePrinter.visitMethod(generatedMethodName, method.getCode().asCfCode());
       index++;
     }
-    codePrinter.getImports().forEach(imports::addImport);
+    codePrinter.getImportsSorted().forEach(imports::addImport);
     return createCfCodeMethodNames;
   }
 
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
index 28ab9a7..0cd6b23 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
@@ -8,30 +8,65 @@
 import com.android.tools.r8.cf.CfCodePrinter;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.ClassKind;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.JarApplicationReader;
 import com.android.tools.r8.graph.JarClassFileReader;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.BooleanBox;
 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.Reporter;
+import com.beust.jcommander.internal.Sets;
+import com.google.common.collect.ImmutableList;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
 import java.util.TreeSet;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 public abstract class MethodGenerationBase extends CodeGenerationBase {
 
   protected abstract List<Class<?>> getMethodTemplateClasses();
 
+  protected List<Class<?>> getClassesToGenerate() {
+    return ImmutableList.of();
+  }
+
+  protected DexEncodedField getField(DexEncodedField field) {
+    return field;
+  }
+
+  protected boolean includeMethod(DexEncodedMethod method) {
+    return !method.isInstanceInitializer();
+  }
+
   protected CfCode getCode(String holderName, String methodName, CfCode code) {
     return code;
   }
 
+  protected DexEncodedMethod mapMethod(DexEncodedMethod method) {
+    return method;
+  }
+
   // Running this method will regenerate / overwrite the content of the generated class.
   protected void generateMethodsAndWriteThemToFile() throws IOException {
     FileUtils.writeToFile(getGeneratedFile(), null, generateMethods().getBytes());
@@ -43,31 +78,47 @@
 
     File tempFile = File.createTempFile("output-", ".java");
 
-    readMethodTemplatesInto(codePrinter);
-    generateRawOutput(codePrinter, tempFile.toPath());
+    Map<DexEncodedMethod, String> generatedMethods = new HashMap<>();
+    List<DexEncodedField> fields = new ArrayList<>();
+    readMethodTemplatesInto(codePrinter, generatedMethods::put, fields::add);
+    generateRawOutput(generatedMethods, fields, codePrinter, tempFile.toPath());
     String result = formatRawOutput(tempFile.toPath());
 
     tempFile.deleteOnExit();
     return result;
   }
 
-  private void readMethodTemplatesInto(CfCodePrinter codePrinter) throws IOException {
+  private void readMethodTemplatesInto(
+      CfCodePrinter codePrinter,
+      BiConsumer<DexEncodedMethod, String> generatedMethods,
+      Consumer<DexEncodedField> generatedFields)
+      throws IOException {
     InternalOptions options = new InternalOptions(factory, new Reporter());
     options.testing.readInputStackMaps = true;
     JarClassFileReader<DexProgramClass> reader =
         new JarClassFileReader<>(
             new JarApplicationReader(options),
             clazz -> {
+              for (DexEncodedField field : clazz.fields()) {
+                DexEncodedField fieldToAddToClass = getField(field);
+                if (fieldToAddToClass != null) {
+                  generatedFields.accept(fieldToAddToClass);
+                }
+              }
               for (DexEncodedMethod method : clazz.allMethodsSorted()) {
-                if (method.isInitializer()) {
+                if (!includeMethod(method)) {
                   continue;
                 }
                 String holderName = method.getHolderType().getName();
                 String methodName = method.getReference().name.toString();
+                if (methodName.equals("<init>")) {
+                  methodName = "constructor_" + method.getProto().getArity();
+                }
                 String generatedMethodName = holderName + "_" + methodName;
                 CfCode code = getCode(holderName, methodName, method.getCode().asCfCode());
                 if (code != null) {
                   codePrinter.visitMethod(generatedMethodName, code);
+                  generatedMethods.accept(method, generatedMethodName);
                 }
               }
             },
@@ -77,11 +128,222 @@
     }
   }
 
-  private void generateRawOutput(CfCodePrinter codePrinter, Path tempFile) throws IOException {
+  private String createType(DexType type) {
+    if (type.isVoidType()) {
+      return "factory.voidType";
+    }
+    if (type.isBooleanType()) {
+      return "factory.booleanType";
+    }
+    if (type.isIntType()) {
+      return "factory.intType";
+    }
+    if (type.isLongType()) {
+      return "factory.longType";
+    }
+    if (type.descriptor.toString().equals("Ljava/lang/Object;")) {
+      return "factory.objectType";
+    }
+    return "factory.createType(factory.createString(\"" + type.getDescriptor() + "\"))";
+  }
+
+  private String createField(DexEncodedField field) {
+    return "factory.createField(\n"
+        + "builder.getType(),\n"
+        + createType(field.getType())
+        + ",\n"
+        + "factory.createString(\""
+        + field.getName()
+        + "\"))\n";
+  }
+
+  private String buildSyntheticMethod(
+      DexEncodedMethod method, String codeGenerator, Set<String> requiredImports) {
+    String name =
+        method.isInstanceInitializer()
+            ? "constructor_" + method.getProto().getArity()
+            : method.getName().toString();
+    requiredImports.add("com.android.tools.r8.graph.DexEncodedMethod");
+    requiredImports.add("com.android.tools.r8.graph.MethodAccessFlags");
+    requiredImports.add("com.android.tools.r8.dex.Constants");
+    return "DexEncodedMethod.syntheticBuilder()\n"
+        + "        .setMethod("
+        + name
+        + ")\n"
+        + "        .setAccessFlags(\n"
+        + "            MethodAccessFlags.fromSharedAccessFlags(\n"
+        + "                Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, "
+        + (method.isInstanceInitializer())
+        + "))\n"
+        + "        .setCode("
+        + codeGenerator
+        + "(factory, "
+        + name
+        + "))\n"
+        + "        .disableAndroidApiLevelCheck()\n"
+        + "        .build()";
+  }
+
+  private String generateCreateProto(DexProto proto) {
+    StringBuilder builder = new StringBuilder("factory.createProto(");
+    builder.append(createType(proto.returnType));
+    proto.getParameters().forEach(type -> builder.append(", ").append(createType(type)));
+    builder.append(")");
+    return builder.toString();
+  }
+
+  private String generateCreateMethod(DexEncodedMethod method) {
+    return "factory.createMethod(\n"
+        + "builder.getType(), "
+        + generateCreateProto(method.getProto())
+        + ", factory.createString(\""
+        + method.getName()
+        + "\"));";
+  }
+
+  private void generateDexMethodLocals(
+      Map<DexEncodedMethod, String> generatedMethods, PrintStream printer, Class<?> clazz) {
+    // Generate local variable for a DexMethod:
+    //  DexMethod name = factory.createMethod(
+    //      builder.getType(), factory.createProto(...), factory.createString(...));
+
+    generatedMethods.forEach(
+        (method, codeGenerator) -> {
+          if (method.getHolderType().toSourceString().equals(clazz.getCanonicalName())) {
+            String name =
+                method.isInstanceInitializer()
+                    ? "constructor_" + method.getProto().getArity()
+                    : method.getName().toString();
+            printer.println("DexMethod " + name + " = " + generateCreateMethod(mapMethod(method)));
+          }
+        });
+  }
+
+  private void generateSyntheticMethodsList(
+      Map<DexEncodedMethod, String> generatedMethods,
+      Set<String> requiredImports,
+      PrintStream printer,
+      Class<?> clazz,
+      Predicate<DexEncodedMethod> filter) {
+    printer.println("ImmutableList.of(");
+    BooleanBox first = new BooleanBox(true);
+    generatedMethods.forEach(
+        (method, codeGenerator) -> {
+          if (method.getHolderType().toSourceString().equals(clazz.getCanonicalName())
+              && filter.test(method)) {
+            if (!first.get()) {
+              printer.println(",\n");
+            }
+            first.set(false);
+            printer.println(buildSyntheticMethod(method, codeGenerator, requiredImports));
+          }
+        });
+    printer.println(")");
+  }
+
+  private String buildSyntheticField(DexEncodedField field, Set<String> requiredImports) {
+    requiredImports.add("com.android.tools.r8.graph.DexEncodedField");
+    requiredImports.add("com.android.tools.r8.graph.FieldAccessFlags");
+    return "            DexEncodedField.syntheticBuilder()\n"
+        + "                .setField("
+        + createField(field)
+        + ")\n"
+        + "                .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())\n"
+        + "                .disableAndroidApiLevelCheck()\n"
+        + "                .build()\n";
+  }
+
+  private void generateFieldList(
+      List<DexEncodedField> fields,
+      Set<String> requiredImports,
+      PrintStream printer,
+      Class<?> clazz) {
+    printer.println("ImmutableList.of(");
+    BooleanBox first = new BooleanBox(true);
+    for (DexEncodedField field : fields) {
+      if (field.getHolderType().toSourceString().equals(clazz.getCanonicalName())) {
+        if (!first.get()) {
+          printer.println(",\n");
+        }
+        first.set(false);
+        printer.println(buildSyntheticField(field, requiredImports));
+      }
+    }
+    printer.println(")");
+  }
+
+  private <K, V> Map<K, V> filterMapOnKey(Map<K, V> map, Predicate<K> predicate) {
+    return map.entrySet().stream()
+        .filter(entry -> predicate.test(entry.getKey()))
+        .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
+  }
+
+  private void generateGenerateClasses(
+      Map<DexEncodedMethod, String> allMethods,
+      List<DexEncodedField> allFields,
+      Set<String> requiredImports,
+      PrintStream printer) {
+    for (Class<?> clazz : getClassesToGenerate()) {
+      String simpleName =
+          clazz.getSimpleName(); // "DesugarVarHandle"; //name.substring(name.lastIndexOf('.'));
+      List<DexEncodedField> classFields =
+          ListUtils.filter(
+              allFields,
+              field -> field.getHolderType().toSourceString().equals(clazz.getCanonicalName()));
+      Map<DexEncodedMethod, String> classMethods =
+          filterMapOnKey(
+              allMethods,
+              method -> method.getHolderType().toSourceString().equals(clazz.getCanonicalName()));
+      requiredImports.add("com.android.tools.r8.synthesis.SyntheticProgramClassBuilder");
+      printer.println(
+          "public static void generate"
+              + simpleName
+              + "Class(SyntheticProgramClassBuilder builder, DexItemFactory factory) {");
+      printer.println("builder.setInstanceFields(");
+      generateFieldList(classFields, requiredImports, printer, clazz);
+      printer.println(");");
+      generateDexMethodLocals(classMethods, printer, clazz);
+      if (clazz.getSuperclass() != Object.class) {
+        printer.println(
+            "    builder.setSuperType("
+                + createType(factory.createType(Reference.classFromClass(clazz.getSuperclass())))
+                + ");");
+      }
+      printer.println("builder.setDirectMethods(");
+      generateSyntheticMethodsList(
+          classMethods, requiredImports, printer, clazz, DexEncodedMethod::isInstanceInitializer);
+      printer.println(");");
+      printer.println("builder.setVirtualMethods(");
+      generateSyntheticMethodsList(
+          allMethods, requiredImports, printer, clazz, method -> !method.isInstanceInitializer());
+      printer.println(");");
+      printer.println("}");
+    }
+  }
+
+  private void generateRawOutput(
+      Map<DexEncodedMethod, String> generatedMethods,
+      List<DexEncodedField> fields,
+      CfCodePrinter codePrinter,
+      Path tempFile)
+      throws IOException {
     try (PrintStream printer = new PrintStream(Files.newOutputStream(tempFile))) {
       printer.print(getHeaderString());
-      printer.println("import com.android.tools.r8.graph.DexItemFactory;");
-      codePrinter.getImports().forEach(i -> printer.println("import " + i + ";"));
+
+      Set<String> imports = Sets.newHashSet();
+      imports.add("com.android.tools.r8.graph.DexItemFactory");
+      // TODO(b/260985726): Consider only calling generateGenerateClasses once.
+      // Generate classes into an unused PrintStream to collect imports.
+      generateGenerateClasses(
+          generatedMethods,
+          fields,
+          imports,
+          new PrintStream(new ByteArrayOutputStream(), true, StandardCharsets.UTF_8.name()));
+      imports.addAll(codePrinter.getImports());
+      ArrayList<String> sortedImports = new ArrayList<>(imports);
+      sortedImports.sort(String::compareTo);
+      sortedImports.forEach(i -> printer.println("import " + i + ";"));
+
       printer.println("public final class " + getGeneratedClassName() + " {\n");
       printer.println(
           "public static void registerSynthesizedCodeReferences(DexItemFactory factory) {");
@@ -89,8 +351,12 @@
         printer.println("factory.createSynthesizedType(\"" + type + "\");");
       }
       printer.println("}");
+      // TODO(b/260985726): Consider only calling generateGenerateClasses once.
+      // Generate classes ignoring the collected imports (they are already added to the file).
+      generateGenerateClasses(generatedMethods, fields, Sets.newHashSet(), printer);
       codePrinter.getMethods().forEach(printer::println);
       printer.println("}");
     }
   }
+
 }
diff --git a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
index 812eb63..b00d78b 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
@@ -10,7 +10,9 @@
 import com.android.tools.r8.dex.code.DexConst4;
 import com.android.tools.r8.dex.code.DexConstClass;
 import com.android.tools.r8.dex.code.DexConstString;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
 import com.android.tools.r8.dex.code.DexInvokeVirtual;
+import com.android.tools.r8.dex.code.DexMoveResultObject;
 import com.android.tools.r8.dex.code.DexNewArray;
 import com.android.tools.r8.dex.code.DexReturnVoid;
 import com.android.tools.r8.graph.DexCode;
@@ -93,16 +95,29 @@
     assertTrue(method.isPresent());
 
     DexCode code = method.getMethod().getCode().asDexCode();
-    assertTrue(code.instructions[0] instanceof DexConst4);
-    assertTrue(code.instructions[1] instanceof DexNewArray);
-    assertTrue(code.instructions[2] instanceof DexConst4);
-    assertTrue(code.instructions[3] instanceof DexConstClass);
-    assertTrue(code.instructions[4] instanceof DexAputObject);
-    assertTrue(code.instructions[5] instanceof DexConstClass);
-    assertTrue(code.instructions[6] instanceof DexConstString);
-    assertNotEquals("foo", code.instructions[6].asConstString().getString().toString());
-    assertTrue(code.instructions[7] instanceof DexInvokeVirtual);
-    assertTrue(code.instructions[8] instanceof DexReturnVoid);
+
+    // Accept either array construction style (differs based on minSdkVersion).
+    if (code.instructions[1] instanceof DexFilledNewArray) {
+      assertTrue(code.instructions[0] instanceof DexConstClass);
+      assertTrue(code.instructions[1] instanceof DexFilledNewArray);
+      assertTrue(code.instructions[2] instanceof DexMoveResultObject);
+      assertTrue(code.instructions[3] instanceof DexConstClass);
+      assertTrue(code.instructions[4] instanceof DexConstString);
+      assertNotEquals("foo", code.instructions[4].asConstString().getString().toString());
+      assertTrue(code.instructions[5] instanceof DexInvokeVirtual);
+      assertTrue(code.instructions[6] instanceof DexReturnVoid);
+    } else {
+      assertTrue(code.instructions[0] instanceof DexConst4);
+      assertTrue(code.instructions[1] instanceof DexNewArray);
+      assertTrue(code.instructions[2] instanceof DexConst4);
+      assertTrue(code.instructions[3] instanceof DexConstClass);
+      assertTrue(code.instructions[4] instanceof DexAputObject);
+      assertTrue(code.instructions[5] instanceof DexConstClass);
+      assertTrue(code.instructions[6] instanceof DexConstString);
+      assertNotEquals("foo", code.instructions[6].asConstString().getString().toString());
+      assertTrue(code.instructions[7] instanceof DexInvokeVirtual);
+      assertTrue(code.instructions[8] instanceof DexReturnVoid);
+    }
   }
 
 }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/AsmZeroLineEntryRegressionTest.java b/src/test/java/com/android/tools/r8/debuginfo/AsmZeroLineEntryRegressionTest.java
new file mode 100644
index 0000000..018c618
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/AsmZeroLineEntryRegressionTest.java
@@ -0,0 +1,126 @@
+// 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.debuginfo;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+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.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.IntBox;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Label;
+
+@RunWith(Parameterized.class)
+public class AsmZeroLineEntryRegressionTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultCfRuntime().build();
+  }
+
+  public AsmZeroLineEntryRegressionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  /**
+   * Reference test showing JVM printing of zero-line valued entries in the line number table.
+   *
+   * <p>JVM spec defines line values to be u2 (unsigned 2-byte) values without other restrictions.
+   */
+  @Test
+  public void testReference() throws Exception {
+    testForJvm()
+        .addProgramClassFileData(getClassWithZeroLineEntry())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(RuntimeException.class)
+        .inspectOriginalStackTrace(st -> checkLineNumber(st, 0));
+  }
+
+  /**
+   * Regression test for ASM stripping out zero-line entries.
+   *
+   * <p>See b/260389461
+   */
+  @Test
+  public void testAsmIdentity() throws Exception {
+    testForJvm()
+        .addProgramClassFileData(
+            getClassAfterAsmIdentity(
+                getClassWithZeroLineEntry(), Reference.classFromClass(TestClass.class)))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(RuntimeException.class)
+        // If this becomes zero ASM has been updated to not assign line zero a special meaning.
+        .inspectOriginalStackTrace(st -> checkLineNumber(st, 1));
+  }
+
+  private void checkLineNumber(StackTrace st, int lineNumber) {
+    assertThat(
+        st,
+        isSame(
+            StackTrace.builder()
+                .add(
+                    StackTraceLine.builder()
+                        .setFileName(getClass().getSimpleName() + ".java")
+                        .setClassName(TestClass.class.getTypeName())
+                        .setMethodName("main")
+                        .setLineNumber(lineNumber)
+                        .build())
+                .build()));
+  }
+
+  private byte[] getClassAfterAsmIdentity(byte[] bytes, ClassReference clazz) {
+    return transformer(bytes, clazz)
+        // Add the identity line transform. If no method transformation is added then ASM will not
+        // interpret the line table and will retain the zero.
+        .addMethodTransformer(
+            new MethodTransformer() {
+              @Override
+              public void visitLineNumber(int line, Label start) {
+                super.visitLineNumber(line, start);
+              }
+            })
+        .transform();
+  }
+
+  private byte[] getClassWithZeroLineEntry() throws IOException {
+    IntBox nextLine = new IntBox(1);
+    return transformer(TestClass.class)
+        .addMethodTransformer(
+            new MethodTransformer() {
+              @Override
+              public void visitLineNumber(int line, Label start) {
+                if (getContext().getReference().getMethodName().equals("main")) {
+                  int newLine = nextLine.get();
+                  if (newLine > 0) {
+                    nextLine.decrement(1);
+                  }
+                  super.visitLineNumber(newLine, start);
+                } else {
+                  super.visitLineNumber(line, start);
+                }
+              }
+            })
+        .transform();
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println("Hello, world!");
+      throw new RuntimeException("BOO!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java b/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java
index 053f4b3..455bfe4 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java
@@ -3,24 +3,18 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.debuginfo;
 
-import static org.junit.Assert.assertNull;
 
 import com.android.tools.r8.AssumeMayHaveSideEffects;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.DexParser;
 import com.android.tools.r8.dex.DexSection;
-import com.android.tools.r8.graph.DexDebugInfo;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.io.IOException;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -54,7 +48,7 @@
     Class<?> clazzA = ClassA.class;
     Class<?> clazzB = ClassB.class;
 
-    R8TestCompileResult result =
+    Path classesPath =
         testForR8(Backend.DEX)
             .setMinApi(AndroidApiLevel.B)
             .addProgramClasses(clazzA, clazzB)
@@ -63,19 +57,11 @@
                 "-keep class ** { public void call(int); }")
             .enableInliningAnnotations()
             .enableSideEffectAnnotations()
-            .compile();
-    result.inspect(
-        inspector -> {
-          DexEncodedMethod method =
-              inspector.clazz(ClassA.class).uniqueMethodWithOriginalName("call").getMethod();
-          DexDebugInfo debugInfo = method.getCode().asDexCode().getDebugInfo();
-          assertNull(debugInfo);
-        });
-    Path classesPath = temp.getRoot().toPath();
-    result.app.write(classesPath, OutputMode.DexIndexed);
-    int numberOfDebugInfos =
-        getNumberOfDebugInfos(Paths.get(temp.getRoot().getCanonicalPath(), "classes.dex"));
-    Assert.assertEquals(0, numberOfDebugInfos);
+            .compile()
+            .writeToDirectory();
+    int numberOfDebugInfos = getNumberOfDebugInfos(classesPath.resolve("classes.dex"));
+    // All methods have one parameter and use a single shared pc2pc item.
+    Assert.assertEquals(1, numberOfDebugInfos);
   }
 
   // Two classes which has debug info that looks exactly the same, except for SetInlineFrame.
diff --git a/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoInlineRemoveTest.java b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoInlineRemoveTest.java
index c577259..84db0e5 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoInlineRemoveTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoInlineRemoveTest.java
@@ -51,6 +51,11 @@
             .map(StackTrace::extractFromJvm);
   }
 
+  private boolean compileApiHasPcLineSupport() {
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithPcAsLineNumberSupport());
+  }
+
   @Test
   public void testDefaultSourceFile() throws Exception {
     testForR8(parameters.getBackend())
@@ -70,7 +75,7 @@
               assertThat(mainSubject.uniqueMethodWithOriginalName("inlinee"), not(isPresent()));
               assertThat(
                   mainSubject.uniqueMethodWithOriginalName("shouldRemoveLineNumberForInline"),
-                  notIf(hasLineNumberTable(), parameters.isDexRuntime()));
+                  notIf(hasLineNumberTable(), compileApiHasPcLineSupport()));
             });
   }
 
@@ -94,7 +99,7 @@
               assertThat(mainSubject.uniqueMethodWithOriginalName("inlinee"), not(isPresent()));
               assertThat(
                   mainSubject.uniqueMethodWithOriginalName("shouldRemoveLineNumberForInline"),
-                  notIf(hasLineNumberTable(), parameters.isDexRuntime()));
+                  notIf(hasLineNumberTable(), compileApiHasPcLineSupport()));
             });
   }
 
@@ -118,7 +123,7 @@
               assertThat(mainSubject.uniqueMethodWithOriginalName("inlinee"), not(isPresent()));
               assertThat(
                   mainSubject.uniqueMethodWithOriginalName("shouldRemoveLineNumberForInline"),
-                  notIf(hasLineNumberTable(), parameters.isDexRuntime()));
+                  notIf(hasLineNumberTable(), compileApiHasPcLineSupport()));
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoMultipleCallsRemoveTest.java b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoMultipleCallsRemoveTest.java
index 542cfb6..e1075c6 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoMultipleCallsRemoveTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoMultipleCallsRemoveTest.java
@@ -5,9 +5,6 @@
 package com.android.tools.r8.debuginfo;
 
 import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
-import static com.android.tools.r8.utils.codeinspector.Matchers.hasLineNumberTable;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -17,7 +14,6 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.naming.retrace.StackTrace;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -63,20 +59,7 @@
         .enableInliningAnnotations()
         .run(parameters.getRuntime(), Main.class)
         .assertFailureWithErrorThatThrows(NullPointerException.class)
-        .inspectStackTrace(
-            (stackTrace, inspector) -> {
-              assertThat(stackTrace, isSame(expectedStackTrace));
-              assertThat(inspector.clazz(Builder.class), isPresent());
-              ClassSubject mainSubject = inspector.clazz(Main.class);
-              assertThat(mainSubject, isPresent());
-              assertThat(
-                  mainSubject.uniqueMethodWithOriginalName(
-                      "shouldRemoveLineNumberForMultipleInvokes"),
-                  notIf(hasLineNumberTable(), parameters.isDexRuntime()));
-              assertThat(
-                  mainSubject.uniqueMethodWithOriginalName("main"),
-                  notIf(hasLineNumberTable(), parameters.isDexRuntime()));
-            });
+        .inspectStackTrace(stackTrace -> assertThat(stackTrace, isSame(expectedStackTrace)));
   }
 
   @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoRemoveTest.java b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoRemoveTest.java
index 8076acf..46b9e22 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoRemoveTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoRemoveTest.java
@@ -98,7 +98,9 @@
   }
 
   private boolean canSingleLineDebugInfoBeDiscarded() {
-    return parameters.isDexRuntime() && !customSourceFile;
+    return parameters.isDexRuntime()
+        && !customSourceFile
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithPcAsLineNumberSupport());
   }
 
   public static class Main {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/ZeroLineEntryTest.java b/src/test/java/com/android/tools/r8/debuginfo/ZeroLineEntryTest.java
new file mode 100644
index 0000000..4b7259f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/ZeroLineEntryTest.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debuginfo;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+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.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ZeroLineEntryTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withApiLevel(AndroidApiLevel.B).build();
+  }
+
+  public ZeroLineEntryTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    SmaliBuilder smali = new SmaliBuilder();
+    smali.addClass("Test");
+    smali.setSourceFile("Test.java");
+    smali.addMainMethod(
+        0,
+        ".line 42",
+        "invoke-static {}, Ljava/lang/System;->nanoTime()J",
+        ".line 0",
+        "new-instance v0, Ljava/lang/RuntimeException;",
+        "invoke-direct {v0}, Ljava/lang/RuntimeException;-><init>()V",
+        ".line 123",
+        "throw v0");
+
+    testForRuntime(parameters)
+        .addProgramDexFileData(smali.compile())
+        .run(parameters.getRuntime(), "Test")
+        .assertFailureWithErrorThatThrows(RuntimeException.class)
+        .inspectStackTrace(this::checkStackTrace);
+  }
+
+  private void checkStackTrace(StackTrace stacktrace) {
+    assertThat(
+        stacktrace,
+        isSame(
+            StackTrace.builder()
+                .add(
+                    StackTraceLine.builder()
+                        .setLineNumber(0)
+                        .setMethodName("main")
+                        .setClassName("Test")
+                        .setFileName("Test.java")
+                        .build())
+                .build()));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountSingleLineCodeTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountSingleLineCodeTestRunner.java
index 398ecb4..c036aaf 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountSingleLineCodeTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountSingleLineCodeTestRunner.java
@@ -5,7 +5,6 @@
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
@@ -58,15 +57,13 @@
                   // For a custom source file, all debug info must be present.
                   assertEquals("X", line.fileName);
                   assertTrue("Expected line number in: " + line, line.hasLineNumber());
-                } else if (vmHasPcSupport()) {
-                  // Single line debug info is stripped. If running with PC support the PC is
-                  // printed.
+                } else if (compileApiHasPcSupport()) {
                   assertEquals("Unknown Source", line.fileName);
                   assertTrue("Expected PC in: " + line, line.hasLineNumber());
                 } else {
-                  // Otherwise, just the bare source file is printed.
+                  // Otherwise, the bare source file is printed and a line.
                   assertEquals("SourceFile", line.fileName);
-                  assertFalse("Expected no line number in: " + line, line.hasLineNumber());
+                  assertTrue("Expected line number in: " + line, line.hasLineNumber());
                 }
               }
               assertEquals("Expected 4 stack frames in:\n" + s, 4, s.getStackTraceLines().size());
@@ -84,11 +81,8 @@
                             .build())));
   }
 
-  private boolean vmHasPcSupport() {
-    return parameters
-        .asDexRuntime()
-        .maxSupportedApiLevel()
-        .isGreaterThanOrEqualTo(apiLevelWithPcAsLineNumberSupport());
+  private boolean compileApiHasPcSupport() {
+    return parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithPcAsLineNumberSupport());
   }
 
   private StackTraceLine makeLine(String methodName, int lineNumber) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
index 4602235..a54633b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
@@ -123,7 +123,13 @@
 
   // TODO(b/238179854): Investigate how to fix these.
   private static final Set<String> MISSING_GENERIC_TYPE_CONVERSION_PATH =
-      ImmutableSet.of("java.lang.Iterable java.nio.file.FileSystem.getFileStores()");
+      ImmutableSet.of(
+          "java.lang.Iterable java.nio.file.FileSystem.getFileStores()",
+          // The Acl seems to be unusable on Android anyway.
+          "java.util.Set java.nio.file.attribute.AclEntry.permissions()",
+          "java.util.Set java.nio.file.attribute.AclEntry.flags()",
+          "java.util.List java.nio.file.attribute.AclFileAttributeView.getAcl()",
+          "void java.nio.file.attribute.AclFileAttributeView.setAcl(java.util.List)");
 
   // TODO(b/238179854): Investigate how to fix these.
   private static final Set<String> MISSING_GENERIC_TYPE_CONVERSION_FLOW =
@@ -233,10 +239,13 @@
         specification.getWrappers().keySet().stream()
             .map(DexType::toString)
             .collect(Collectors.toSet());
-    Set<String> customConversionsInSpec =
+    Set<String> customConversionsOnly =
         specification.getCustomConversions().keySet().stream()
             .map(DexType::toString)
             .collect(Collectors.toSet());
+    // Some types are present both as custom conversions and wrappers, so that the custom conversion
+    // can catch some specific cases on top of the wrapper. We are not interested in those.
+    customConversionsOnly.removeAll(wrappersInSpec);
     Set<String> maintainTypeInSet =
         specification.getMaintainType().stream().map(DexType::toString).collect(Collectors.toSet());
     Map<String, boolean[]> genericConversionsInSpec = new HashMap<>();
@@ -251,9 +260,6 @@
               genericConversionsInSpec.put(method.toString(), indexes);
             });
 
-    assertEquals(
-        Collections.emptySet(), Sets.intersection(wrappersInSpec, customConversionsInSpec));
-
     CodeInspector nonDesugaredJar = new CodeInspector(ToolHelper.getAndroidJar(targetApi));
     Set<DexEncodedMethod> genericDependencies = new HashSet<>();
     Map<ClassReference, Set<MethodReference>> directWrappers =
@@ -261,7 +267,7 @@
             desugaredApiJar,
             preDesugarTypes,
             nonDesugaredJar,
-            customConversionsInSpec,
+            customConversionsOnly,
             maintainTypeInSet,
             genericConversionsInSpec,
             genericDependencies);
@@ -270,7 +276,7 @@
             directWrappers,
             preDesugarTypes,
             nonDesugaredJar,
-            customConversionsInSpec,
+            customConversionsOnly,
             maintainTypeInSet,
             specification.getWrappers(),
             genericConversionsInSpec,
@@ -301,7 +307,10 @@
     // java.util.stream.Collector$Characteristics is required for api generic type conversion
     // on JDK8, but that is not supported on legacy specification used for JDK8 and on old
     // R8 compiler versions.
-    int expectedMissingWrappers = libraryDesugaringSpecification == JDK8 ? 1 : 0;
+    int expectedMissingWrappers =
+        libraryDesugaringSpecification == JDK8
+            ? 1
+            : (libraryDesugaringSpecification == JDK11_PATH ? 4 : 0);
 
     {
       Set<String> missingWrappers = getMissingWrappers(indirectWrappers, wrappersInSpec);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesCreateTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesCreateTest.java
new file mode 100644
index 0000000..af5df8a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesCreateTest.java
@@ -0,0 +1,220 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary.jdk11;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+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 FilesCreateTest extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "ind3f class java.nio.file.NoSuchFileException :: dir",
+          "ind4f class java.nio.file.NoSuchFileException :: dir",
+          "ind5f class java.nio.file.NoSuchFileException :: f.txt",
+          "ind6f class java.nio.file.NoSuchFileException :: f.txt",
+          "true",
+          "true",
+          "notExisting1 class java.nio.file.NoSuchFileException :: notExisting1.txt",
+          "false",
+          "false",
+          "readAttributes class java.nio.file.NoSuchFileException :: f1.txt",
+          "readAttributes class java.nio.file.NoSuchFileException :: f1.txt");
+  private static final String EXPECTED_RESULT_DESUGARING =
+      StringUtils.lines(
+          "ind3f class java.nio.file.NoSuchFileException :: dir",
+          "ind4f class java.nio.file.NoSuchFileException :: dir",
+          "ind5f class java.io.FileNotFoundException :: f.txt: open failed: ENOENT (No such file or"
+              + " directory)",
+          "ind6f class java.io.FileNotFoundException :: f.txt: open failed: ENOENT (No such file or"
+              + " directory)",
+          "true",
+          "true",
+          "notExisting1 class java.io.IOException :: notExisting1.txt before deletion.",
+          "false",
+          "true",
+          "1970-01-01T00:00:00Z",
+          "1970-01-01T00:00:00Z");
+  private static final String EXPECTED_FILES =
+      StringUtils.lines(
+          "ind2s/dir", "ind2s", "ind1s/dir", "ind1s", "f4.txt", "f3.txt", "dir2s", "dir1s");
+
+  private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        // Skip Android 4.4.4 due to missing libjavacrypto.
+        getTestParameters()
+            .withCfRuntime(CfVm.JDK11)
+            .withDexRuntime(Version.V4_0_4)
+            .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+            .withAllApiLevels()
+            .build(),
+        ImmutableList.of(JDK11_PATH),
+        DEFAULT_SPECIFICATIONS);
+  }
+
+  public FilesCreateTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
+    this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
+  }
+
+  @Test
+  public void test() throws Throwable {
+    if (parameters.isCfRuntime()) {
+      // Reference runtime, we use Jdk 11 since this is Jdk 11 desugared library, not that Jdk 8
+      // behaves differently on this test.
+      Assume.assumeTrue(parameters.isCfRuntime(CfVm.JDK11) && !ToolHelper.isWindows());
+      testForJvm()
+          .addInnerClasses(getClass())
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(getExpectedResult());
+      return;
+    }
+    Assume.assumeFalse(
+        "The command mkdir fails on Android 7.0, we need to investigate if this is an emulator"
+            + "issue or a real issue.",
+        parameters.getDexRuntimeVersion().equals(Version.V7_0_0));
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .compile()
+        .withArt6Plus64BitsLib()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(getExpectedResult());
+  }
+
+  private String getExpectedResult() {
+    if (parameters.isCfRuntime()) {
+      return EXPECTED_RESULT + EXPECTED_FILES;
+    }
+    return (libraryDesugaringSpecification.usesPlatformFileSystem(parameters)
+            ? EXPECTED_RESULT
+            : EXPECTED_RESULT_DESUGARING)
+        + EXPECTED_FILES;
+  }
+
+  public static class TestClass {
+
+    private static void printError(Throwable t, String prefix) {
+      String[] split =
+          t.getMessage() == null ? new String[] {"no-message"} : t.getMessage().split("/");
+      System.out.println(prefix + " " + t.getClass() + " :: " + split[split.length - 1]);
+    }
+
+    public static void main(String[] args) throws Throwable {
+      Path root = Files.createTempDirectory("tmp_test");
+      Files.createDirectories(root.resolve("ind1s/dir"));
+      Files.createDirectories(root.resolve("ind2s/dir"), getFileAttribute());
+      try {
+        Files.createDirectory(root.resolve("ind3f/dir"));
+      } catch (Throwable t) {
+        printError(t, "ind3f");
+      }
+      try {
+        Files.createDirectory(root.resolve("ind4f/dir"), getFileAttribute());
+      } catch (Throwable t) {
+        printError(t, "ind4f");
+      }
+      Files.createDirectory(root.resolve("dir1s"));
+      Files.createDirectory(root.resolve("dir2s"), getFileAttribute());
+      try {
+        Files.createFile(root.resolve("ind5f/f.txt"));
+      } catch (Throwable t) {
+        printError(t, "ind5f");
+      }
+      try {
+        Files.createFile(root.resolve("ind6f/f.txt"), getFileAttribute());
+      } catch (Throwable t) {
+        printError(t, "ind6f");
+      }
+      Files.createFile(root.resolve("f1.txt"));
+      Files.createFile(root.resolve("f2.txt"), getFileAttribute());
+
+      System.out.println(Files.exists(root.resolve("f1.txt")));
+      System.out.println(Files.exists(root.resolve("f1.txt"), LinkOption.NOFOLLOW_LINKS));
+
+      Files.delete(root.resolve("f1.txt"));
+      try {
+        Files.delete(root.resolve("notExisting1.txt"));
+      } catch (Throwable t) {
+        printError(t, "notExisting1");
+      }
+      Files.deleteIfExists(root.resolve("f2.txt"));
+      Files.deleteIfExists(root.resolve("notExisting2.txt"));
+
+      System.out.println(Files.exists(root.resolve("f1.txt")));
+      System.out.println(Files.exists(root.resolve("f1.txt"), LinkOption.NOFOLLOW_LINKS));
+      try {
+        System.out.println(
+            Files.readAttributes(root.resolve("f1.txt"), BasicFileAttributes.class)
+                .lastAccessTime());
+      } catch (Throwable t) {
+        printError(t, "readAttributes");
+      }
+      try {
+        System.out.println(
+            Files.readAttributes(
+                    root.resolve("f1.txt"), BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)
+                .lastAccessTime());
+      } catch (Throwable t) {
+        printError(t, "readAttributes");
+      }
+
+      // Recreate for the final print.
+      Files.createFile(root.resolve("f3.txt"));
+      Files.createFile(root.resolve("f4.txt"), getFileAttribute());
+
+      Files.walk(root)
+          .sorted(Comparator.reverseOrder())
+          .map(
+              f -> {
+                if (f != root) {
+                  System.out.println(f.subpath(2, f.getNameCount()));
+                }
+                return f.toFile();
+              })
+          .forEach(File::delete);
+    }
+
+    public static FileAttribute<Set<PosixFilePermission>> getFileAttribute() {
+      return PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr-xr-x"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesMoveCopyTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesMoveCopyTest.java
new file mode 100644
index 0000000..e6eeff2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesMoveCopyTest.java
@@ -0,0 +1,259 @@
+// 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.jdk11.FilesMoveCopyTest.TestClass.CopyOrMove.COPY;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdk11.FilesMoveCopyTest.TestClass.CopyOrMove.COPY_FROM_INPUT_STREAM;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdk11.FilesMoveCopyTest.TestClass.CopyOrMove.COPY_TO_OUTPUT_STREAM;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdk11.FilesMoveCopyTest.TestClass.CopyOrMove.MOVE;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdk11.FilesMoveCopyTest.TestClass.NewFile.EXISTING;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdk11.FilesMoveCopyTest.TestClass.NewFile.NEW;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.CopyOption;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests Files#copy and Files#move methods. Known limitations/details below 26: - ATOMIC MOVE is a
+ * no-op, moves are effectively file renaming which seem to be atomic on Android. - COPY_ATTRIBUTES
+ * is a no-op, except if unsupported in which case it throws appropriately, now it seems only basic
+ * file attributes are supported below 26 anyway and such attributes are not meaningfully copied
+ * around anyway. Creation/last modification/last access times are set to the time of the move/copy,
+ * and the other ones such as "isDirectory" cannot be easily abused.
+ */
+@RunWith(Parameterized.class)
+public class FilesMoveCopyTest extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED_RESULT_FORMAT =
+      StringUtils.lines(
+          "Testing COPY_TO_OUTPUT_STREAM EXISTING NONE",
+          "COPY_TO_OUTPUT_STREAM_NONE",
+          "Testing MOVE NEW NONE",
+          "MOVE_NONE",
+          "Testing COPY NEW NONE",
+          "COPY_NONE",
+          "Testing COPY_FROM_INPUT_STREAM NEW NONE",
+          "COPY_FROM_INPUT_STREAM_NONE",
+          "Testing MOVE EXISTING NONE",
+          "Failure: class java.nio.file.FileAlreadyExistsException :: dest",
+          "Testing COPY EXISTING NONE",
+          "Failure: class java.nio.file.FileAlreadyExistsException :: dest",
+          "Testing COPY_FROM_INPUT_STREAM EXISTING NONE",
+          "%s",
+          "Testing MOVE NEW ATOMIC_MOVE",
+          "MOVE_ATOMIC_MOVE",
+          "Testing COPY NEW ATOMIC_MOVE",
+          "Failure: class java.lang.UnsupportedOperationException :: Unsupported copy option",
+          "Testing COPY_FROM_INPUT_STREAM NEW ATOMIC_MOVE",
+          "Failure: class java.lang.UnsupportedOperationException :: ATOMIC_MOVE not supported",
+          "Testing MOVE NEW COPY_ATTRIBUTES",
+          "Failure: class java.lang.UnsupportedOperationException :: Unsupported copy option",
+          "Testing COPY NEW COPY_ATTRIBUTES",
+          "COPY_COPY_ATTRIBUTES",
+          "Testing COPY_FROM_INPUT_STREAM NEW COPY_ATTRIBUTES",
+          "Failure: class java.lang.UnsupportedOperationException :: COPY_ATTRIBUTES not supported",
+          "Testing MOVE NEW REPLACE_EXISTING",
+          "MOVE_REPLACE_EXISTING",
+          "Testing COPY NEW REPLACE_EXISTING",
+          "COPY_REPLACE_EXISTING",
+          "Testing COPY_FROM_INPUT_STREAM NEW REPLACE_EXISTING",
+          "COPY_FROM_INPUT_STREAM_REPLACE_EXISTING",
+          "Testing MOVE EXISTING REPLACE_EXISTING",
+          "MOVE_REPLACE_EXISTING",
+          "Testing COPY EXISTING REPLACE_EXISTING",
+          "COPY_REPLACE_EXISTING",
+          "Testing COPY_FROM_INPUT_STREAM EXISTING REPLACE_EXISTING",
+          "COPY_FROM_INPUT_STREAM_REPLACE_EXISTING",
+          "Testing MOVE NEW NOFOLLOW_LINKS",
+          "MOVE_NOFOLLOW_LINKS",
+          "Testing COPY NEW NOFOLLOW_LINKS",
+          "COPY_NOFOLLOW_LINKS",
+          "Testing COPY_FROM_INPUT_STREAM NEW NOFOLLOW_LINKS",
+          "Failure: class java.lang.UnsupportedOperationException :: NOFOLLOW_LINKS not supported");
+
+  private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        // Skip Android 4.4.4 due to missing libjavacrypto.
+        getTestParameters()
+            .withCfRuntime(CfVm.JDK11)
+            .withDexRuntime(Version.V4_0_4)
+            .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+            .withAllApiLevels()
+            .build(),
+        ImmutableList.of(JDK11_PATH),
+        DEFAULT_SPECIFICATIONS);
+  }
+
+  public FilesMoveCopyTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
+    this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
+  }
+
+  private String getExpectedResult() {
+    // TODO(b/260208125): Fix invalid behavior.
+    // When copying from the input stream into an existing file and no options,
+    // the desugared version succeeds by deleting the file while the regular version fails
+    // with FileAlreadyExistsException exception.
+    String result =
+        (parameters.isDexRuntime()
+                && !libraryDesugaringSpecification.usesPlatformFileSystem(parameters))
+            ? "COPY_FROM_INPUT_STREAM_NONE"
+            : "Failure: class java.nio.file.FileAlreadyExistsException :: dest";
+    return String.format(EXPECTED_RESULT_FORMAT, result);
+  }
+
+  @Test
+  public void test() throws Throwable {
+    if (parameters.isCfRuntime()) {
+      // Reference runtime, we use Jdk 11 since this is Jdk 11 desugared library, not that Jdk 8
+      // behaves differently on this test.
+      Assume.assumeTrue(parameters.isCfRuntime(CfVm.JDK11) && !ToolHelper.isWindows());
+      testForJvm()
+          .addInnerClasses(getClass())
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(getExpectedResult());
+      return;
+    }
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .compile()
+        .withArt6Plus64BitsLib()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(getExpectedResult());
+  }
+
+  public static class TestClass {
+
+    enum CopyOrMove {
+      COPY,
+      COPY_FROM_INPUT_STREAM,
+      COPY_TO_OUTPUT_STREAM,
+      MOVE
+    }
+
+    enum NewFile {
+      EXISTING,
+      NEW
+    }
+
+    public static void main(String[] args) throws Throwable {
+      test(COPY_TO_OUTPUT_STREAM, EXISTING, null);
+      for (CopyOption copyOption : allOptions()) {
+        test(MOVE, NEW, copyOption);
+        test(COPY, NEW, copyOption);
+        test(COPY_FROM_INPUT_STREAM, NEW, copyOption);
+        if (copyOption == null || copyOption == StandardCopyOption.REPLACE_EXISTING) {
+          test(MOVE, EXISTING, copyOption);
+          test(COPY, EXISTING, copyOption);
+          test(COPY_FROM_INPUT_STREAM, EXISTING, copyOption);
+        }
+      }
+    }
+
+    private static CopyOption[] allOptions() {
+      return new CopyOption[] {
+        null,
+        StandardCopyOption.ATOMIC_MOVE,
+        StandardCopyOption.COPY_ATTRIBUTES,
+        StandardCopyOption.REPLACE_EXISTING,
+        LinkOption.NOFOLLOW_LINKS
+      };
+    }
+
+    private static void clearTmp(Path src, Path dest) throws IOException {
+      if (Files.exists(src)) {
+        Files.delete(src);
+      }
+      if (Files.exists(dest)) {
+        Files.delete(dest);
+      }
+    }
+
+    private static void test(CopyOrMove copyOrMove, NewFile newFile, CopyOption copyOption)
+        throws IOException {
+      String copyOptionString = copyOption == null ? "NONE" : copyOption.toString();
+      CopyOption[] opts = copyOption == null ? new CopyOption[0] : new CopyOption[] {copyOption};
+      System.out.println("Testing " + copyOrMove + " " + newFile + " " + copyOptionString);
+      Path src = Files.createTempFile("src_", ".txt");
+      Path dest =
+          newFile == NEW
+              ? src.getParent().resolve("dest_" + System.currentTimeMillis() + ".txt")
+              : Files.createTempFile("dest_", ".txt");
+      String toWrite = copyOrMove + "_" + copyOptionString;
+      Files.write(src, toWrite.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE);
+      try {
+        switch (copyOrMove) {
+          case COPY:
+            Files.copy(src, dest, opts);
+            break;
+          case MOVE:
+            Files.move(src, dest, opts);
+            break;
+          case COPY_FROM_INPUT_STREAM:
+            FileInputStream in = new FileInputStream(src.toFile());
+            Files.copy(in, dest, opts);
+            in.close();
+            break;
+          case COPY_TO_OUTPUT_STREAM:
+            if (opts.length != 0) {
+              throw new UnsupportedOperationException("Api does not encode options");
+            }
+            FileOutputStream out = new FileOutputStream(dest.toFile());
+            Files.copy(src, out);
+            out.close();
+            break;
+        }
+        System.out.println(Files.readAllLines(dest).get(0));
+      } catch (Throwable t) {
+        String subMessage;
+        if (t instanceof FileAlreadyExistsException && t.getMessage() != null) {
+          // The message is the file path, however we cannot rely on temp path, so we check
+          // only against the known part of the path.
+          String[] split = t.getMessage().split("/");
+          subMessage = split[split.length - 1].substring(0, 4);
+        } else {
+          subMessage = t.getMessage();
+        }
+        System.out.println("Failure: " + t.getClass() + " :: " + subMessage);
+      }
+      clearTmp(src, dest);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesMoveTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesMoveTest.java
deleted file mode 100644
index 97feb33..0000000
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesMoveTest.java
+++ /dev/null
@@ -1,80 +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.CompilationSpecification.DEFAULT_SPECIFICATIONS;
-import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
-
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
-import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
-import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
-import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.ImmutableList;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.StandardOpenOption;
-import java.util.List;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class FilesMoveTest extends DesugaredLibraryTestBase {
-
-  private static final String EXPECTED_RESULT = StringUtils.lines("Hello");
-
-  private final TestParameters parameters;
-  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
-  private final CompilationSpecification compilationSpecification;
-
-  @Parameters(name = "{0}, spec: {1}, {2}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        // Skip Android 4.4.4 due to missing libjavacrypto.
-        getTestParameters()
-            .withDexRuntime(Version.V4_0_4)
-            .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
-            .withAllApiLevels()
-            .build(),
-        ImmutableList.of(JDK11_PATH),
-        DEFAULT_SPECIFICATIONS);
-  }
-
-  public FilesMoveTest(
-      TestParameters parameters,
-      LibraryDesugaringSpecification libraryDesugaringSpecification,
-      CompilationSpecification compilationSpecification) {
-    this.parameters = parameters;
-    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
-    this.compilationSpecification = compilationSpecification;
-  }
-
-  @Test
-  public void test() throws Throwable {
-    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
-        .addInnerClasses(getClass())
-        .addKeepMainRule(TestClass.class)
-        .compile()
-        .withArt6Plus64BitsLib()
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(EXPECTED_RESULT);
-  }
-
-  public static class TestClass {
-
-    public static void main(String[] args) throws Throwable {
-      Path src = Files.createTempFile("src", ".txt");
-      Path dest = src.getParent().resolve("dest");
-      Files.write(src, "Hello".getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE);
-      Files.move(src, dest, StandardCopyOption.ATOMIC_MOVE);
-      System.out.println(Files.readAllLines(dest).get(0));
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java
index 5ae7b6c..27f42f0 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java
@@ -6,6 +6,8 @@
 
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
+import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
@@ -25,12 +27,20 @@
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.AclFileAttributeView;
+import java.nio.file.attribute.BasicFileAttributeView;
 import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.DosFileAttributeView;
+import java.nio.file.attribute.DosFileAttributes;
+import java.nio.file.attribute.FileOwnerAttributeView;
 import java.nio.file.attribute.FileTime;
 import java.nio.file.attribute.PosixFileAttributeView;
 import java.nio.file.attribute.PosixFileAttributes;
 import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.UserDefinedFileAttributeView;
 import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -45,47 +55,51 @@
 @RunWith(Parameterized.class)
 public class FilesTest extends DesugaredLibraryTestBase {
 
-  private static final String END_EXPECTED_RESULT =
-      StringUtils.lines("tmp", "/", "true", "This", "is", "fun!");
-  private static final String EXPECTED_RESULT_DESUGARING_FILE_SYSTEM =
+  private static final String EXPECTED_RESULT_FORMAT =
       StringUtils.lines(
-              "bytes written: 11",
-              "String written: Hello World",
-              "bytes read: 11",
-              "String read: Hello World",
-              "bytes read: 11",
-              "String read: Hello World",
-              "null",
-              "true",
-              "unsupported",
-              "j$.nio.file.attribute")
-          + END_EXPECTED_RESULT;
-  private static final String EXPECTED_RESULT_PLATFORM_FILE_SYSTEM_DESUGARING =
-      StringUtils.lines(
-              "bytes written: 11",
-              "String written: Hello World",
-              "bytes read: 11",
-              "String read: Hello World",
-              "bytes read: 11",
-              "String read: Hello World",
-              "true",
-              "true",
-              "true",
-              "j$.nio.file.attribute")
-          + END_EXPECTED_RESULT;
-  private static final String EXPECTED_RESULT_PLATFORM_FILE_SYSTEM =
-      StringUtils.lines(
-              "bytes written: 11",
-              "String written: Hello World",
-              "bytes read: 11",
-              "String read: Hello World",
-              "bytes read: 11",
-              "String read: Hello World",
-              "true",
-              "true",
-              "true",
-              "java.nio.file.attribute")
-          + END_EXPECTED_RESULT;
+          "bytes written: 11",
+          "String written: Hello World",
+          "bytes read: 11",
+          "String read: Hello World",
+          "bytes read: 11",
+          "String read: Hello World",
+          "true",
+          "%s",
+          "null",
+          "null",
+          "%s",
+          "null",
+          "true",
+          "%s",
+          "unsupported",
+          "tmp",
+          "/",
+          "true",
+          "tmpFile",
+          "This",
+          "is",
+          "fun!",
+          "%s",
+          "%s",
+          "%s",
+          "%s");
+  private static final List<String> EXPECTED_RESULT_POSIX =
+      ImmutableList.of(
+          "true",
+          "true",
+          "true",
+          "Succeeded with POSIX RO:false",
+          "Successfully set RO with POSIX",
+          "Succeeded with POSIX RO:true");
+  private static final List<String> EXPECTED_RESULT_DESUGARING_NON_POSIX =
+      ImmutableList.of(
+          "null",
+          "null",
+          "unsupported",
+          "Fail to understand if the file is read-only: class"
+              + " java.lang.UnsupportedOperationException",
+          "Fail to set file as read-only: class java.lang.UnsupportedOperationException",
+          "NotSet");
 
   private final TestParameters parameters;
   private final LibraryDesugaringSpecification libraryDesugaringSpecification;
@@ -113,13 +127,21 @@
     this.compilationSpecification = compilationSpecification;
   }
 
+  private static String computeExpectedResult(boolean supportPosix, boolean j$nioClasses) {
+    List<String> strings =
+        new ArrayList<>(
+            supportPosix ? EXPECTED_RESULT_POSIX : EXPECTED_RESULT_DESUGARING_NON_POSIX);
+    strings.add(j$nioClasses ? "j$.nio.file.attribute" : "java.nio.file.attribute");
+    return String.format(EXPECTED_RESULT_FORMAT, strings.toArray());
+  }
+
   private String getExpectedResult() {
     if (libraryDesugaringSpecification.usesPlatformFileSystem(parameters)) {
       return libraryDesugaringSpecification.hasNioFileDesugaring(parameters)
-          ? EXPECTED_RESULT_PLATFORM_FILE_SYSTEM_DESUGARING
-          : EXPECTED_RESULT_PLATFORM_FILE_SYSTEM;
+          ? computeExpectedResult(true, true)
+          : computeExpectedResult(true, false);
     }
-    return EXPECTED_RESULT_DESUGARING_FILE_SYSTEM;
+    return computeExpectedResult(false, true);
   }
 
   @Test
@@ -139,11 +161,81 @@
       Path path = Files.createTempFile("example", ".txt");
       readWriteThroughFilesAPI(path);
       readThroughFileChannelAPI(path);
+      attributeViewAccess(path);
       attributeAccess(path);
       Files.setAttribute(path, "basic:lastModifiedTime", FileTime.from(Instant.EPOCH));
-      fspMethodsWithGeneric(path);
       pathGeneric();
       lines(path);
+      readOnlyTest(path);
+      fspMethodsWithGeneric(path);
+    }
+
+    private static void readOnlyTest(Path path) {
+      isReadOnly(path);
+      if (setReadOnly(path)) {
+        isReadOnly(path);
+      } else {
+        System.out.println("NotSet");
+      }
+    }
+
+    private static boolean isReadOnly(Path path) {
+      try {
+        // DOS attempt.
+        try {
+          DosFileAttributeView dosFileAttributeView =
+              Files.getFileAttributeView(path, DosFileAttributeView.class);
+          if (dosFileAttributeView != null && dosFileAttributeView.readAttributes() != null) {
+            boolean readOnly = dosFileAttributeView.readAttributes().isReadOnly();
+            System.out.println("Succeeded with DOS RO:" + readOnly);
+            return readOnly;
+          }
+        } catch (IOException ignored) {
+        }
+        // Posix attempt.
+        Set<PosixFilePermission> posixFilePermissions = Files.getPosixFilePermissions(path);
+        boolean readOnly =
+            posixFilePermissions.contains(OWNER_READ)
+                && !posixFilePermissions.contains(OWNER_WRITE);
+        System.out.println("Succeeded with POSIX RO:" + readOnly);
+        return readOnly;
+
+      } catch (Throwable t) {
+        System.out.println("Fail to understand if the file is read-only: " + t.getClass());
+        return false;
+      }
+    }
+
+    /** Common pattern to set a file as read-only: Try on Dos, on failure, retry on Posix. */
+    private static boolean setReadOnly(Path path) {
+      try {
+
+        // DOS attempt.
+        try {
+          DosFileAttributeView dosFileAttributeView =
+              Files.getFileAttributeView(path, DosFileAttributeView.class);
+          if (dosFileAttributeView != null) {
+            dosFileAttributeView.setReadOnly(true);
+            System.out.println("Successfully set RO with DOS");
+            return true;
+          }
+        } catch (IOException ignored) {
+        }
+
+        // Posix attempt.
+        Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path);
+        List<PosixFilePermission> readPermissions = Arrays.asList(OWNER_READ);
+        List<PosixFilePermission> writePermissions = Arrays.asList(PosixFilePermission.OWNER_WRITE);
+        permissions.addAll(readPermissions);
+        permissions.removeAll(writePermissions);
+        Files.setPosixFilePermissions(path, permissions);
+        System.out.println("Successfully set RO with POSIX");
+        return true;
+
+      } catch (Throwable t) {
+        System.out.println("Fail to set file as read-only: " + t.getClass());
+        return false;
+      }
     }
 
     private static void pathGeneric() throws IOException {
@@ -154,7 +246,9 @@
       Iterable<Path> rootDirectories = tmpFile.getFileSystem().getRootDirectories();
       System.out.println(rootDirectories.iterator().next());
       DirectoryStream<Path> paths = Files.newDirectoryStream(tmpDict);
-      System.out.println(paths.iterator().hasNext());
+      Iterator<Path> theIterator = paths.iterator();
+      System.out.println(theIterator.hasNext());
+      System.out.println(theIterator.next().getFileName());
     }
 
     private static void fspMethodsWithGeneric(Path path) throws IOException {
@@ -162,15 +256,71 @@
       System.out.println(mapping.values().iterator().next().getClass().getPackage().getName());
     }
 
-    private static void attributeAccess(Path path) throws IOException {
-      PosixFileAttributeView view = Files.getFileAttributeView(path, PosixFileAttributeView.class);
-      if (view != null) {
-        System.out.println(
-            view.readAttributes().permissions().contains(PosixFilePermission.OWNER_READ));
+    private static void attributeViewAccess(Path path) throws IOException {
+      BasicFileAttributeView basicView =
+          Files.getFileAttributeView(path, BasicFileAttributeView.class);
+      if (basicView != null) {
+        System.out.println(basicView.readAttributes().isRegularFile());
       } else {
         System.out.println("null");
       }
 
+      PosixFileAttributeView posixView =
+          Files.getFileAttributeView(path, PosixFileAttributeView.class);
+      if (posixView != null) {
+        System.out.println(posixView.readAttributes().permissions().contains(OWNER_READ));
+      } else {
+        System.out.println("null");
+      }
+
+      try {
+        DosFileAttributeView dosView = Files.getFileAttributeView(path, DosFileAttributeView.class);
+        if (dosView != null) {
+          System.out.println(dosView.readAttributes().isReadOnly());
+        } else {
+          System.out.println("null");
+        }
+      } catch (UnsupportedOperationException e) {
+        System.out.println("unsupported");
+      }
+
+      try {
+        AclFileAttributeView aclView = Files.getFileAttributeView(path, AclFileAttributeView.class);
+        if (aclView != null) {
+          System.out.println(aclView.getAcl().isEmpty());
+        } else {
+          System.out.println("null");
+        }
+      } catch (UnsupportedOperationException e) {
+        System.out.println("unsupported");
+      }
+
+      try {
+        FileOwnerAttributeView foView =
+            Files.getFileAttributeView(path, FileOwnerAttributeView.class);
+        if (foView != null) {
+          System.out.println(foView.getOwner() != null);
+        } else {
+          System.out.println("null");
+        }
+      } catch (UnsupportedOperationException e) {
+        System.out.println("unsupported");
+      }
+
+      try {
+        UserDefinedFileAttributeView udView =
+            Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
+        if (udView != null) {
+          System.out.println(udView.name());
+        } else {
+          System.out.println("null");
+        }
+      } catch (UnsupportedOperationException e) {
+        System.out.println("unsupported");
+      }
+    }
+
+    private static void attributeAccess(Path path) throws IOException {
       BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class);
       if (attributes != null) {
         System.out.println(attributes.isRegularFile());
@@ -181,8 +331,18 @@
       try {
         PosixFileAttributes posixAttributes = Files.readAttributes(path, PosixFileAttributes.class);
         if (posixAttributes != null) {
-          System.out.println(
-              posixAttributes.permissions().contains(PosixFilePermission.OWNER_READ));
+          System.out.println(posixAttributes.permissions().contains(OWNER_READ));
+        } else {
+          System.out.println("null");
+        }
+      } catch (UnsupportedOperationException e) {
+        System.out.println("unsupported");
+      }
+
+      try {
+        DosFileAttributes dosFileAttributes = Files.readAttributes(path, DosFileAttributes.class);
+        if (dosFileAttributes != null) {
+          System.out.println(dosFileAttributes.isReadOnly());
         } else {
           System.out.println("null");
         }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesVisitTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesVisitTest.java
new file mode 100644
index 0000000..4d7cccc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesVisitTest.java
@@ -0,0 +1,169 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary.jdk11;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.FileVisitOption;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+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 FilesVisitTest extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "Find f1",
+          "[f1.txt]",
+          "[f1.txt]",
+          "[f1.txt]",
+          "Find f4",
+          "[]",
+          "[f4.txt]",
+          "[f4.txt]",
+          "List",
+          "[f1.txt, f2.txt, f3.txt, innerDir]",
+          "Walk 1",
+          "[f1.txt, f2.txt, f3.txt, innerDir, root]",
+          "[f1.txt, f2.txt, f3.txt, innerDir, root]",
+          "Walk 7",
+          "[f1.txt, f2.txt, f3.txt, f4.txt, innerDir, root]",
+          "[f1.txt, f2.txt, f3.txt, f4.txt, innerDir, root]");
+
+  private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        // Skip Android 4.4.4 due to missing libjavacrypto.
+        getTestParameters()
+            .withCfRuntime(CfVm.JDK11)
+            .withDexRuntime(Version.V4_0_4)
+            .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+            .withAllApiLevels()
+            .build(),
+        ImmutableList.of(JDK11_PATH),
+        DEFAULT_SPECIFICATIONS);
+  }
+
+  public FilesVisitTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
+    this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
+  }
+
+  @Test
+  public void test() throws Throwable {
+    if (parameters.isCfRuntime() && !ToolHelper.isWindows()) {
+      // Reference runtime, we use Jdk 11 since this is Jdk 11 desugared library, not that Jdk 8
+      // behaves differently on this test.
+      Assume.assumeTrue(parameters.isCfRuntime(CfVm.JDK11) && !ToolHelper.isWindows());
+      testForJvm()
+          .addInnerClasses(getClass())
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+      return;
+    }
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .compile()
+        .withArt6Plus64BitsLib()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  public static class TestClass {
+
+    private static Path root;
+
+    public static void main(String[] args) throws IOException {
+      createDirStructure();
+      findTest();
+      listTest();
+      walkTest();
+    }
+
+    /** Creates the following structure root | f1 | f2 | f3 | innerDir | f4 */
+    private static void createDirStructure() throws IOException {
+      root = Files.createTempDirectory("rootTemp");
+      Files.createFile(root.resolve("f1.txt"));
+      Files.createFile(root.resolve("f2.txt"));
+      Files.createFile(root.resolve("f3.txt"));
+      Path innerDir = Files.createDirectory(root.resolve("innerDir"));
+      Files.createFile(root.resolve(innerDir.resolve("f4.txt")));
+    }
+
+    private static void meaningfulPrint(Stream<Path> paths) {
+      System.out.println(
+          paths
+              .map(f -> f == root ? Paths.get("root") : f)
+              .map(Path::getFileName)
+              .sorted()
+              .collect(Collectors.toList()));
+    }
+
+    private static void findTest() throws IOException {
+      System.out.println("Find f1");
+      meaningfulPrint(Files.find(root, 1, (p, a) -> p.getFileName().toString().contains("f1")));
+      meaningfulPrint(Files.find(root, 7, (p, a) -> p.getFileName().toString().contains("f1")));
+      meaningfulPrint(
+          Files.find(
+              root,
+              7,
+              (p, a) -> p.getFileName().toString().contains("f1"),
+              FileVisitOption.FOLLOW_LINKS));
+
+      System.out.println("Find f4");
+      meaningfulPrint(Files.find(root, 1, (p, a) -> p.getFileName().toString().contains("f4")));
+      meaningfulPrint(Files.find(root, 7, (p, a) -> p.getFileName().toString().contains("f4")));
+      meaningfulPrint(
+          Files.find(
+              root,
+              7,
+              (p, a) -> p.getFileName().toString().contains("f4"),
+              FileVisitOption.FOLLOW_LINKS));
+    }
+
+    private static void listTest() throws IOException {
+      System.out.println("List");
+      meaningfulPrint(Files.list(root));
+    }
+
+    private static void walkTest() throws IOException {
+      System.out.println("Walk 1");
+      meaningfulPrint(Files.walk(root, 1));
+      meaningfulPrint(Files.walk(root, 1, FileVisitOption.FOLLOW_LINKS));
+      System.out.println("Walk 7");
+      meaningfulPrint(Files.walk(root, 7));
+      meaningfulPrint(Files.walk(root, 7, FileVisitOption.FOLLOW_LINKS));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java
index 31f87d9..9c29a9b 100644
--- a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java
@@ -108,7 +108,21 @@
             .getStdOut();
     assertTrue(
         StringUtils.splitLines(stdout).stream()
-            .allMatch(s -> s.contains(isAndroidOOrLater ? "NULL" : "SourceFile")));
+            .allMatch(
+                s -> {
+                  if (parameters
+                      .getApiLevel()
+                      .isGreaterThanOrEqualTo(apiLevelWithPcAsLineNumberSupport())) {
+                    return s.contains("(NULL)");
+                  } else if (isAndroidOOrLater) {
+                    // On VMs with native support, no line info results in no source file printing.
+                    // TODO(b/260384637): Create debug info for such methods to avoid this.
+                    return s.equals("main(NULL)")
+                        || (!s.startsWith("main") && s.contains("(SourceFile)"));
+                  } else {
+                    return s.contains("(SourceFile)");
+                  }
+                }));
   }
 
   static class TestRunner {
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingFieldsTest.java
similarity index 70%
copy from src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingTest.java
copy to src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingFieldsTest.java
index 9829235..8e6e694 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingFieldsTest.java
@@ -21,12 +21,13 @@
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
 @RunWith(Parameterized.class)
-public class NestAttributesInDexShrinkingTest extends NestAttributesInDexTestBase
+public class NestAttributesInDexShrinkingFieldsTest extends NestAttributesInDexTestBase
     implements Opcodes {
 
   @Parameter() public TestParameters parameters;
@@ -136,59 +137,86 @@
     Dump of:
 
     public class Host {
+      private String s1 = ", ";
+      private String s2 = "!";
       public static void main(String[] args) {
         new Host().h1();
         System.out.println();
       }
 
       static class Member1 {
-        private void m(Host host) {  // private or public
-          host.h2("Hello");
-        }
+        private String s = "Hello";
       }
 
       static class Member2 {
-        private void m(Host host) {  // private or public
-          host.h2(", world!");
+        private String s = "world";
+
+        public void m(Host host) {
+          System.out.println(host.s1);
         }
       }
 
-      private void h1() {  // private or public
-        new Member1().m(this);
+      public void h1() {
+        System.out.print(new Member1().s);
+        System.out.print(s1);
+        System.out.print(new Member2().s);
         new Member2().m(this);
       }
-
-      private void h2(String message) {  // private or public
-        System.out.print(message);
-      }
     }
 
     compiled with `-target 11`. Not a transformer here, as transforming the javac post nest
     access methods is not feasible.
   */
 
-  public static byte[] dumpHost(int methodAccess) throws Exception {
-    assert methodAccess == ACC_PUBLIC || methodAccess == ACC_PRIVATE;
+  public static byte[] dumpHost(int fieldAccess) throws Exception {
+    assert fieldAccess == ACC_PUBLIC || fieldAccess == ACC_PRIVATE;
 
     ClassWriter classWriter = new ClassWriter(0);
+    FieldVisitor fieldVisitor;
     MethodVisitor methodVisitor;
 
     classWriter.visit(V11, ACC_PUBLIC | ACC_SUPER, "Host", null, "java/lang/Object", null);
+
     classWriter.visitSource("Host.java", null);
+
     classWriter.visitNestMember("Host$Member2");
+
     classWriter.visitNestMember("Host$Member1");
+
     classWriter.visitInnerClass("Host$Member2", "Host", "Member2", ACC_STATIC);
+
     classWriter.visitInnerClass("Host$Member1", "Host", "Member1", ACC_STATIC);
+
+    {
+      fieldVisitor = classWriter.visitField(fieldAccess, "s1", "Ljava/lang/String;", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      fieldVisitor = classWriter.visitField(fieldAccess, "s2", "Ljava/lang/String;", null, null);
+      fieldVisitor.visitEnd();
+    }
     {
       methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
       methodVisitor.visitCode();
       Label label0 = new Label();
       methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(1, label0);
+      methodVisitor.visitLineNumber(2, label0);
       methodVisitor.visitVarInsn(ALOAD, 0);
       methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(3, label1);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitLdcInsn(", ");
+      methodVisitor.visitFieldInsn(PUTFIELD, "Host", "s1", "Ljava/lang/String;");
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(4, label2);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitLdcInsn("!");
+      methodVisitor.visitFieldInsn(PUTFIELD, "Host", "s2", "Ljava/lang/String;");
       methodVisitor.visitInsn(RETURN);
-      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitMaxs(2, 1);
       methodVisitor.visitEnd();
     }
     {
@@ -198,146 +226,167 @@
       methodVisitor.visitCode();
       Label label0 = new Label();
       methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(3, label0);
+      methodVisitor.visitLineNumber(5, label0);
       methodVisitor.visitTypeInsn(NEW, "Host");
       methodVisitor.visitInsn(DUP);
       methodVisitor.visitMethodInsn(INVOKESPECIAL, "Host", "<init>", "()V", false);
       methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "Host", "h1", "()V", false);
       Label label1 = new Label();
       methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(4, label1);
+      methodVisitor.visitLineNumber(6, label1);
       methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
       methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "()V", false);
       Label label2 = new Label();
       methodVisitor.visitLabel(label2);
-      methodVisitor.visitLineNumber(5, label2);
+      methodVisitor.visitLineNumber(7, label2);
       methodVisitor.visitInsn(RETURN);
       methodVisitor.visitMaxs(2, 1);
       methodVisitor.visitEnd();
     }
     {
-      methodVisitor = classWriter.visitMethod(methodAccess, "h1", "()V", null, null);
+      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "h1", "()V", null, null);
       methodVisitor.visitCode();
       Label label0 = new Label();
       methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(18, label0);
+      methodVisitor.visitLineNumber(26, label0);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
       methodVisitor.visitTypeInsn(NEW, "Host$Member1");
       methodVisitor.visitInsn(DUP);
       methodVisitor.visitMethodInsn(INVOKESPECIAL, "Host$Member1", "<init>", "()V", false);
-      methodVisitor.visitVarInsn(ALOAD, 0);
-      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "Host$Member1", "m", "(LHost;)V", false);
+      methodVisitor.visitFieldInsn(GETFIELD, "Host$Member1", "s", "Ljava/lang/String;");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);
       Label label1 = new Label();
       methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(19, label1);
+      methodVisitor.visitLineNumber(27, label1);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitFieldInsn(GETFIELD, "Host", "s1", "Ljava/lang/String;");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(28, label2);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitTypeInsn(NEW, "Host$Member2");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "Host$Member2", "<init>", "()V", false);
+      methodVisitor.visitFieldInsn(GETFIELD, "Host$Member2", "s", "Ljava/lang/String;");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);
+      Label label3 = new Label();
+      methodVisitor.visitLabel(label3);
+      methodVisitor.visitLineNumber(30, label3);
       methodVisitor.visitTypeInsn(NEW, "Host$Member2");
       methodVisitor.visitInsn(DUP);
       methodVisitor.visitMethodInsn(INVOKESPECIAL, "Host$Member2", "<init>", "()V", false);
       methodVisitor.visitVarInsn(ALOAD, 0);
       methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "Host$Member2", "m", "(LHost;)V", false);
-      Label label2 = new Label();
-      methodVisitor.visitLabel(label2);
-      methodVisitor.visitLineNumber(20, label2);
+      Label label4 = new Label();
+      methodVisitor.visitLabel(label4);
+      methodVisitor.visitLineNumber(31, label4);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(3, 1);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+
+  public static byte[] dumpMember1(int fieldAccess) throws Exception {
+    assert fieldAccess == ACC_PUBLIC || fieldAccess == ACC_PRIVATE;
+
+    ClassWriter classWriter = new ClassWriter(0);
+    FieldVisitor fieldVisitor;
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(V11, ACC_SUPER, "Host$Member1", null, "java/lang/Object", null);
+
+    classWriter.visitSource("Host.java", null);
+
+    classWriter.visitNestHost("Host");
+
+    classWriter.visitInnerClass("Host$Member1", "Host", "Member1", ACC_STATIC);
+
+    {
+      fieldVisitor = classWriter.visitField(fieldAccess, "s", "Ljava/lang/String;", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(9, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(10, label1);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitLdcInsn("Hello");
+      methodVisitor.visitFieldInsn(PUTFIELD, "Host$Member1", "s", "Ljava/lang/String;");
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 1);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+
+  public static byte[] dumpMember2(int fieldAccess) throws Exception {
+    assert fieldAccess == ACC_PUBLIC || fieldAccess == ACC_PRIVATE;
+
+    ClassWriter classWriter = new ClassWriter(0);
+    FieldVisitor fieldVisitor;
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(V11, ACC_SUPER, "Host$Member2", null, "java/lang/Object", null);
+
+    classWriter.visitSource("Host.java", null);
+
+    classWriter.visitNestHost("Host");
+
+    classWriter.visitInnerClass("Host$Member2", "Host", "Member2", ACC_STATIC);
+
+    {
+      fieldVisitor = classWriter.visitField(fieldAccess, "s", "Ljava/lang/String;", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(16, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(17, label1);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitLdcInsn("world");
+      methodVisitor.visitFieldInsn(PUTFIELD, "Host$Member2", "s", "Ljava/lang/String;");
       methodVisitor.visitInsn(RETURN);
       methodVisitor.visitMaxs(2, 1);
       methodVisitor.visitEnd();
     }
     {
-      methodVisitor =
-          classWriter.visitMethod(methodAccess, "h2", "(Ljava/lang/String;)V", null, null);
+      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "m", "(LHost;)V", null, null);
       methodVisitor.visitCode();
       Label label0 = new Label();
       methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(23, label0);
+      methodVisitor.visitLineNumber(20, label0);
       methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
       methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitFieldInsn(GETFIELD, "Host", "s2", "Ljava/lang/String;");
       methodVisitor.visitMethodInsn(
           INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);
       Label label1 = new Label();
       methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(24, label1);
-      methodVisitor.visitInsn(RETURN);
-      methodVisitor.visitMaxs(2, 2);
-      methodVisitor.visitEnd();
-    }
-    classWriter.visitEnd();
-
-    return classWriter.toByteArray();
-  }
-
-  public static byte[] dumpMember1(int methodAccess) throws Exception {
-    assert methodAccess == ACC_PUBLIC || methodAccess == ACC_PRIVATE;
-
-    ClassWriter classWriter = new ClassWriter(0);
-    MethodVisitor methodVisitor;
-
-    classWriter.visit(V11, ACC_SUPER, "Host$Member1", null, "java/lang/Object", null);
-    classWriter.visitSource("Host.java", null);
-    classWriter.visitNestHost("Host");
-    classWriter.visitInnerClass("Host$Member1", "Host", "Member1", ACC_STATIC);
-    {
-      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(7, label0);
-      methodVisitor.visitVarInsn(ALOAD, 0);
-      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
-      methodVisitor.visitInsn(RETURN);
-      methodVisitor.visitMaxs(1, 1);
-      methodVisitor.visitEnd();
-    }
-    {
-      methodVisitor = classWriter.visitMethod(methodAccess, "m", "(LHost;)V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(9, label0);
-      methodVisitor.visitVarInsn(ALOAD, 1);
-      methodVisitor.visitLdcInsn("Hello");
-      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "Host", "h2", "(Ljava/lang/String;)V", false);
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(10, label1);
-      methodVisitor.visitInsn(RETURN);
-      methodVisitor.visitMaxs(2, 2);
-      methodVisitor.visitEnd();
-    }
-    classWriter.visitEnd();
-
-    return classWriter.toByteArray();
-  }
-
-  public static byte[] dumpMember2(int methodAccess) throws Exception {
-    assert methodAccess == ACC_PUBLIC || methodAccess == ACC_PRIVATE;
-
-    ClassWriter classWriter = new ClassWriter(0);
-    MethodVisitor methodVisitor;
-
-    classWriter.visit(V11, ACC_SUPER, "Host$Member2", null, "java/lang/Object", null);
-    classWriter.visitSource("Host.java", null);
-    classWriter.visitNestHost("Host");
-    classWriter.visitInnerClass("Host$Member2", "Host", "Member2", ACC_STATIC);
-    {
-      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(13, label0);
-      methodVisitor.visitVarInsn(ALOAD, 0);
-      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
-      methodVisitor.visitInsn(RETURN);
-      methodVisitor.visitMaxs(1, 1);
-      methodVisitor.visitEnd();
-    }
-    {
-      methodVisitor = classWriter.visitMethod(methodAccess, "m", "(LHost;)V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(14, label0);
-      methodVisitor.visitVarInsn(ALOAD, 1);
-      methodVisitor.visitLdcInsn(", world!");
-      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "Host", "h2", "(Ljava/lang/String;)V", false);
+      methodVisitor.visitLineNumber(21, label1);
       methodVisitor.visitInsn(RETURN);
       methodVisitor.visitMaxs(2, 2);
       methodVisitor.visitEnd();
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingMethodsTest.java
similarity index 98%
rename from src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingTest.java
rename to src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingMethodsTest.java
index 9829235..d710ad0 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingMethodsTest.java
@@ -26,7 +26,7 @@
 import org.objectweb.asm.Opcodes;
 
 @RunWith(Parameterized.class)
-public class NestAttributesInDexShrinkingTest extends NestAttributesInDexTestBase
+public class NestAttributesInDexShrinkingMethodsTest extends NestAttributesInDexTestBase
     implements Opcodes {
 
   @Parameter() public TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/examples/jdk9/VarHandle.java b/src/test/java/com/android/tools/r8/examples/jdk9/VarHandle.java
new file mode 100644
index 0000000..620e01b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/jdk9/VarHandle.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.examples.jdk9;
+
+import com.android.tools.r8.examples.JavaExampleClassProxy;
+import java.nio.file.Path;
+
+public class VarHandle {
+
+  private static final String EXAMPLE_FILE = "examplesJava9/varhandle";
+
+  public static final JavaExampleClassProxy VarHandleTests =
+      new JavaExampleClassProxy(EXAMPLE_FILE, "varhandle/VarHandleTests");
+
+  public static final JavaExampleClassProxy ArrayOfInt =
+      new JavaExampleClassProxy(EXAMPLE_FILE, "varhandle/ArrayOfInt");
+  public static final JavaExampleClassProxy ArrayOfLong =
+      new JavaExampleClassProxy(EXAMPLE_FILE, "varhandle/ArrayOfLong");
+
+  public static final JavaExampleClassProxy InstanceIntField =
+      new JavaExampleClassProxy(EXAMPLE_FILE, "varhandle/InstanceIntField");
+  public static final JavaExampleClassProxy StaticIntField =
+      new JavaExampleClassProxy(EXAMPLE_FILE, "varhandle/StaticIntField");
+  public static final JavaExampleClassProxy IntFieldWithMethodHandle =
+      new JavaExampleClassProxy(EXAMPLE_FILE, "varhandle/IntFieldWithMethodHandle");
+
+  public static final JavaExampleClassProxy InstanceLongField =
+      new JavaExampleClassProxy(EXAMPLE_FILE, "varhandle/InstanceLongField");
+
+  public static final JavaExampleClassProxy InstanceObjectField =
+      new JavaExampleClassProxy(EXAMPLE_FILE, "varhandle/InstanceObjectField");
+
+  public static final JavaExampleClassProxy InstanceStringField =
+      new JavaExampleClassProxy(EXAMPLE_FILE, "varhandle/InstanceStringField");
+
+  public static Path jar() {
+    return JavaExampleClassProxy.examplesJar(EXAMPLE_FILE);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarMethodHandlesLookup.java b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarMethodHandlesLookup.java
new file mode 100644
index 0000000..d700033
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarMethodHandlesLookup.java
@@ -0,0 +1,126 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.varhandle;
+
+public final class DesugarMethodHandlesLookup {
+  public DesugarVarHandle findVarHandle(Class<?> recv, String name, Class<?> type)
+      throws NoSuchFieldException, IllegalAccessException {
+    return new DesugarVarHandle(recv, name, type);
+  }
+
+  /*
+   * Remaining methods on MethodHandles.Lookup.
+   *
+   * These could be implemented by forwarding to the runtime MethodHandles.Lookup if present at
+   * runtime.
+   *
+
+  public Class<?>	accessClass(Class<?> targetClass) throws IllegalAccessException {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public MethodHandle bind(Object receiver, String name, MethodType type) throws NoSuchMethodException,
+      IllegalAccessException {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public Class<?> defineClass(byte[] bytes) throws IllegalAccessException {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public MethodHandles.Lookup dropLookupMode(int modeToDrop) {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public Class<?> findClass(String targetName) throws ClassNotFoundException, IllegalAccessException {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public MethodHandle findConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public MethodHandle findGetter(Class<?> refc, String name, Class<?> type) throws NoSuchMethodException, IllegalAccessException {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public MethodHandle findSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public MethodHandle findSpecial(Class<?> refc, String name, MethodType type, Class<?> specialCaller) throws NoSuchFieldException, IllegalAccessException {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public MethodHandle findStatic(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public MethodHandle findStaticGetter(Class<?> refc, String name, Class<?> type) throws NoSuchMethodException, IllegalAccessException {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public MethodHandle findStaticSetter(Class<?> refc, String name, Class<?> type) throws NoSuchMethodException, IllegalAccessException {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public DesugarVarHandle findStaticVarHandle(Class<?> decl, String name, Class<?> type) throws Exception throws NoSuchFieldException, IllegalAccessException {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public MethodHandle findVirtual(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public boolean hasPrivateAccess() {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public MethodHandles.Lookup in(Class<?> requestedLookupClass) {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public Class<?> lookupClass() {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public int lookupModes() {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public MethodHandleInfo revealDirect(MethodHandle target) {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public String toString() {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public MethodHandle unreflect(Method m) throws IllegalAccessException {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public MethodHandle unreflectConstructor(Constructor<?> c) throws IllegalAccessException {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public MethodHandle unreflectGetter(Field f) throws IllegalAccessException {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public MethodHandle unreflectSetter(Field f) throws IllegalAccessException {
+    throw new RuntimeException("Unsupported");
+  }
+
+  public MethodHandle unreflectSpecial(Method m, Class<?> specialCaller) {
+    throw new RuntimeException("Unsupported");
+  }
+
+  DesugarVarHandle unreflectVarHandle(Field f) throws IllegalAccessException {
+    throw new RuntimeException("Unsupported");
+  }
+
+  */
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java
new file mode 100644
index 0000000..7e88943
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.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.ir.desugar.varhandle;
+
+import java.lang.reflect.Field;
+
+// Template class for desugaring VarHandle into com.android.tools.r8.DesugarVarHandle.
+public final class DesugarVarHandle {
+
+  // This only have methods found in libcore/libart/src/main/java/sun/misc/Unsafe.java for Lollipop.
+  private static class UnsafeStub {
+
+    public long objectFieldOffset(Field f) {
+      throw new RuntimeException("Stub called.");
+    }
+  }
+
+  private final UnsafeStub U;
+  private final Class<?> recv;
+  private final Class<?> type;
+  private final long offset;
+
+  DesugarVarHandle(Class<?> recv, String name, Class<?> type)
+      throws NoSuchFieldException, IllegalAccessException {
+    Field theUnsafe = UnsafeStub.class.getDeclaredField("theUnsafe");
+    theUnsafe.setAccessible(true);
+    U = (UnsafeStub) theUnsafe.get(null);
+    this.recv = recv;
+    Field field = recv.getDeclaredField(name);
+    this.type = field.getType();
+    this.offset = U.objectFieldOffset(recv.getDeclaredField(name));
+  }
+
+  // get variants.
+  Object get(Object ct1) {
+    // TODO(b/247076137): Implement.
+    return null;
+  }
+
+  int getInt(Object ct1) {
+    // TODO(b/247076137): Implement.
+    return -1;
+  }
+
+  long getLong(Object ct1) {
+    // TODO(b/247076137): Implement.
+    return -1L;
+  }
+
+  // set variants.
+  void set(Object ct1, Object newValue) {
+    // TODO(b/247076137): Implement.
+  }
+
+  void setInt(Object ct1, int newValue) {
+    // TODO(b/247076137): Implement.
+  }
+
+  void setLong(Object ct1, long newValue) {
+    // TODO(b/247076137): Implement.
+  }
+
+  boolean compareAndSet(Object ct1, Object expectedValue, Object newValue) {
+    // TODO(b/247076137): Implement.
+    return false;
+  }
+
+  boolean compareAndSetInt(Object ct1, int expectedValue, int newValue) {
+    // TODO(b/247076137): Implement.
+    return false;
+  }
+
+  boolean compareAndSetLong(Object ct1, long expectedValue, long newValue) {
+    // TODO(b/247076137): Implement.
+    return false;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/varhandle/GenerateVarHandleMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/GenerateVarHandleMethods.java
new file mode 100644
index 0000000..6ce480b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/GenerateVarHandleMethods.java
@@ -0,0 +1,233 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.varhandle;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfTypeInstruction;
+import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class GenerateVarHandleMethods extends MethodGenerationBase {
+
+  private final DexType GENERATED_TYPE =
+      factory.createType("Lcom/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods;");
+  private final List<Class<?>> METHOD_TEMPLATE_CLASSES =
+      ImmutableList.of(DesugarMethodHandlesLookup.class, DesugarVarHandle.class);
+
+  protected final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntime(CfVm.JDK9).build();
+  }
+
+  public GenerateVarHandleMethods(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Override
+  protected DexType getGeneratedType() {
+    return GENERATED_TYPE;
+  }
+
+  @Override
+  protected List<Class<?>> getMethodTemplateClasses() {
+    return METHOD_TEMPLATE_CLASSES;
+  }
+
+  @Override
+  protected List<Class<?>> getClassesToGenerate() {
+    return ImmutableList.of(DesugarVarHandle.class, DesugarMethodHandlesLookup.class);
+  }
+
+  @Override
+  protected boolean includeMethod(DexEncodedMethod method) {
+    // Include all methods, including constructors.
+    return true;
+  }
+
+  @Override
+  protected int getYear() {
+    return 2022;
+  }
+
+  @Override
+  protected DexEncodedField getField(DexEncodedField field) {
+    if (field.getType().getTypeName().endsWith("$UnsafeStub")) {
+      return DexEncodedField.builder(field)
+          .setField(
+              factory.createField(
+                  field.getHolderType(), factory.createType("Lsun/misc/Unsafe;"), field.getName()))
+          .disableAndroidApiLevelCheck()
+          .build();
+    }
+    return field;
+  }
+
+  // TODO(b/261024278): Share this code.
+  private class InstructionTypeMapper {
+    private final Map<DexType, DexType> typeMap;
+
+    InstructionTypeMapper(Map<DexType, DexType> typeMap) {
+      this.typeMap = typeMap;
+    }
+
+    private CfInstruction rewriteInstruction(CfInstruction instruction) {
+      if (instruction.isTypeInstruction()) {
+        CfInstruction rewritten = rewriteTypeInstruction(instruction.asTypeInstruction());
+        return rewritten == null ? instruction : rewritten;
+      }
+      if (instruction.isFieldInstruction()) {
+        return rewriteFieldInstruction(instruction.asFieldInstruction());
+      }
+      if (instruction.isInvoke()) {
+        return rewriteInvokeInstruction(instruction.asInvoke());
+      }
+      if (instruction.isFrame()) {
+        return rewriteFrameInstruction(instruction.asFrame());
+      }
+      return instruction;
+    }
+
+    private CfInstruction rewriteInvokeInstruction(CfInvoke instruction) {
+      CfInvoke invoke = instruction.asInvoke();
+      DexMethod method = invoke.getMethod();
+      String name = method.getName().toString();
+      DexType holderType = invoke.getMethod().getHolderType();
+      DexType rewrittenType = typeMap.getOrDefault(holderType, holderType);
+      if (rewrittenType != holderType) {
+        // TODO(b/261024278): If sharing this code also rewrite signature.
+        return new CfInvoke(
+            invoke.getOpcode(),
+            factory.createMethod(
+                rewrittenType, invoke.getMethod().getProto(), factory.createString(name)),
+            invoke.isInterface());
+      }
+      return instruction;
+    }
+
+    private CfFieldInstruction rewriteFieldInstruction(CfFieldInstruction instruction) {
+      DexType holderType = instruction.getField().getHolderType();
+      DexType rewrittenHolderType = typeMap.getOrDefault(holderType, holderType);
+      DexType fieldType = instruction.getField().getType();
+      DexType rewrittenType = typeMap.getOrDefault(fieldType, fieldType);
+      if (rewrittenHolderType != holderType || rewrittenType != fieldType) {
+        return instruction.createWithField(
+            factory.createField(rewrittenHolderType, rewrittenType, instruction.getField().name));
+      }
+      return instruction;
+    }
+
+    private CfInstruction rewriteTypeInstruction(CfTypeInstruction instruction) {
+      DexType rewrittenType = typeMap.getOrDefault(instruction.getType(), instruction.getType());
+      return rewrittenType != instruction.getType() ? instruction.withType(rewrittenType) : null;
+    }
+
+    private CfInstruction rewriteFrameInstruction(CfFrame instruction) {
+      return instruction.asFrame().mapReferenceTypes(type -> typeMap.getOrDefault(type, type));
+    }
+  }
+
+  @Override
+  protected CfCode getCode(String holderName, String methodName, CfCode code) {
+    if (methodName.endsWith("Stub")) {
+      // Don't include stubs targeted only for rewriting in the generated code.
+      return null;
+    }
+    if (!holderName.equals("DesugarVarHandle")
+        && !holderName.equals("DesugarMethodHandlesLookup")) {
+      throw new RuntimeException("Unexpected: " + holderName);
+    }
+    // Rewrite references to com.android.tools.r8.ir.desugar.varhandle.DesugarVarHandle to
+    // com.android.tools.r8.DesugarVarHandle and rewrite references to UnsafeStub to
+    // sun.misc.Unsafe.
+    InstructionTypeMapper instructionTypeMapper =
+        new InstructionTypeMapper(
+            ImmutableMap.of(
+                factory.createType(
+                    "L" + DesugarVarHandle.class.getTypeName().replace('.', '/') + ";"),
+                factory.desugarVarHandleType,
+                factory.createType(
+                    "L" + DesugarVarHandle.class.getTypeName().replace('.', '/') + "$UnsafeStub;"),
+                factory.unsafeType));
+    code.setInstructions(
+        code.getInstructions().stream()
+            .map(instructionTypeMapper::rewriteInstruction)
+            .collect(Collectors.toList()));
+    return code;
+  }
+
+  private DexEncodedMethod methodWithName(DexEncodedMethod method, String name) {
+    DexType holder = method.getHolderType();
+    DexType desugarVarHandle = factory.desugarVarHandleType;
+    DexType desugarVarHandleStub =
+        factory.createType("L" + DesugarVarHandle.class.getTypeName().replace('.', '/') + ";");
+    // Map methods to be on the final DesugarVarHandle class.
+    if (holder == desugarVarHandleStub) {
+      holder = desugarVarHandle;
+    }
+    DexProto proto = method.getProto();
+    if (proto.getReturnType() == desugarVarHandleStub) {
+      proto = factory.createProto(desugarVarHandle, proto.parameters);
+    }
+    return DexEncodedMethod.syntheticBuilder(method)
+        .setMethod(factory.createMethod(holder, proto, factory.createString(name)))
+        .build();
+  }
+
+  @Override
+  protected DexEncodedMethod mapMethod(DexEncodedMethod method) {
+    // Map VarHandle access mode methods to not have the Int/Long postfix.
+    for (String prefix : ImmutableList.of("get", "set", "compareAndSet")) {
+      if (method.getName().startsWith(prefix)) {
+        assert method.getName().toString().substring(prefix.length()).equals("Int")
+            || method.getName().toString().substring(prefix.length()).equals("Long")
+            || method.getName().toString().equals(prefix);
+        return methodWithName(method, prefix);
+      }
+    }
+    return methodWithName(method, method.getName().toString());
+  }
+
+  @Test
+  public void testVarHandleDesugaringGenerated() throws Exception {
+    ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());
+    sorted.sort(Comparator.comparing(Class::getTypeName));
+    assertEquals("Classes should be listed in sorted order", sorted, getMethodTemplateClasses());
+    assertEquals(
+        FileUtils.readTextFile(getGeneratedFile(), StandardCharsets.UTF_8), generateMethods());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new GenerateVarHandleMethods(null).generateMethodsAndWriteThemToFile();
+  }
+}
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 1e43777..7141ad08 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
@@ -53,8 +53,7 @@
         .addProgramClasses(I.class, A.class)
         .addProgramClassFileData(getTransformedMain())
         .addKeepMainRule(Main.class)
-        // Keep get() to prevent that we optimize it into having static return type A.
-        .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get(...); }")
+        .addKeepMethodRules(Reference.methodFromMethod(Main.class.getDeclaredMethod("get")))
         .addOptionsModification(
             options ->
                 options
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index 8983d39..4d2d2d2 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -191,7 +191,7 @@
     }
 
     @Override
-    public void addThrowingInstructionToPossiblyThrowingBlock(
+    public BasicBlock addThrowingInstructionToPossiblyThrowingBlock(
         IRCode code,
         ListIterator<BasicBlock> blockIterator,
         Instruction instruction,
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingLibraryBridgeVerticallyMergeTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingLibraryBridgeVerticallyMergeTest.java
new file mode 100644
index 0000000..786df4d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingLibraryBridgeVerticallyMergeTest.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.memberrebinding;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MemberRebindingLibraryBridgeVerticallyMergeTest extends TestBase {
+
+  private final String[] EXPECTED = new String[] {"Lib::foo", "B::foo"};
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(A.class, B.class, C.class, D.class, Main.class)
+        .addLibraryClasses(Lib.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClasses(A.class, B.class, C.class, D.class, Main.class)
+        .addLibraryClasses(Lib.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addBootClasspathClasses(Lib.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, B.class, C.class, D.class, Main.class)
+        .addLibraryClasses(Lib.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addKeepClassRules(C.class, D.class)
+        .enableInliningAnnotations()
+        .allowAccessModification()
+        .compile()
+        .addBootClasspathClasses(Lib.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  public static class Lib {
+
+    public void foo() {
+      System.out.println("Lib::foo");
+    }
+  }
+
+  public static class A extends Lib {}
+
+  public static class B extends A {
+
+    @Override
+    @NeverInline
+    public void foo() {
+      super.foo();
+      System.out.println("B::foo");
+    }
+  }
+
+  public static class C extends B {}
+
+  public static class D extends B {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      callFoo(System.currentTimeMillis() > 0 ? new C() : new B());
+    }
+
+    public static void callFoo(B b) {
+      b.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
index 83ea780..c544c9e 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -16,10 +16,12 @@
 import com.android.tools.r8.dex.code.DexConst4;
 import com.android.tools.r8.dex.code.DexConstClass;
 import com.android.tools.r8.dex.code.DexConstString;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
 import com.android.tools.r8.dex.code.DexInvokeDirect;
 import com.android.tools.r8.dex.code.DexInvokeStatic;
 import com.android.tools.r8.dex.code.DexInvokeVirtual;
 import com.android.tools.r8.dex.code.DexIputObject;
+import com.android.tools.r8.dex.code.DexMoveResultObject;
 import com.android.tools.r8.dex.code.DexNewArray;
 import com.android.tools.r8.dex.code.DexReturnVoid;
 import com.android.tools.r8.dex.code.DexSgetObject;
@@ -639,7 +641,8 @@
             + "}",
         "-keep class " + CLASS_NAME,
         "-keep class R { *; }");
-    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
+    CodeInspector inspector =
+        compileWithR8(builder, testBuilder -> testBuilder.addKeepRules(pgConfigs)).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -647,19 +650,33 @@
     assertNotNull(method);
 
     DexCode code = method.getCode().asDexCode();
-    checkInstructions(
-        code,
-        ImmutableList.of(
-            DexInvokeDirect.class,
-            DexConst4.class,
-            DexNewArray.class,
-            DexConst4.class,
-            DexConstClass.class,
-            DexAputObject.class,
-            DexConstString.class,
-            DexInvokeStatic.class,
-            DexReturnVoid.class));
-    DexConstString constString = (DexConstString) code.instructions[6];
+    // Accept either array construction style (differs based on minSdkVersion).
+    if (code.instructions[2].getClass() == DexFilledNewArray.class) {
+      checkInstructions(
+          code,
+          ImmutableList.of(
+              DexInvokeDirect.class,
+              DexConstClass.class,
+              DexFilledNewArray.class,
+              DexMoveResultObject.class,
+              DexConstString.class,
+              DexInvokeStatic.class,
+              DexReturnVoid.class));
+    } else {
+      checkInstructions(
+          code,
+          ImmutableList.of(
+              DexInvokeDirect.class,
+              DexConst4.class,
+              DexNewArray.class,
+              DexConst4.class,
+              DexConstClass.class,
+              DexAputObject.class,
+              DexConstString.class,
+              DexInvokeStatic.class,
+              DexReturnVoid.class));
+    }
+    DexConstString constString = (DexConstString) code.instructions[code.instructions.length - 3];
     assertEquals("foo", constString.getString().toString());
   }
 
@@ -700,7 +717,8 @@
             + "}",
         "-keep class " + CLASS_NAME,
         "-keep,allowobfuscation class R { *; }");
-    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
+    CodeInspector inspector =
+        compileWithR8(builder, testBuilder -> testBuilder.addKeepRules(pgConfigs)).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -708,19 +726,33 @@
     assertNotNull(method);
 
     DexCode code = method.getCode().asDexCode();
-    checkInstructions(
-        code,
-        ImmutableList.of(
-            DexInvokeDirect.class,
-            DexConst4.class,
-            DexNewArray.class,
-            DexConst4.class,
-            DexConstClass.class,
-            DexAputObject.class,
-            DexConstString.class,
-            DexInvokeStatic.class,
-            DexReturnVoid.class));
-    DexConstString constString = (DexConstString) code.instructions[6];
+    // Accept either array construction style (differs based on minSdkVersion).
+    if (code.instructions[2].getClass() == DexFilledNewArray.class) {
+      checkInstructions(
+          code,
+          ImmutableList.of(
+              DexInvokeDirect.class,
+              DexConstClass.class,
+              DexFilledNewArray.class,
+              DexMoveResultObject.class,
+              DexConstString.class,
+              DexInvokeStatic.class,
+              DexReturnVoid.class));
+    } else {
+      checkInstructions(
+          code,
+          ImmutableList.of(
+              DexInvokeDirect.class,
+              DexConst4.class,
+              DexNewArray.class,
+              DexConst4.class,
+              DexConstClass.class,
+              DexAputObject.class,
+              DexConstString.class,
+              DexInvokeStatic.class,
+              DexReturnVoid.class));
+    }
+    DexConstString constString = (DexConstString) code.instructions[code.instructions.length - 3];
     assertNotEquals("foo", constString.getString().toString());
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java
index 10fec23..5475557 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java
@@ -76,10 +76,6 @@
             .assertFailure();
 
     // Extract actual stack trace and retraced stack trace from failed run result.
-    // TODO(122940268): Remove test code when fixed.
-    System.out.println("<--- TEST RESULT START --->");
-    System.out.println(result);
-    System.out.println("<--- TEST RESULT END --->");
     StackTrace actualStackTrace = StackTrace.extractFromArt(result.getStdErr());
     StackTrace retracedStackTrace =
         actualStackTrace.retrace(result.proguardMap(), temp.newFolder().toPath());
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
index 5403673..7e6306d 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
@@ -129,26 +129,6 @@
         compileResult -> setSyntheticMethod(compileResult, syntheticMethod));
   }
 
-  @Test
-  public void testNoLineNumberTable() throws Exception {
-    assumeTrue(compat);
-    assumeTrue(parameters.isDexRuntime());
-    Box<MethodSubject> syntheticMethod = new Box<>();
-    runTest(
-        ImmutableList.of(),
-        (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
-          StackTrace reprocessedStackTrace =
-              retracedStackTrace.filter(
-                  stackTraceLine -> filterSynthesizedMethod(stackTraceLine, syntheticMethod.get()));
-          assertThat(
-              reprocessedStackTrace.filter(this::isNotDalvikNativeStartMethod),
-              isSameExceptForFileNameAndLineNumber(
-                  expectedStackTrace.filter(this::isNotDalvikNativeStartMethod)));
-          assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
-        },
-        compileResult -> setSyntheticMethod(compileResult, syntheticMethod));
-  }
-
   private void setSyntheticMethod(
       R8TestCompileResult compileResult, Box<MethodSubject> syntheticMethod) throws IOException {
     compileResult.inspect(
diff --git a/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java b/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java
index d01d917..1dab89f 100644
--- a/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java
+++ b/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java
@@ -6,7 +6,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceCompanionWithPreambleTest.java b/src/test/java/com/android/tools/r8/retrace/RetraceCompanionWithPreambleTest.java
new file mode 100644
index 0000000..12eff52
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceCompanionWithPreambleTest.java
@@ -0,0 +1,153 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileNameAndLineNumber;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.transformers.ClassFileTransformer.LineTranslation;
+import com.android.tools.r8.transformers.MethodTransformer.MethodContext;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RetraceCompanionWithPreambleTest extends TestBase {
+
+  public enum Preamble {
+    NONE,
+    ONLY,
+    BOTH
+  }
+
+  @Parameters(name = "{0}, preamble:{1}")
+  public static List<Object[]> parameters() {
+    return buildParameters(
+        getTestParameters()
+            .withDefaultRuntimes()
+            .withApiLevel(AndroidApiLevel.B)
+            .enableApiLevelsForCf()
+            .build(),
+        Preamble.values());
+  }
+
+  private final TestParameters parameters;
+  private final Preamble preamble;
+
+  public RetraceCompanionWithPreambleTest(TestParameters parameters, Preamble preamble) {
+    this.parameters = parameters;
+    this.preamble = preamble;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(Main.class, A.class)
+        .addProgramClassFileData(getI())
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkRunResult)
+        .inspectStackTrace(RetraceCompanionWithPreambleTest::checkExpectedStackTrace);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .internalEnableMappingOutput()
+        .addProgramClasses(Main.class, A.class)
+        .addProgramClassFileData(getI())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkRunResult)
+        .inspectStackTrace(RetraceCompanionWithPreambleTest::checkExpectedStackTrace);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, A.class)
+        .addProgramClassFileData(getI())
+        .addKeepMainRule(Main.class)
+        .addKeepAttributeSourceFile()
+        .addKeepAttributeLineNumberTable()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkRunResult)
+        .inspectStackTrace(RetraceCompanionWithPreambleTest::checkExpectedStackTrace);
+  }
+
+  private void checkRunResult(SingleTestRunResult<?> runResult) {
+    runResult.assertFailureWithErrorThatMatches(containsString("Throw It!"));
+  }
+
+  private static void checkExpectedStackTrace(StackTrace stackTrace) {
+    assertThat(
+        stackTrace,
+        isSameExceptForFileNameAndLineNumber(
+            StackTrace.builder()
+                .addWithoutFileNameAndLineNumber(Main.class, "doThrow")
+                .addWithoutFileNameAndLineNumber(I.class, "foo")
+                .addWithoutFileNameAndLineNumber(Main.class, "main")
+                .build()));
+  }
+
+  private byte[] getI() throws IOException {
+    return transformer(I.class)
+        .setPredictiveLineNumbering(
+            new LineTranslation() {
+              boolean firstLine = true;
+
+              @Override
+              public int translate(MethodContext context, int line) {
+                if (!context.getReference().getMethodName().equals("foo")) {
+                  return line;
+                }
+                if (preamble == Preamble.NONE) {
+                  return line;
+                }
+                if (preamble == Preamble.ONLY) {
+                  return -1;
+                }
+                if (firstLine) {
+                  firstLine = false;
+                  return -1;
+                }
+                return line;
+              }
+            })
+        .transform();
+  }
+
+  interface I {
+
+    default void foo() {
+      Main.doThrow();
+      System.out.println("more stuff");
+    }
+  }
+
+  static class A implements I {}
+
+  public static class Main {
+
+    public static void doThrow() {
+      throw new RuntimeException("Throw It!");
+    }
+
+    public static void main(String[] args) {
+      new A().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
index 4a76a6a..513dc73 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
@@ -6,6 +6,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.graph.AppView;
@@ -15,6 +16,7 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -33,7 +35,7 @@
     this.parameters = parameters;
   }
 
-  private static final String[] expectedOutput = {"3"};
+  private static final String[] expectedOutput = {"3", "2"};
 
   @Test
   public void d8() throws Exception {
@@ -44,7 +46,7 @@
         .addOptionsModification(opt -> opt.testing.irModifier = this::transformArray)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(expectedOutput)
-        .inspect(this::assertNoArrayLength);
+        .inspect(i -> inspect(i, true));
   }
 
   @Test
@@ -54,32 +56,52 @@
         .addProgramClasses(Main.class)
         .addOptionsModification(opt -> opt.testing.irModifier = this::transformArray)
         .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(expectedOutput)
-        .inspect(this::assertNoArrayLength);
+        .inspect(i -> inspect(i, false));
   }
 
   private void transformArray(IRCode irCode, AppView<?> appView) {
-    if (irCode.context().getReference().getName().toString().contains("main")) {
       new CodeRewriter(appView).simplifyArrayConstruction(irCode);
+    String name = irCode.context().getReference().getName().toString();
+    if (name.contains("filledArrayData")) {
       assertTrue(irCode.streamInstructions().anyMatch(Instruction::isNewArrayFilledData));
+    } else if (name.contains("filledNewArray")) {
+      assertTrue(irCode.streamInstructions().anyMatch(Instruction::isInvokeNewArray));
     }
   }
 
-  private void assertNoArrayLength(CodeInspector inspector) {
+  private void inspect(CodeInspector inspector, boolean d8) {
     ClassSubject mainClass = inspector.clazz(Main.class);
     assertTrue(mainClass.isPresent());
-    assertTrue(
-        mainClass.mainMethod().streamInstructions().noneMatch(InstructionSubject::isArrayLength));
+    MethodSubject filledArrayData = mainClass.uniqueMethodWithOriginalName("filledArrayData");
+    assertTrue(filledArrayData.streamInstructions().noneMatch(InstructionSubject::isArrayLength));
+    if (!d8) {
+      MethodSubject filledNewArray = mainClass.uniqueMethodWithOriginalName("filledNewArray");
+      assertTrue(filledNewArray.streamInstructions().noneMatch(InstructionSubject::isArrayLength));
+    }
   }
 
   public static final class Main {
+    @NeverInline
+    public static void filledArrayData() {
+      short[] values = new short[3];
+      values[0] = 5;
+      values[1] = 6;
+      values[2] = 1;
+      System.out.println(values.length);
+    }
+
+    @NeverInline
+    public static void filledNewArray() {
+      int[] values = new int[] {7, 8};
+      System.out.println(values.length);
+    }
+
     public static void main(String[] args) {
-      int[] ints = new int[3];
-      ints[0] = 5;
-      ints[1] = 6;
-      ints[2] = 1;
-      System.out.println(ints.length);
+      filledArrayData();
+      filledNewArray();
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/FilledArrayDataWithCatchHandlerTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/FilledArrayDataWithCatchHandlerTest.java
new file mode 100644
index 0000000..3e04138
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/FilledArrayDataWithCatchHandlerTest.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.rewrite.arrays;
+
+import static com.android.tools.r8.cf.methodhandles.fields.ClassFieldMethodHandleTest.Main.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.code.DexFillArrayData;
+import com.android.tools.r8.dex.code.DexNewArray;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionOffsetSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class FilledArrayDataWithCatchHandlerTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("1");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public FilledArrayDataWithCatchHandlerTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(FilledArrayDataWithCatchHandlerTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testReleaseD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .release()
+        .setMinApi(parameters.getApiLevel())
+        .addInnerClasses(FilledArrayDataWithCatchHandlerTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkInstructions);
+  }
+
+  private void checkInstructions(CodeInspector inspector) {
+    MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+    List<InstructionSubject> newArrays =
+        foo.streamInstructions()
+            .filter(i -> i.asDexInstruction().getInstruction() instanceof DexNewArray)
+            .collect(Collectors.toList());
+    assertEquals(1, newArrays.size());
+
+    List<InstructionSubject> fillArrays =
+        foo.streamInstructions()
+            .filter(i -> i.asDexInstruction().getInstruction() instanceof DexFillArrayData)
+            .collect(Collectors.toList());
+    assertEquals(1, fillArrays.size());
+
+    InstructionOffsetSubject offsetNew = newArrays.get(0).getOffset(foo);
+    InstructionOffsetSubject offsetFill = newArrays.get(0).getOffset(foo);
+    assertTrue(
+        foo.streamTryCatches()
+            .allMatch(r -> r.getRange().includes(offsetNew) && r.getRange().includes(offsetFill)));
+  }
+
+  static class TestClass {
+
+    public static int foo() {
+      int value = 1;
+      int[] array = null;
+      try {
+        array = new int[6];
+      } catch (RuntimeException e) {
+        return array[0];
+      }
+      array[0] = value;
+      array[1] = value;
+      array[2] = value;
+      array[3] = value;
+      array[4] = value;
+      array[5] = value;
+      return array[5];
+    }
+
+    public static void main(String[] args) {
+      System.out.println(foo());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java
index 8cc0379..0003703 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java
@@ -3,17 +3,25 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.rewrite.arrays;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionOffsetSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
-// Regression test for issue found in b/259986613
 @RunWith(Parameterized.class)
 public class NewArrayInCatchRangeTest extends TestBase {
 
@@ -46,19 +54,32 @@
         .setMinApi(parameters.getApiLevel())
         .addInnerClasses(NewArrayInCatchRangeTest.class)
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(EXPECTED);
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkInstructions);
+  }
+
+  private void checkInstructions(CodeInspector inspector) {
+    MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+    List<InstructionSubject> filledArrayInstructions =
+        foo.streamInstructions()
+            .filter(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray)
+            .collect(Collectors.toList());
+    assertEquals(1, filledArrayInstructions.size());
+    InstructionOffsetSubject offset = filledArrayInstructions.get(0).getOffset(foo);
+    assertTrue(foo.streamTryCatches().allMatch(r -> r.getRange().includes(offset)));
   }
 
   static class TestClass {
 
     public static int foo() {
       int value = 1;
-      int[] array = new int[1];
+      int[] array = null;
       try {
-        array[0] = value;
-      } catch (RuntimeException e) {
-        return array[0];
+        array = new int[1];
+      } catch (Exception e) {
+        return array == null ? -1 : array.length;
       }
+      array[0] = value;
       return array[0];
     }
 
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInSameCatchRangeTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInSameCatchRangeTest.java
new file mode 100644
index 0000000..5ad8f6b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInSameCatchRangeTest.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.rewrite.arrays;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NewArrayInSameCatchRangeTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("1");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public NewArrayInSameCatchRangeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(NewArrayInSameCatchRangeTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testReleaseD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .release()
+        .setMinApi(parameters.getApiLevel())
+        .addInnerClasses(NewArrayInSameCatchRangeTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkHasFilledNewArray);
+  }
+
+  private void checkHasFilledNewArray(CodeInspector inspector) {
+    MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+    assertTrue(
+        foo.streamInstructions()
+            .anyMatch(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray));
+  }
+
+  static class TestClass {
+
+    public static int foo() {
+      int value = 1;
+      try {
+        int[] array = new int[2];
+        array[0] = value;
+        array[1] = value;
+        return System.nanoTime() > 0 ? array[0] : 2;
+      } catch (RuntimeException e) {
+        return 42;
+      }
+    }
+
+    public static void main(String[] args) {
+      System.out.println(foo());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java
index ab11b3d..3996647 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java
@@ -3,12 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.rewrite.arrays;
 
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -45,7 +49,15 @@
         .setMinApi(parameters.getApiLevel())
         .addInnerClasses(NewArrayInTwoCatchRangesTest.class)
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(EXPECTED);
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkHasFilledNewArray);
+  }
+
+  private void checkHasFilledNewArray(CodeInspector inspector) {
+    MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+    assertTrue(
+        foo.streamInstructions()
+            .anyMatch(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayMonitorTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayMonitorTest.java
new file mode 100644
index 0000000..40d1557
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayMonitorTest.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.rewrite.arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionOffsetSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NewArrayMonitorTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("1");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public NewArrayMonitorTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(NewArrayMonitorTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testReleaseD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .release()
+        .setMinApi(parameters.getApiLevel())
+        .addInnerClasses(NewArrayMonitorTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkInstructions);
+  }
+
+  private void checkInstructions(CodeInspector inspector) {
+    MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+    List<InstructionSubject> filledArrayInstructions =
+        foo.streamInstructions()
+            .filter(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray)
+            .collect(Collectors.toList());
+    assertEquals(1, filledArrayInstructions.size());
+    InstructionOffsetSubject offset = filledArrayInstructions.get(0).getOffset(foo);
+    assertTrue(foo.streamTryCatches().allMatch(r -> r.getRange().includes(offset)));
+  }
+
+  static class TestClass {
+
+    public static synchronized int foo() {
+      int value = 1;
+      int[] array = new int[1];
+      try {
+        array[0] = value;
+      } catch (RuntimeException e) {
+        return array[0];
+      }
+      return array[0];
+    }
+
+    public static void main(String[] args) {
+      System.out.println(foo());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayPutInCatchRangeTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayPutInCatchRangeTest.java
new file mode 100644
index 0000000..d7dc866
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayPutInCatchRangeTest.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.rewrite.arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Collections;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+// Regression test for issue found in b/259986613
+@RunWith(Parameterized.class)
+public class NewArrayPutInCatchRangeTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("1");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public NewArrayPutInCatchRangeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(NewArrayPutInCatchRangeTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testReleaseD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .release()
+        .setMinApi(parameters.getApiLevel())
+        .addInnerClasses(NewArrayPutInCatchRangeTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkHasFilledNewArray);
+  }
+
+  private void checkHasFilledNewArray(CodeInspector inspector) {
+    MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+    assertTrue(
+        foo.streamInstructions()
+            .anyMatch(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray));
+    assertEquals(Collections.emptyList(), foo.streamTryCatches().collect(Collectors.toList()));
+  }
+
+  static class TestClass {
+
+    public static int foo() {
+      int value = 1;
+      int[] array = new int[1];
+      try {
+        array[0] = value;
+      } catch (RuntimeException e) {
+        return array[0];
+      }
+      return array[0];
+    }
+
+    public static void main(String[] args) {
+      System.out.println(foo());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArraySynchronizedBlockTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArraySynchronizedBlockTest.java
new file mode 100644
index 0000000..3319ccc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArraySynchronizedBlockTest.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.rewrite.arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NewArraySynchronizedBlockTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("1");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public NewArraySynchronizedBlockTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(NewArraySynchronizedBlockTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testReleaseD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .release()
+        .setMinApi(parameters.getApiLevel())
+        .addInnerClasses(NewArraySynchronizedBlockTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkInstructions);
+  }
+
+  private void checkInstructions(CodeInspector inspector) {
+    MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+    List<InstructionSubject> filledArrayInstructions =
+        foo.streamInstructions()
+            .filter(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray)
+            .collect(Collectors.toList());
+    assertEquals(0, filledArrayInstructions.size());
+  }
+
+  static class TestClass {
+
+    public static int foo() {
+      int value = 1;
+      int[] array;
+      synchronized (TestClass.class) {
+        array = new int[1];
+      } // monitor exit here prohibits optimization as its failure could observe the lack of init.
+      array[0] = value;
+      return array[0];
+    }
+
+    public static void main(String[] args) {
+      System.out.println(foo());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java
new file mode 100644
index 0000000..611ed3f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java
@@ -0,0 +1,774 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.rewrite.arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.dex.code.DexFillArrayData;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
+import com.android.tools.r8.dex.code.DexFilledNewArrayRange;
+import com.android.tools.r8.dex.code.DexNewArray;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.beust.jcommander.internal.Lists;
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+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 SimplifyArrayConstructionTest extends TestBase {
+  @Parameters(name = "{0}, mode = {1}")
+  public static Iterable<?> data() {
+    return buildParameters(
+        getTestParameters().withDefaultCfRuntime().withDexRuntimesAndAllApiLevels().build(),
+        CompilationMode.values());
+  }
+
+  private final TestParameters parameters;
+  private final CompilationMode compilationMode;
+
+  public SimplifyArrayConstructionTest(TestParameters parameters, CompilationMode compilationMode) {
+    this.parameters = parameters;
+    this.compilationMode = compilationMode;
+  }
+
+  private static final Class<?>[] DEX_ARRAY_INSTRUCTIONS = {
+    DexNewArray.class, DexFilledNewArray.class, DexFilledNewArrayRange.class, DexFillArrayData.class
+  };
+
+  private static final String[] EXPECTED_OUTPUT = {
+    "[a]",
+    "[a, 1, null]",
+    "[1, null]",
+    "[1, null, 2]",
+    "[1, null, 2]",
+    "[1]",
+    "[1, 2]",
+    "[1, 2, 3, 4, 5]",
+    "[1]",
+    "[a, 1, null, d, e, f]",
+    "[1, null, 3, null, null, 6]",
+    "[1, 2, 3, 4, 5, 6]",
+    "[true, false]",
+    "[1, 2]",
+    "[1, 2]",
+    "[1, 2]",
+    "[1.0, 2.0]",
+    "[1.0, 2.0]",
+    "[]",
+    "[]",
+    "[true]",
+    "[1]",
+    "[1]",
+    "[1]",
+    "[1.0]",
+    "[1.0]",
+    "[0, 1]",
+    "[1, null]",
+    "[a]",
+    "[0, 1]",
+    "200",
+    "[0, 1, 2, 3, 4]",
+    "[4, 0, 0, 0, 0]",
+    "[4, 1, 2, 3, 4]",
+    "[0, 1, 2]",
+    "[0]",
+    "[0, 1, 2]",
+    "[1, 2, 3]",
+    "[1, 2, 3, 4, 5, 6]",
+    "[0]",
+    "[null, null]",
+  };
+
+  private static final byte[] TRANSFORMED_MAIN = transformedMain();
+
+  @Test
+  public void testRuntime() throws Exception {
+    assumeFalse(compilationMode == CompilationMode.DEBUG);
+    testForRuntime(
+            parameters.getRuntime(),
+            d8TestBuilder ->
+                d8TestBuilder.setMinApi(parameters.getApiLevel()).setMode(compilationMode))
+        .addProgramClassFileData(TRANSFORMED_MAIN)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED_OUTPUT)
+        .inspect(inspector -> inspect(inspector, false));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(
+            options ->
+                options
+                    .getOpenClosedInterfacesOptions()
+                    .suppressSingleOpenInterface(Reference.classFromClass(Serializable.class)))
+        .setMode(compilationMode)
+        .addProgramClassFileData(TRANSFORMED_MAIN)
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .addKeepAnnotation()
+        .addKeepRules("-keepclassmembers class * { @com.android.tools.r8.Keep *; }")
+        .addKeepRules("-assumenosideeffects class * { *** assumedNullField return null; }")
+        .addKeepRules("-assumenosideeffects class * { *** assumedNonNullField return _NONNULL_; }")
+        .addDontObfuscate()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED_OUTPUT)
+        .inspect(inspector -> inspect(inspector, true));
+  }
+
+  private static byte[] transformedMain() {
+    try {
+      return transformer(Main.class)
+          .transformMethodInsnInMethod(
+              "interfaceArrayWithRawObject",
+              (opcode, owner, name, descriptor, isInterface, visitor) -> {
+                if (name.equals("getObjectThatImplementsSerializable")) {
+                  visitor.visitMethodInsn(opcode, owner, name, "()Ljava/lang/Object;", isInterface);
+                } else {
+                  visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+                }
+              })
+          .setReturnType(
+              ClassFileTransformer.MethodPredicate.onName("getObjectThatImplementsSerializable"),
+              Object.class.getTypeName())
+          .transform();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private void inspect(CodeInspector inspector, boolean isR8) {
+    if (parameters.isCfRuntime()) {
+      return;
+    }
+    ClassSubject mainClass = inspector.clazz(Main.class);
+    assertTrue(mainClass.isPresent());
+
+    MethodSubject stringArrays = mainClass.uniqueMethodWithOriginalName("stringArrays");
+    MethodSubject referenceArraysNoCasts =
+        mainClass.uniqueMethodWithOriginalName("referenceArraysNoCasts");
+    MethodSubject referenceArraysWithSubclasses =
+        mainClass.uniqueMethodWithOriginalName("referenceArraysWithSubclasses");
+    MethodSubject interfaceArrayWithRawObject =
+        mainClass.uniqueMethodWithOriginalName("interfaceArrayWithRawObject");
+
+    MethodSubject phiFilledNewArray = mainClass.uniqueMethodWithOriginalName("phiFilledNewArray");
+    MethodSubject intsThatUseFilledNewArray =
+        mainClass.uniqueMethodWithOriginalName("intsThatUseFilledNewArray");
+    MethodSubject twoDimensionalArrays =
+        mainClass.uniqueMethodWithOriginalName("twoDimensionalArrays");
+    MethodSubject objectArraysFilledNewArrayRange =
+        mainClass.uniqueMethodWithOriginalName("objectArraysFilledNewArrayRange");
+    MethodSubject arraysThatUseFilledData =
+        mainClass.uniqueMethodWithOriginalName("arraysThatUseFilledData");
+    MethodSubject arraysThatUseNewArrayEmpty =
+        mainClass.uniqueMethodWithOriginalName("arraysThatUseNewArrayEmpty");
+    MethodSubject reversedArray = mainClass.uniqueMethodWithOriginalName("reversedArray");
+    MethodSubject arrayWithCorrectCountButIncompleteCoverage =
+        mainClass.uniqueMethodWithOriginalName("arrayWithCorrectCountButIncompleteCoverage");
+    MethodSubject arrayWithExtraInitialPuts =
+        mainClass.uniqueMethodWithOriginalName("arrayWithExtraInitialPuts");
+    MethodSubject catchHandlerThrowing =
+        mainClass.uniqueMethodWithOriginalName("catchHandlerThrowing");
+    MethodSubject catchHandlerNonThrowingFilledNewArray =
+        mainClass.uniqueMethodWithOriginalName("catchHandlerNonThrowingFilledNewArray");
+    MethodSubject catchHandlerNonThrowingFillArrayData =
+        mainClass.uniqueMethodWithOriginalName("catchHandlerNonThrowingFillArrayData");
+    MethodSubject assumedValues = mainClass.uniqueMethodWithOriginalName("assumedValues");
+
+    assertArrayTypes(arraysThatUseNewArrayEmpty, DexNewArray.class);
+    assertArrayTypes(intsThatUseFilledNewArray, DexFilledNewArray.class);
+    assertFilledArrayData(arraysThatUseFilledData);
+    assertFilledArrayData(catchHandlerNonThrowingFillArrayData);
+
+    if (compilationMode == CompilationMode.DEBUG) {
+      // The explicit assignments can't be collapsed without breaking the debugger's ability to
+      // visit each line.
+      assertArrayTypes(reversedArray, DexNewArray.class);
+    } else {
+      assertArrayTypes(reversedArray, DexFilledNewArray.class);
+    }
+
+    // Cannot use filled-new-array of String before K.
+    if (parameters.getApiLevel().isLessThan(AndroidApiLevel.K)) {
+      assertArrayTypes(stringArrays, DexNewArray.class);
+    } else {
+      assertArrayTypes(stringArrays, DexFilledNewArray.class);
+    }
+    // Cannot use filled-new-array of Object before L.
+    if (parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) {
+      assertArrayTypes(referenceArraysNoCasts, DexNewArray.class);
+      assertArrayTypes(referenceArraysWithSubclasses, DexNewArray.class);
+      assertArrayTypes(phiFilledNewArray, DexNewArray.class);
+      assertArrayTypes(objectArraysFilledNewArrayRange, DexNewArray.class);
+      assertArrayTypes(twoDimensionalArrays, DexNewArray.class);
+      assertArrayTypes(assumedValues, DexNewArray.class);
+    } else {
+      assertArrayTypes(referenceArraysNoCasts, DexFilledNewArray.class);
+      // TODO(b/246971330): Add support for arrays with subtypes.
+      if (isR8) {
+        assertArrayTypes(referenceArraysWithSubclasses, DexFilledNewArray.class);
+      } else {
+        assertArrayTypes(referenceArraysWithSubclasses, DexNewArray.class);
+      }
+
+      // TODO(b/246971330): Add support for arrays whose values have conditionals.
+      // assertArrayTypes(phiFilledNewArray, DexFilledNewArray.class);
+
+      assertArrayTypes(objectArraysFilledNewArrayRange, DexFilledNewArrayRange.class);
+
+      if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L)) {
+        assertArrayTypes(twoDimensionalArrays, DexFilledNewArray.class);
+      } else {
+        // No need to assert this case. If it's wrong, Dalvik verify errors cause test failures.
+      }
+
+      assertArrayTypes(assumedValues, DexFilledNewArray.class);
+    }
+    // filled-new-array fails verification when types of parameters are not subclasses (aput-object
+    // does not).
+    assertArrayTypes(interfaceArrayWithRawObject, DexNewArray.class);
+
+    // These could be optimized to use InvokeNewArray, but they seem like rare code patterns so we
+    // haven't bothered.
+    assertArrayTypes(arrayWithExtraInitialPuts, DexNewArray.class);
+    assertArrayTypes(arrayWithCorrectCountButIncompleteCoverage, DexNewArray.class);
+
+    assertArrayTypes(catchHandlerThrowing, DexNewArray.class);
+    assertArrayTypes(catchHandlerNonThrowingFilledNewArray, DexFilledNewArray.class);
+  }
+
+  private static Predicate<InstructionSubject> isInstruction(List<Class<?>> allowlist) {
+    return (ins) -> allowlist.contains(ins.asDexInstruction().getInstruction().getClass());
+  }
+
+  private static Predicate<InstructionSubject> isInstruction(Class<?> clazz) {
+    return isInstruction(Arrays.asList(clazz));
+  }
+
+  private static void assertArrayTypes(MethodSubject method, Class<?> allowedArrayInst) {
+    assertTrue(method.isPresent());
+    List<Class<?>> disallowedClasses = Lists.newArrayList(DEX_ARRAY_INSTRUCTIONS);
+    disallowedClasses.remove(allowedArrayInst);
+
+    assertTrue(method.streamInstructions().anyMatch(isInstruction(allowedArrayInst)));
+    assertTrue(method.streamInstructions().noneMatch(isInstruction(disallowedClasses)));
+  }
+
+  private static void assertFilledArrayData(MethodSubject method) {
+    assertTrue(method.isPresent());
+    List<Class<?>> disallowedClasses = Lists.newArrayList(DEX_ARRAY_INSTRUCTIONS);
+    disallowedClasses.remove(DexFillArrayData.class);
+    disallowedClasses.remove(DexNewArray.class);
+
+    assertTrue(method.streamInstructions().noneMatch(isInstruction(disallowedClasses)));
+    assertTrue(method.streamInstructions().noneMatch(InstructionSubject::isArrayPut));
+    long numNewArray = method.streamInstructions().filter(InstructionSubject::isNewArray).count();
+    long numFillArray =
+        method.streamInstructions().filter(isInstruction(DexFillArrayData.class)).count();
+    assertEquals(numNewArray, numFillArray);
+  }
+
+  public static final class Main {
+    static final String assumedNonNullField = null;
+    static final String assumedNullField = null;
+
+    public static void main(String[] args) {
+      stringArrays();
+      referenceArraysNoCasts();
+      referenceArraysWithSubclasses();
+      interfaceArrayWithRawObject();
+      phiFilledNewArray();
+      intsThatUseFilledNewArray();
+      twoDimensionalArrays();
+      objectArraysFilledNewArrayRange();
+      arraysThatUseFilledData();
+      arraysThatUseNewArrayEmpty();
+      reversedArray();
+      arrayWithCorrectCountButIncompleteCoverage();
+      arrayWithExtraInitialPuts();
+      catchHandlerThrowing();
+      catchHandlerNonThrowingFilledNewArray();
+      catchHandlerNonThrowingFillArrayData();
+      arrayIntoAnotherArray();
+      assumedValues();
+    }
+
+    @NeverInline
+    private static void stringArrays() {
+      // Test exact class, no null.
+      String[] stringArr = {"a"};
+      System.out.println(Arrays.toString(stringArr));
+    }
+
+    @NeverInline
+    private static void referenceArraysNoCasts() {
+      // Tests that no type info is needed when array type is Object[].
+      Object[] objectArr = {"a", 1, null};
+      System.out.println(Arrays.toString(objectArr));
+      // Test that interface arrays work when we have the exact interface already.
+      Serializable[] interfaceArr = {getSerializable(1), null};
+      System.out.println(Arrays.toString(interfaceArr));
+    }
+
+    @Keep
+    private static Serializable getSerializable(Integer value) {
+      return value;
+    }
+
+    @NeverInline
+    private static void referenceArraysWithSubclasses() {
+      Serializable[] interfaceArr = {1, null, 2};
+      System.out.println(Arrays.toString(interfaceArr));
+      Number[] objArray = {1, null, 2};
+      System.out.println(Arrays.toString(objArray));
+    }
+
+    @NeverInline
+    private static void interfaceArrayWithRawObject() {
+      // Interfaces can use filled-new-array only when we know types implement the interface.
+      Serializable[] arr = new Serializable[1];
+      // Transformed from `I get()` to `Object get()`.
+      arr[0] = getObjectThatImplementsSerializable();
+      System.out.println(Arrays.toString(arr));
+    }
+
+    @Keep
+    private static /*Object*/ Serializable getObjectThatImplementsSerializable() {
+      return 1;
+    }
+
+    @NeverInline
+    private static void reversedArray() {
+      int[] arr = new int[5];
+      arr[4] = 4;
+      arr[3] = 3;
+      arr[2] = 2;
+      arr[1] = 1;
+      arr[0] = 0;
+      System.out.println(Arrays.toString(arr));
+    }
+
+    @NeverInline
+    private static void arrayWithCorrectCountButIncompleteCoverage() {
+      int[] arr = new int[5];
+      arr[0] = 0;
+      arr[0] = 1;
+      arr[0] = 2;
+      arr[0] = 3;
+      arr[0] = 4;
+      System.out.println(Arrays.toString(arr));
+    }
+
+    @NeverInline
+    private static void arrayWithExtraInitialPuts() {
+      int[] arr = new int[5];
+      arr[0] = 0;
+      arr[0] = 1;
+      arr[0] = 2;
+      arr[0] = 3;
+      arr[0] = 4;
+      arr[1] = 1;
+      arr[2] = 2;
+      arr[3] = 3;
+      arr[4] = 4;
+      System.out.println(Arrays.toString(arr));
+    }
+
+    @NeverInline
+    private static void catchHandlerNonThrowingFilledNewArray() {
+      try {
+        int[] arr1 = {1, 2, 3};
+        System.currentTimeMillis();
+        System.out.println(Arrays.toString(arr1));
+      } catch (Throwable t) {
+        throw new RuntimeException(t);
+      }
+    }
+
+    @NeverInline
+    private static void catchHandlerNonThrowingFillArrayData() {
+      try {
+        int[] arr = {1, 2, 3, 4, 5, 6};
+        System.currentTimeMillis();
+        System.out.println(Arrays.toString(arr));
+      } catch (Throwable t) {
+        throw new RuntimeException(t);
+      }
+    }
+
+    @NeverInline
+    private static void catchHandlerThrowing() {
+      int[] arr1 = new int[3];
+      arr1[0] = 0;
+      arr1[1] = 1;
+      // Since the array is used in only one spot, and that spot is not within the try/catch, it
+      // should be safe to use filled-new-array, but we don't.
+      try {
+        System.currentTimeMillis();
+        arr1[2] = 2;
+      } catch (Throwable t) {
+        throw new RuntimeException(t);
+      }
+      System.out.println(Arrays.toString(arr1));
+
+      try {
+        // Test filled-new-array with a throwing instruction before the last array-put.
+        int[] arr2 = new int[1];
+        System.currentTimeMillis();
+        arr2[0] = 0;
+        System.out.println(Arrays.toString(arr2));
+
+        // Test filled-array-data with a throwing instruction before the last array-put.
+        short[] arr3 = new short[3];
+        arr3[0] = 0;
+        arr3[1] = 1;
+        System.currentTimeMillis();
+        arr3[2] = 2;
+        System.out.println(Arrays.toString(arr3));
+      } catch (Throwable t) {
+        throw new RuntimeException(t);
+      }
+    }
+
+    @NeverInline
+    private static void arrayIntoAnotherArray() {
+      // Tests that our computeValues() method does not assume all array-put-object users have
+      // the array as the target.
+      int[] intArr = new int[1];
+      Object[] objArr = new Object[2];
+      objArr[0] = intArr;
+      System.out.println(Arrays.toString((int[]) objArr[0]));
+    }
+
+    @NeverInline
+    private static void assumedValues() {
+      Object[] arr = {assumedNonNullField, assumedNullField};
+      System.out.println(Arrays.toString(arr));
+    }
+
+    @NeverInline
+    private static void phiFilledNewArray() {
+      // The presence of ? should not affect use of filled-new-array.
+      Integer[] phiArray = {1, System.nanoTime() > 0 ? 2 : 3};
+      System.out.println(Arrays.toString(phiArray));
+    }
+
+    @NeverInline
+    private static void intsThatUseFilledNewArray() {
+      // Up to 5 ints uses filled-new-array rather than filled-array-data.
+      int[] intArr = {1, 2, 3, 4, 5};
+      System.out.println(Arrays.toString(intArr));
+    }
+
+    @NeverInline
+    private static void twoDimensionalArrays() {
+      Integer[][] twoDimensions = {new Integer[] {1}, null};
+      System.out.println(Arrays.toString(Arrays.asList(twoDimensions).get(0)));
+    }
+
+    @NeverInline
+    private static void objectArraysFilledNewArrayRange() {
+      // 6 or more elements use /range variant.
+      Object[] objectArr = {"a", 1, null, "d", "e", "f"};
+      System.out.println(Arrays.toString(objectArr));
+      Serializable[] interfaceArr = {
+        getSerializable(1), null, getSerializable(3), null, null, getSerializable(6)
+      };
+      System.out.println(Arrays.toString(interfaceArr));
+    }
+
+    @NeverInline
+    private static void arraysThatUseFilledData() {
+      // For int[], <= 5 elements should use NewArrayFilledData (otherwise NewFilledArray is used).
+      int[] intArr = {1, 2, 3, 4, 5, 6};
+      // For other primitives, > 1 element leads to fill-array-data.
+      System.out.println(Arrays.toString(intArr));
+      boolean[] boolArr = {true, false};
+      System.out.println(Arrays.toString(boolArr));
+      byte[] byteArr = {1, 2};
+      System.out.println(Arrays.toString(byteArr));
+      char[] charArr = {'1', '2'};
+      System.out.println(Arrays.toString(charArr));
+      long[] longArr = {1, 2};
+      System.out.println(Arrays.toString(longArr));
+      float[] floatArr = {1, 2};
+      System.out.println(Arrays.toString(floatArr));
+      double[] doubleArr = {1, 2};
+      System.out.println(Arrays.toString(doubleArr));
+    }
+
+    @NeverInline
+    private static void arraysThatUseNewArrayEmpty() {
+      // int/object of size zero should not use filled-new-array.
+      int[] intArr = {};
+      System.out.println(Arrays.toString(intArr));
+      String[] strArr = {};
+      System.out.println(Arrays.toString(strArr));
+
+      // Other primitives with size <= 1 should not use filled-array-data.
+      boolean[] boolArr = {true};
+      System.out.println(Arrays.toString(boolArr));
+      byte[] byteArr = {1};
+      System.out.println(Arrays.toString(byteArr));
+      char[] charArr = {'1'};
+      System.out.println(Arrays.toString(charArr));
+      long[] longArr = {1};
+      System.out.println(Arrays.toString(longArr));
+      float[] floatArr = {1};
+      System.out.println(Arrays.toString(floatArr));
+      double[] doubleArr = {1};
+      System.out.println(Arrays.toString(doubleArr));
+
+      // Array used before all members are set.
+      int[] readArray = new int[2];
+      readArray[0] = (int) (System.currentTimeMillis() / System.nanoTime());
+      readArray[1] = readArray[0] + 1;
+      System.out.println(Arrays.toString(readArray));
+
+      // Array does not have all elements set (we could make this work, but likely this is rare).
+      Integer[] partialArray = new Integer[2];
+      partialArray[0] = 1;
+      System.out.println(Arrays.toString(partialArray));
+
+      // Non-constant array size.
+      int trickyZero = (int) (System.currentTimeMillis() / System.nanoTime());
+      Object[] nonConstSize = new Object[trickyZero + 1];
+      nonConstSize[0] = "a";
+      System.out.println(Arrays.toString(nonConstSize));
+
+      // Non-constant index.
+      Object[] nonConstIndex = new Object[2];
+      nonConstIndex[trickyZero] = 0;
+      nonConstIndex[trickyZero + 1] = 1;
+      System.out.println(Arrays.toString(nonConstIndex));
+
+      // Exceeds our (arbitrary) size limit for /range.
+      String[] bigArr = new String[201];
+      bigArr[0] = "0";
+      bigArr[1] = "1";
+      bigArr[2] = "2";
+      bigArr[3] = "3";
+      bigArr[4] = "4";
+      bigArr[5] = "5";
+      bigArr[6] = "6";
+      bigArr[7] = "7";
+      bigArr[8] = "8";
+      bigArr[9] = "9";
+      bigArr[10] = "10";
+      bigArr[11] = "11";
+      bigArr[12] = "12";
+      bigArr[13] = "13";
+      bigArr[14] = "14";
+      bigArr[15] = "15";
+      bigArr[16] = "16";
+      bigArr[17] = "17";
+      bigArr[18] = "18";
+      bigArr[19] = "19";
+      bigArr[20] = "20";
+      bigArr[21] = "21";
+      bigArr[22] = "22";
+      bigArr[23] = "23";
+      bigArr[24] = "24";
+      bigArr[25] = "25";
+      bigArr[26] = "26";
+      bigArr[27] = "27";
+      bigArr[28] = "28";
+      bigArr[29] = "29";
+      bigArr[30] = "30";
+      bigArr[31] = "31";
+      bigArr[32] = "32";
+      bigArr[33] = "33";
+      bigArr[34] = "34";
+      bigArr[35] = "35";
+      bigArr[36] = "36";
+      bigArr[37] = "37";
+      bigArr[38] = "38";
+      bigArr[39] = "39";
+      bigArr[40] = "40";
+      bigArr[41] = "41";
+      bigArr[42] = "42";
+      bigArr[43] = "43";
+      bigArr[44] = "44";
+      bigArr[45] = "45";
+      bigArr[46] = "46";
+      bigArr[47] = "47";
+      bigArr[48] = "48";
+      bigArr[49] = "49";
+      bigArr[50] = "50";
+      bigArr[51] = "51";
+      bigArr[52] = "52";
+      bigArr[53] = "53";
+      bigArr[54] = "54";
+      bigArr[55] = "55";
+      bigArr[56] = "56";
+      bigArr[57] = "57";
+      bigArr[58] = "58";
+      bigArr[59] = "59";
+      bigArr[60] = "60";
+      bigArr[61] = "61";
+      bigArr[62] = "62";
+      bigArr[63] = "63";
+      bigArr[64] = "64";
+      bigArr[65] = "65";
+      bigArr[66] = "66";
+      bigArr[67] = "67";
+      bigArr[68] = "68";
+      bigArr[69] = "69";
+      bigArr[70] = "70";
+      bigArr[71] = "71";
+      bigArr[72] = "72";
+      bigArr[73] = "73";
+      bigArr[74] = "74";
+      bigArr[75] = "75";
+      bigArr[76] = "76";
+      bigArr[77] = "77";
+      bigArr[78] = "78";
+      bigArr[79] = "79";
+      bigArr[80] = "80";
+      bigArr[81] = "81";
+      bigArr[82] = "82";
+      bigArr[83] = "83";
+      bigArr[84] = "84";
+      bigArr[85] = "85";
+      bigArr[86] = "86";
+      bigArr[87] = "87";
+      bigArr[88] = "88";
+      bigArr[89] = "89";
+      bigArr[90] = "90";
+      bigArr[91] = "91";
+      bigArr[92] = "92";
+      bigArr[93] = "93";
+      bigArr[94] = "94";
+      bigArr[95] = "95";
+      bigArr[96] = "96";
+      bigArr[97] = "97";
+      bigArr[98] = "98";
+      bigArr[99] = "99";
+      bigArr[100] = "100";
+      bigArr[101] = "101";
+      bigArr[102] = "102";
+      bigArr[103] = "103";
+      bigArr[104] = "104";
+      bigArr[105] = "105";
+      bigArr[106] = "106";
+      bigArr[107] = "107";
+      bigArr[108] = "108";
+      bigArr[109] = "109";
+      bigArr[110] = "110";
+      bigArr[111] = "111";
+      bigArr[112] = "112";
+      bigArr[113] = "113";
+      bigArr[114] = "114";
+      bigArr[115] = "115";
+      bigArr[116] = "116";
+      bigArr[117] = "117";
+      bigArr[118] = "118";
+      bigArr[119] = "119";
+      bigArr[120] = "120";
+      bigArr[121] = "121";
+      bigArr[122] = "122";
+      bigArr[123] = "123";
+      bigArr[124] = "124";
+      bigArr[125] = "125";
+      bigArr[126] = "126";
+      bigArr[127] = "127";
+      bigArr[128] = "128";
+      bigArr[129] = "129";
+      bigArr[130] = "130";
+      bigArr[131] = "131";
+      bigArr[132] = "132";
+      bigArr[133] = "133";
+      bigArr[134] = "134";
+      bigArr[135] = "135";
+      bigArr[136] = "136";
+      bigArr[137] = "137";
+      bigArr[138] = "138";
+      bigArr[139] = "139";
+      bigArr[140] = "140";
+      bigArr[141] = "141";
+      bigArr[142] = "142";
+      bigArr[143] = "143";
+      bigArr[144] = "144";
+      bigArr[145] = "145";
+      bigArr[146] = "146";
+      bigArr[147] = "147";
+      bigArr[148] = "148";
+      bigArr[149] = "149";
+      bigArr[150] = "150";
+      bigArr[151] = "151";
+      bigArr[152] = "152";
+      bigArr[153] = "153";
+      bigArr[154] = "154";
+      bigArr[155] = "155";
+      bigArr[156] = "156";
+      bigArr[157] = "157";
+      bigArr[158] = "158";
+      bigArr[159] = "159";
+      bigArr[160] = "160";
+      bigArr[161] = "161";
+      bigArr[162] = "162";
+      bigArr[163] = "163";
+      bigArr[164] = "164";
+      bigArr[165] = "165";
+      bigArr[166] = "166";
+      bigArr[167] = "167";
+      bigArr[168] = "168";
+      bigArr[169] = "169";
+      bigArr[170] = "170";
+      bigArr[171] = "171";
+      bigArr[172] = "172";
+      bigArr[173] = "173";
+      bigArr[174] = "174";
+      bigArr[175] = "175";
+      bigArr[176] = "176";
+      bigArr[177] = "177";
+      bigArr[178] = "178";
+      bigArr[179] = "179";
+      bigArr[180] = "180";
+      bigArr[181] = "181";
+      bigArr[182] = "182";
+      bigArr[183] = "183";
+      bigArr[184] = "184";
+      bigArr[185] = "185";
+      bigArr[186] = "186";
+      bigArr[187] = "187";
+      bigArr[188] = "188";
+      bigArr[189] = "189";
+      bigArr[190] = "190";
+      bigArr[191] = "191";
+      bigArr[192] = "192";
+      bigArr[193] = "193";
+      bigArr[194] = "194";
+      bigArr[195] = "195";
+      bigArr[196] = "196";
+      bigArr[197] = "197";
+      bigArr[198] = "198";
+      bigArr[199] = "199";
+      bigArr[200] = "200";
+      System.out.println(Arrays.asList(bigArr).get(200));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
index 70e7eab..9ec695c 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
@@ -122,6 +122,31 @@
     };
   }
 
+  public static Matcher<MethodSubject> containsConstClass(ClassReference classReference) {
+    return new TypeSafeMatcher<MethodSubject>() {
+      @Override
+      protected boolean matchesSafely(MethodSubject subject) {
+        return subject.isPresent()
+            && subject.getMethod().hasCode()
+            && subject
+                .streamInstructions()
+                .anyMatch(
+                    instructionSubject ->
+                        instructionSubject.isConstClass(classReference.getTypeName()));
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("contains constclass");
+      }
+
+      @Override
+      public void describeMismatchSafely(MethodSubject subject, Description description) {
+        description.appendText("method did not");
+      }
+    };
+  }
+
   public static Matcher<MethodSubject> instantiatesClass(Class<?> clazz) {
     return instantiatesClass(clazz.getTypeName());
   }
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 867d312..fe921e2 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -15,7 +15,7 @@
 
 import utils
 
-R8_DEV_BRANCH = '8.0'
+R8_DEV_BRANCH = '8.1'
 R8_VERSION_FILE = os.path.join(
     'src', 'main', 'java', 'com', 'android', 'tools', 'r8', 'Version.java')
 THIS_FILE_RELATIVE = os.path.join('tools', 'r8_release.py')
@@ -193,7 +193,8 @@
       # Don't upload if requested not to, or if changes are not committed due
       # to --use-existing-work-branch
       if not options.no_upload and not options.use_existing_work_branch:
-        process = subprocess.Popen(['repo', 'upload', '.', '--verify'],
+        # TODO(b/260680525): Consider removing "-o banned-words~skip" if b/260680525 is resolved.
+        process = subprocess.Popen(['repo', 'upload', '.', '--verify', '-o' 'banned-words~skip'],
                                    stdin=subprocess.PIPE)
         return process.communicate(input=b'y\n')[0]