Merge commit 'c933a05659b0b0808dbf898636b6ef6a9319e55c' into dev-release
diff --git a/build.gradle b/build.gradle
index 9981676..21a20be 100644
--- a/build.gradle
+++ b/build.gradle
@@ -779,11 +779,16 @@
     archiveFileName = 'sources_main_11.jar'
 }
 
-def r8CreateTask(name, baseName, sources, includeSwissArmyKnife) {
+def r8CreateTask(name, baseName, sources, includeLibraryLicenses, includeSwissArmyKnife) {
     return tasks.create("r8Create${name}", Jar) {
         entryCompression ZipEntryCompression.STORED
         dependsOn sources
-        from consolidatedLicense.outputs.files
+        dependsOn files('LICENSE')
+        if (includeLibraryLicenses) {
+            from consolidatedLicense.outputs.files
+        } else {
+            from files('LICENSE')
+        }
         from sources.collect { zipTree(it) }
         exclude "$buildDir/classes/**"
         archiveFileName = baseName
@@ -844,6 +849,7 @@
             'WithDeps',
             'r8_with_deps.jar',
             repackageSources.outputs.files + repackageDeps.outputs.files,
+            true,
             true)
     dependsOn r8Task
     outputs.files r8Task.outputs.files
@@ -857,6 +863,7 @@
             'WithDeps11',
             'r8_with_deps_11.jar',
             repackageSources11.outputs.files + repackageDeps.outputs.files,
+            true,
             true)
     dependsOn r8Task
     outputs.files r8Task.outputs.files
@@ -883,6 +890,7 @@
             'WithoutDeps',
             'r8_without_deps.jar',
             repackageSources.outputs.files,
+            false,
             true)
     dependsOn r8Task
     outputs.files r8Task.outputs.files
@@ -905,6 +913,7 @@
             'NoManifestWithoutDeps',
             'r8_no_manifest_without_deps.jar',
             repackageSources.outputs.files,
+            false,
             false)
     dependsOn r8Task
     outputs.files r8Task.outputs.files
@@ -917,6 +926,7 @@
             'NoManifestWithDeps',
             'r8_no_manifest_with_deps.jar',
             repackageSources.outputs.files + repackageDeps.outputs.files,
+            true,
             false)
     dependsOn r8Task
     outputs.files r8Task.outputs.files
diff --git a/scripts/dex-size-in-apk.sh b/scripts/dex-size-in-apk.sh
new file mode 100755
index 0000000..ebc9364
--- /dev/null
+++ b/scripts/dex-size-in-apk.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+#
+# 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.
+
+zipinfo $1 | grep 'classes.*\.dex' | awk '{printf "%s%s",sep,$4; sep="+"} END{print ""}' | bc
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index 27bdafd..9dc5143 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -48,6 +48,7 @@
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.io.File;
 import java.io.PrintStream;
@@ -56,6 +57,7 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.LinkedHashMap;
@@ -75,22 +77,39 @@
   private final InternalOptions options = new InternalOptions(factory, reporter);
 
   private final MachineDesugaredLibrarySpecification desugaredLibrarySpecification;
-  private final Path desugaredLibraryImplementation;
+  private final Collection<Path> desugaredLibraryImplementation;
   private final Path outputDirectory;
 
   private final Set<DexMethod> parallelMethods = Sets.newIdentityHashSet();
 
+  public static GenerateLintFiles createForTesting(
+      Path specification, Set<Path> implementation, Path outputDirectory) throws Exception {
+    return new GenerateLintFiles(specification, implementation, outputDirectory);
+  }
+
   public GenerateLintFiles(
       String desugarConfigurationPath, String desugarImplementationPath, String outputDirectory)
       throws Exception {
+    this(
+        Paths.get(desugarConfigurationPath),
+        ImmutableList.of(Paths.get(desugarImplementationPath)),
+        Paths.get(outputDirectory));
+  }
+
+  private GenerateLintFiles(
+      Path desugarConfigurationPath,
+      Collection<Path> desugarImplementationPath,
+      Path outputDirectory)
+      throws Exception {
     DesugaredLibrarySpecification specification =
         readDesugaredLibraryConfiguration(desugarConfigurationPath);
     Path androidJarPath = getAndroidJarPath(specification.getRequiredCompilationApiLevel());
     this.desugaredLibrarySpecification =
-        specification.toMachineSpecification(options, androidJarPath, Timing.empty());
+        specification.toMachineSpecification(
+            options, ImmutableList.of(androidJarPath), Timing.empty());
 
-    this.desugaredLibraryImplementation = Paths.get(desugarImplementationPath);
-    this.outputDirectory = Paths.get(outputDirectory);
+    this.desugaredLibraryImplementation = desugarImplementationPath;
+    this.outputDirectory = outputDirectory;
     if (!Files.isDirectory(this.outputDirectory)) {
       throw new Exception("Output directory " + outputDirectory + " is not a directory");
     }
@@ -125,9 +144,9 @@
   }
 
   private DesugaredLibrarySpecification readDesugaredLibraryConfiguration(
-      String desugarConfigurationPath) {
+      Path desugarConfigurationPath) {
     return DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecification(
-        StringResource.fromFile(Paths.get(desugarConfigurationPath)),
+        StringResource.fromFile(desugarConfigurationPath),
         factory,
         reporter,
         false,
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 9798222..d2c931e 100644
--- a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
@@ -21,7 +21,6 @@
 import com.android.tools.r8.cf.code.CfDexItemBasedConstString;
 import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfGoto;
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfIfCmp;
@@ -52,6 +51,7 @@
 import com.android.tools.r8.cf.code.CfSwitch;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.CfCode;
@@ -201,7 +201,7 @@
   }
 
   private String frameTypeType() {
-    return r8Type("FrameType", ImmutableList.of("cf", "code", "CfFrame"));
+    return r8Type("FrameType", ImmutableList.of("cf", "code", "frame"));
   }
 
   private String monitorType() {
@@ -544,7 +544,7 @@
       } else {
         return frameTypeType()
             + ".initialized("
-            + dexType(frameType.asSingleInitializedType().getInitializedType())
+            + dexType(frameType.asInitializedReferenceType().getInitializedType())
             + ")";
       }
     }
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 6030119..0c4a184 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.cf.code.CfDexItemBasedConstString;
 import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfGoto;
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfIfCmp;
@@ -55,6 +54,8 @@
 import com.android.tools.r8.cf.code.CfSwitch.Kind;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.cf.code.frame.FrameType;
+import com.android.tools.r8.cf.code.frame.PreciseFrameType;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
@@ -445,7 +446,7 @@
     builder.append("] [");
     {
       String separator = "";
-      for (FrameType element : frame.getStack()) {
+      for (PreciseFrameType element : frame.getStack()) {
         builder.append(separator);
         print(element);
         separator = ", ";
@@ -458,7 +459,7 @@
     if (type.isPrimitive()) {
       builder.append(type.asPrimitive().getTypeName());
     } else if (type.isInitialized()) {
-      appendType(type.asSingleInitializedType().getInitializedType());
+      appendType(type.asInitializedReferenceType().getInitializedType());
     } else if (type.isUninitializedNew()) {
       builder.append("uninitialized ").append(getLabel(type.getUninitializedLabel()));
     } else {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
index 993dd8e..3ed5c3a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
@@ -4,13 +4,11 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -207,18 +205,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., value1, value2 →
-    // ..., result
-    FrameType frameType = FrameType.fromNumericType(type, dexItemFactory);
-    frameBuilder.popAndDiscard(frameType, frameType).push(frameType);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState state,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
index 8a39916..78b9067 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -82,19 +81,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., arrayref →
-    // ..., length
-    frameBuilder
-        .popAndDiscardInitialized(dexItemFactory.objectArrayType)
-        .push(dexItemFactory.intType);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index 129219b..8bba186 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -4,13 +4,11 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -128,18 +126,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., arrayref, index →
-    // ..., value
-    frameBuilder.popAndDiscardInitialized(dexItemFactory.objectArrayType, dexItemFactory.intType);
-    frameBuilder.push(FrameType.fromPreciseMemberType(type, dexItemFactory));
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
index 965c413..489285f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
@@ -4,13 +4,11 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -119,19 +117,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., arrayref, index, value →
-    // ...
-    frameBuilder
-        .popAndDiscard(FrameType.fromPreciseMemberType(type, dexItemFactory))
-        .popAndDiscardInitialized(dexItemFactory.objectArrayType, dexItemFactory.intType);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java b/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java
index 8335908..dc247a4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java
@@ -4,7 +4,8 @@
 
 package com.android.tools.r8.cf.code;
 
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.frame.FrameType;
+import com.android.tools.r8.cf.code.frame.PreciseFrameType;
 import com.android.tools.r8.cf.code.frame.SingleFrameType;
 import com.android.tools.r8.cf.code.frame.WideFrameType;
 import com.android.tools.r8.graph.AppView;
@@ -27,7 +28,7 @@
     }
     return source.isSingle()
         ? isFrameTypeAssignable(source.asSingle(), target.asSingle(), appView)
-        : isFrameTypeAssignable(source.asWide(), target.asWide(), appView);
+        : isFrameTypeAssignable(source.asWide(), target.asWide());
   }
 
   // Based on https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.2.
@@ -61,17 +62,16 @@
       if (source.isInitialized()) {
         // Both are instantiated types and we resort to primitive type/java type hierarchy checking.
         return isAssignable(
-            source.asSingleInitializedType().getInitializedType(),
-            target.asSingleInitializedType().getInitializedType(),
+            source.asInitializedReferenceType().getInitializedType(),
+            target.asInitializedReferenceType().getInitializedType(),
             appView);
       }
-      return target.asSingleInitializedType().getInitializedType() == factory.objectType;
+      return target.asInitializedReferenceType().getInitializedType() == factory.objectType;
     }
     return false;
   }
 
-  public static boolean isFrameTypeAssignable(
-      WideFrameType source, WideFrameType target, AppView<?> appView) {
+  public static boolean isFrameTypeAssignable(WideFrameType source, WideFrameType target) {
     assert !source.isTwoWord();
     return source.lessThanOrEqualTo(target);
   }
@@ -174,7 +174,9 @@
   }
 
   public static AssignabilityResult isStackAssignable(
-      Deque<FrameType> sourceStack, Deque<FrameType> targetStack, AppView<?> appView) {
+      Deque<PreciseFrameType> sourceStack,
+      Deque<PreciseFrameType> targetStack,
+      AppView<?> appView) {
     if (sourceStack.size() != targetStack.size()) {
       return new FailedAssignabilityResult(
           "Source stack "
@@ -183,14 +185,10 @@
               + Arrays.toString(targetStack.toArray())
               + " is not the same size");
     }
-    Iterator<FrameType> otherIterator = targetStack.iterator();
+    Iterator<PreciseFrameType> otherIterator = targetStack.iterator();
     int stackIndex = 0;
-    for (FrameType sourceType : sourceStack) {
-      FrameType destinationType = otherIterator.next();
-      // TODO(b/231260627): By strengthening the stack to Deque<SpecificFrameType> the following
-      //  asserts would be trivial as a result of type checking.
-      assert sourceType.isSpecific();
-      assert destinationType.isSpecific();
+    for (PreciseFrameType sourceType : sourceStack) {
+      PreciseFrameType destinationType = otherIterator.next();
       if (!isFrameTypeAssignable(sourceType, destinationType, appView)) {
         return new FailedAssignabilityResult(
             "Could not assign '"
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index 6ad249b..8d7fe37 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -133,17 +132,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., objectref →
-    // ..., objectref
-    frameBuilder.popAndDiscardInitialized(dexItemFactory.objectType).push(type);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
index 5d3f7b9..c9edfea 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
@@ -4,13 +4,11 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -132,18 +130,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., value1, value2 →
-    // ..., result
-    FrameType frameType = FrameType.fromNumericType(type, dexItemFactory);
-    frameBuilder.popAndDiscard(frameType, frameType).push(dexItemFactory.intType);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
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 43e8eee..b76a1f3 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
@@ -10,7 +10,6 @@
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -140,17 +139,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ... →
-    // ..., value
-    frameBuilder.push(dexItemFactory.classType);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
index da94a6ba..48a6368 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
@@ -227,17 +227,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ... →
-    // ..., value
-    frameBuilder.push(dexItemFactory.classType);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
index ad9f25e..0932c43 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -104,17 +103,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ... →
-    // ..., value
-    frameBuilder.push(dexItemFactory.methodHandleType);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
index dc1bee0..74131ae 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -102,17 +101,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ... →
-    // ..., value
-    frameBuilder.push(dexItemFactory.methodTypeType);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
index 546fd6b..ed6f4ea 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -74,17 +73,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ... →
-    // ..., value
-    frameBuilder.push(DexItemFactory.nullValueType);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
index 3277d2b..f777da5 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -231,18 +230,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ... →
-    // ..., value
-    assert type.isPrimitive();
-    frameBuilder.push(type.toDexType(dexItemFactory));
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
index e143829..a205cb7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -105,17 +104,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ... →
-    // ..., value
-    frameBuilder.push(dexItemFactory.stringType);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
index 374b722..5dcc3fb 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -124,17 +123,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ... →
-    // ..., value
-    frameBuilder.push(dexItemFactory.stringType);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index fafcb67..8d00469 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -6,23 +6,17 @@
 import static org.objectweb.asm.Opcodes.F_NEW;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.cf.code.frame.PrimitiveFrameType;
-import com.android.tools.r8.cf.code.frame.SingleFrameType;
-import com.android.tools.r8.cf.code.frame.WideFrameType;
-import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.cf.code.frame.FrameType;
+import com.android.tools.r8.cf.code.frame.PreciseFrameType;
+import com.android.tools.r8.cf.code.frame.UninitializedFrameType;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.CfCodeStackMapValidatingException;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.code.MemberType;
-import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -50,282 +44,7 @@
 public class CfFrame extends CfInstruction implements Cloneable {
 
   public static final Int2ObjectSortedMap<FrameType> EMPTY_LOCALS = Int2ObjectSortedMaps.emptyMap();
-  public static final Deque<FrameType> EMPTY_STACK = ImmutableDeque.of();
-
-  public abstract static class FrameType {
-
-    public static BooleanFrameType booleanType() {
-      return BooleanFrameType.SINGLETON;
-    }
-
-    public static ByteFrameType byteType() {
-      return ByteFrameType.SINGLETON;
-    }
-
-    public static CharFrameType charType() {
-      return CharFrameType.SINGLETON;
-    }
-
-    public static DoubleFrameType doubleType() {
-      return DoubleFrameType.SINGLETON;
-    }
-
-    public static FloatFrameType floatType() {
-      return FloatFrameType.SINGLETON;
-    }
-
-    public static IntFrameType intType() {
-      return IntFrameType.SINGLETON;
-    }
-
-    public static LongFrameType longType() {
-      return LongFrameType.SINGLETON;
-    }
-
-    public static ShortFrameType shortType() {
-      return ShortFrameType.SINGLETON;
-    }
-
-    public static FrameType initialized(DexType type) {
-      if (type.isPrimitiveType()) {
-        char c = (char) type.getDescriptor().content[0];
-        switch (c) {
-          case 'Z':
-            return booleanType();
-          case 'B':
-            return byteType();
-          case 'C':
-            return charType();
-          case 'D':
-            return doubleType();
-          case 'F':
-            return floatType();
-          case 'I':
-            return intType();
-          case 'J':
-            return longType();
-          case 'S':
-            return shortType();
-          default:
-            throw new Unreachable("Unexpected primitive type: " + type.getTypeName());
-        }
-      }
-      return new SingleInitializedType(type);
-    }
-
-    public static FrameType uninitializedNew(CfLabel label, DexType typeToInitialize) {
-      return new UninitializedNew(label, typeToInitialize);
-    }
-
-    public static FrameType uninitializedThis() {
-      return UninitializedThis.SINGLETON;
-    }
-
-    public static OneWord oneWord() {
-      return OneWord.SINGLETON;
-    }
-
-    public static TwoWord twoWord() {
-      return TwoWord.SINGLETON;
-    }
-
-    public FrameType asFrameType() {
-      return this;
-    }
-
-    abstract Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens);
-
-    public boolean isBoolean() {
-      return false;
-    }
-
-    public boolean isByte() {
-      return false;
-    }
-
-    public boolean isChar() {
-      return false;
-    }
-
-    public boolean isDouble() {
-      return false;
-    }
-
-    public boolean isFloat() {
-      return false;
-    }
-
-    public boolean isInt() {
-      return false;
-    }
-
-    public boolean isLong() {
-      return false;
-    }
-
-    public boolean isShort() {
-      return false;
-    }
-
-    public boolean isNullType() {
-      return false;
-    }
-
-    public boolean isObject() {
-      return false;
-    }
-
-    public DexType getObjectType(DexType context) {
-      assert false : "Unexpected use of getObjectType() for non-object FrameType";
-      return null;
-    }
-
-    public boolean isPrimitive() {
-      return false;
-    }
-
-    public PrimitiveFrameType asPrimitive() {
-      return null;
-    }
-
-    public final boolean isSingle() {
-      return !isWide();
-    }
-
-    public SingleFrameType asSingle() {
-      return null;
-    }
-
-    public SinglePrimitiveFrameType asSinglePrimitive() {
-      return null;
-    }
-
-    public SingleInitializedType asSingleInitializedType() {
-      return null;
-    }
-
-    public boolean isWide() {
-      return false;
-    }
-
-    public WideFrameType asWide() {
-      return null;
-    }
-
-    public int getWidth() {
-      assert isSingle();
-      return 1;
-    }
-
-    public boolean isUninitializedNew() {
-      return false;
-    }
-
-    public boolean isUninitializedObject() {
-      return false;
-    }
-
-    public CfLabel getUninitializedLabel() {
-      return null;
-    }
-
-    public boolean isUninitializedThis() {
-      return false;
-    }
-
-    public boolean isInitialized() {
-      return false;
-    }
-
-    public DexType getInitializedType(DexItemFactory dexItemFactory) {
-      return null;
-    }
-
-    public DexType getUninitializedNewType() {
-      return null;
-    }
-
-    public boolean isOneWord() {
-      return false;
-    }
-
-    public boolean isSpecific() {
-      return true;
-    }
-
-    public boolean isTwoWord() {
-      return false;
-    }
-
-    FrameType map(java.util.function.Function<DexType, DexType> func) {
-      if (isObject()) {
-        if (isInitialized()) {
-          DexType type = asSingleInitializedType().getInitializedType();
-          DexType newType = func.apply(type);
-          if (type != newType) {
-            return initialized(newType);
-          }
-        }
-        if (isUninitializedNew()) {
-          DexType type = getUninitializedNewType();
-          DexType newType = func.apply(type);
-          if (type != newType) {
-            return uninitializedNew(getUninitializedLabel(), newType);
-          }
-        }
-      }
-      return this;
-    }
-
-    private FrameType() {}
-
-    @Override
-    public abstract boolean equals(Object obj);
-
-    @Override
-    public abstract int hashCode();
-
-    public static FrameType fromPreciseMemberType(MemberType memberType, DexItemFactory factory) {
-      assert memberType.isPrecise();
-      switch (memberType) {
-        case OBJECT:
-          return FrameType.initialized(factory.objectType);
-        case BOOLEAN_OR_BYTE:
-          return FrameType.initialized(factory.intType);
-        case CHAR:
-          return FrameType.initialized(factory.intType);
-        case SHORT:
-          return FrameType.initialized(factory.intType);
-        case INT:
-          return FrameType.initialized(factory.intType);
-        case FLOAT:
-          return FrameType.initialized(factory.floatType);
-        case LONG:
-          return FrameType.longType();
-        case DOUBLE:
-          return FrameType.doubleType();
-        default:
-          throw new Unreachable("Unexpected MemberType: " + memberType);
-      }
-    }
-
-    public static FrameType fromNumericType(NumericType numericType, DexItemFactory factory) {
-      return FrameType.initialized(numericType.toDexType(factory));
-    }
-  }
-
-  public abstract static class SingletonFrameType extends FrameType {
-
-    @Override
-    public final boolean equals(Object obj) {
-      return this == obj;
-    }
-
-    @Override
-    public final int hashCode() {
-      return System.identityHashCode(this);
-    }
-  }
+  public static final Deque<PreciseFrameType> EMPTY_STACK = ImmutableDeque.of();
 
   @Override
   public boolean isFrame() {
@@ -350,680 +69,8 @@
     return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
   }
 
-  public abstract static class SinglePrimitiveFrameType extends SingletonFrameType
-      implements PrimitiveFrameType, SingleFrameType {
-
-    public boolean hasIntVerificationType() {
-      return false;
-    }
-
-    @Override
-    public final boolean isInitialized() {
-      return true;
-    }
-
-    @Override
-    public final boolean isPrimitive() {
-      return true;
-    }
-
-    @Override
-    public PrimitiveFrameType asPrimitive() {
-      return this;
-    }
-
-    @Override
-    public final SingleFrameType asSingle() {
-      return this;
-    }
-
-    @Override
-    public final SinglePrimitiveFrameType asSinglePrimitive() {
-      return this;
-    }
-
-    @Override
-    public final SingleFrameType join(SingleFrameType frameType) {
-      if (this == frameType) {
-        return this;
-      }
-      if (hasIntVerificationType()
-          && frameType.isPrimitive()
-          && frameType.asSinglePrimitive().hasIntVerificationType()) {
-        return intType();
-      }
-      return oneWord();
-    }
-
-    @Override
-    public final String toString() {
-      return getTypeName();
-    }
-  }
-
-  public static class BooleanFrameType extends SinglePrimitiveFrameType {
-
-    private static final BooleanFrameType SINGLETON = new BooleanFrameType();
-
-    private BooleanFrameType() {}
-
-    @Override
-    public DexType getInitializedType(DexItemFactory dexItemFactory) {
-      return dexItemFactory.booleanType;
-    }
-
-    @Override
-    public String getTypeName() {
-      return "boolean";
-    }
-
-    @Override
-    Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
-      throw new Unreachable("Unexpected value type: " + this);
-    }
-
-    @Override
-    public boolean hasIntVerificationType() {
-      return true;
-    }
-
-    @Override
-    public boolean isBoolean() {
-      return true;
-    }
-  }
-
-  public static class ByteFrameType extends SinglePrimitiveFrameType {
-
-    private static final ByteFrameType SINGLETON = new ByteFrameType();
-
-    private ByteFrameType() {}
-
-    @Override
-    public DexType getInitializedType(DexItemFactory dexItemFactory) {
-      return dexItemFactory.byteType;
-    }
-
-    @Override
-    public String getTypeName() {
-      return "byte";
-    }
-
-    @Override
-    Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
-      throw new Unreachable("Unexpected value type: " + this);
-    }
-
-    @Override
-    public boolean hasIntVerificationType() {
-      return true;
-    }
-
-    @Override
-    public boolean isByte() {
-      return true;
-    }
-  }
-
-  public static class CharFrameType extends SinglePrimitiveFrameType {
-
-    private static final CharFrameType SINGLETON = new CharFrameType();
-
-    private CharFrameType() {}
-
-    @Override
-    public DexType getInitializedType(DexItemFactory dexItemFactory) {
-      return dexItemFactory.charType;
-    }
-
-    @Override
-    public String getTypeName() {
-      return "char";
-    }
-
-    @Override
-    Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
-      throw new Unreachable("Unexpected value type: " + this);
-    }
-
-    @Override
-    public boolean hasIntVerificationType() {
-      return true;
-    }
-
-    @Override
-    public boolean isChar() {
-      return true;
-    }
-  }
-
-  public static class FloatFrameType extends SinglePrimitiveFrameType {
-
-    private static final FloatFrameType SINGLETON = new FloatFrameType();
-
-    private FloatFrameType() {}
-
-    @Override
-    public DexType getInitializedType(DexItemFactory dexItemFactory) {
-      return dexItemFactory.floatType;
-    }
-
-    @Override
-    public String getTypeName() {
-      return "float";
-    }
-
-    @Override
-    Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
-      return Opcodes.FLOAT;
-    }
-
-    @Override
-    public boolean isFloat() {
-      return true;
-    }
-  }
-
-  public static class IntFrameType extends SinglePrimitiveFrameType {
-
-    private static final IntFrameType SINGLETON = new IntFrameType();
-
-    private IntFrameType() {}
-
-    @Override
-    public DexType getInitializedType(DexItemFactory dexItemFactory) {
-      return dexItemFactory.intType;
-    }
-
-    @Override
-    public String getTypeName() {
-      return "int";
-    }
-
-    @Override
-    Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
-      return Opcodes.INTEGER;
-    }
-
-    @Override
-    public boolean hasIntVerificationType() {
-      return true;
-    }
-
-    @Override
-    public boolean isInt() {
-      return true;
-    }
-  }
-
-  public static class ShortFrameType extends SinglePrimitiveFrameType {
-
-    private static final ShortFrameType SINGLETON = new ShortFrameType();
-
-    private ShortFrameType() {}
-
-    @Override
-    public DexType getInitializedType(DexItemFactory dexItemFactory) {
-      return dexItemFactory.shortType;
-    }
-
-    @Override
-    public String getTypeName() {
-      return "short";
-    }
-
-    @Override
-    Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
-      throw new Unreachable("Unexpected value type: " + this);
-    }
-
-    @Override
-    public boolean hasIntVerificationType() {
-      return true;
-    }
-
-    @Override
-    public boolean isShort() {
-      return true;
-    }
-  }
-
-  public static class SingleInitializedType extends FrameType implements SingleFrameType {
-
-    private final DexType type;
-
-    private SingleInitializedType(DexType type) {
-      assert type != null;
-      assert type.isReferenceType();
-      this.type = type;
-    }
-
-    @Override
-    public SingleInitializedType asSingleInitializedType() {
-      return this;
-    }
-
-    @Override
-    public SingleFrameType join(SingleFrameType frameType) {
-      if (equals(frameType)) {
-        return this;
-      }
-      if (frameType.isOneWord() || frameType.isPrimitive() || frameType.isUninitializedObject()) {
-        return oneWord();
-      }
-      DexType otherType = frameType.asSingleInitializedType().getInitializedType();
-      assert type != otherType;
-      assert type.isReferenceType();
-      if (isNullType()) {
-        return otherType.isReferenceType() ? frameType : oneWord();
-      }
-      if (frameType.isNullType()) {
-        return this;
-      }
-      assert type.isArrayType() || type.isClassType();
-      assert otherType.isArrayType() || otherType.isClassType();
-      // TODO(b/214496607): Implement join of different reference types using class hierarchy.
-      throw new Unimplemented();
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-      if (this == obj) {
-        return true;
-      }
-      if (obj == null || getClass() != obj.getClass()) {
-        return false;
-      }
-      SingleInitializedType initializedType = (SingleInitializedType) obj;
-      return type == initializedType.type;
-    }
-
-    @Override
-    public int hashCode() {
-      return type.hashCode();
-    }
-
-    @Override
-    public String toString() {
-      return "Initialized(" + type.toString() + ")";
-    }
-
-    @Override
-    Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
-      DexType rewrittenType = graphLens.lookupType(type);
-      if (rewrittenType == DexItemFactory.nullValueType) {
-        return Opcodes.NULL;
-      }
-      switch (rewrittenType.toShorty()) {
-        case 'L':
-          return namingLens.lookupInternalName(rewrittenType);
-        case 'I':
-          return Opcodes.INTEGER;
-        case 'F':
-          return Opcodes.FLOAT;
-        case 'J':
-          return Opcodes.LONG;
-        case 'D':
-          return Opcodes.DOUBLE;
-        default:
-          throw new Unreachable("Unexpected value type: " + rewrittenType);
-      }
-    }
-
-    @Override
-    public SingleFrameType asSingle() {
-      return this;
-    }
-
-    @Override
-    public boolean isWide() {
-      return false;
-    }
-
-    @Override
-    public boolean isInitialized() {
-      return true;
-    }
-
-    public DexType getInitializedType() {
-      return type;
-    }
-
-    @Override
-    public DexType getInitializedType(DexItemFactory dexItemFactory) {
-      return getInitializedType();
-    }
-
-    @Override
-    public boolean isNullType() {
-      return type.isNullValueType();
-    }
-
-    @Override
-    public boolean isObject() {
-      return type.isReferenceType();
-    }
-
-    @Override
-    public DexType getObjectType(DexType context) {
-      assert isObject() : "Unexpected use of getObjectType() for non-object FrameType";
-      return type;
-    }
-  }
-
-  public abstract static class WidePrimitiveFrameType extends SingletonFrameType
-      implements PrimitiveFrameType, WideFrameType {
-
-    @Override
-    public boolean isInitialized() {
-      return true;
-    }
-
-    @Override
-    public boolean isPrimitive() {
-      return true;
-    }
-
-    @Override
-    public PrimitiveFrameType asPrimitive() {
-      return this;
-    }
-
-    @Override
-    public boolean isWide() {
-      return true;
-    }
-
-    @Override
-    public WideFrameType asWide() {
-      return this;
-    }
-
-    @Override
-    public int getWidth() {
-      return 2;
-    }
-
-    @Override
-    public WideFrameType join(WideFrameType frameType) {
-      return this == frameType ? this : twoWord();
-    }
-
-    @Override
-    public final String toString() {
-      return getTypeName();
-    }
-  }
-
-  public static class DoubleFrameType extends WidePrimitiveFrameType {
-
-    private static final DoubleFrameType SINGLETON = new DoubleFrameType();
-
-    private DoubleFrameType() {}
-
-    @Override
-    public boolean isDouble() {
-      return true;
-    }
-
-    @Override
-    public DexType getInitializedType(DexItemFactory dexItemFactory) {
-      return dexItemFactory.doubleType;
-    }
-
-    @Override
-    public String getTypeName() {
-      return "double";
-    }
-
-    @Override
-    Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
-      return Opcodes.DOUBLE;
-    }
-  }
-
-  public static class LongFrameType extends WidePrimitiveFrameType {
-
-    private static final LongFrameType SINGLETON = new LongFrameType();
-
-    private LongFrameType() {}
-
-    @Override
-    public boolean isLong() {
-      return true;
-    }
-
-    @Override
-    public DexType getInitializedType(DexItemFactory dexItemFactory) {
-      return dexItemFactory.longType;
-    }
-
-    @Override
-    public String getTypeName() {
-      return "long";
-    }
-
-    @Override
-    Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
-      return Opcodes.LONG;
-    }
-  }
-
-  private static class UninitializedNew extends FrameType implements SingleFrameType {
-
-    private final CfLabel label;
-    private final DexType type;
-
-    private UninitializedNew(CfLabel label, DexType type) {
-      this.label = label;
-      this.type = type;
-    }
-
-    @Override
-    public SingleFrameType asSingle() {
-      return this;
-    }
-
-    @Override
-    public SingleFrameType join(SingleFrameType frameType) {
-      return equals(frameType) ? this : oneWord();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (o == null || getClass() != o.getClass()) {
-        return false;
-      }
-      UninitializedNew uninitializedNew = (UninitializedNew) o;
-      return label == uninitializedNew.label && type == uninitializedNew.type;
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hash(label, type);
-    }
-
-    @Override
-    public String toString() {
-      return "uninitialized new";
-    }
-
-    @Override
-    Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
-      return label.getLabel();
-    }
-
-    @Override
-    public boolean isObject() {
-      return true;
-    }
-
-    @Override
-    public DexType getObjectType(DexType context) {
-      return type;
-    }
-
-    @Override
-    public boolean isUninitializedNew() {
-      return true;
-    }
-
-    @Override
-    public boolean isUninitializedObject() {
-      return true;
-    }
-
-    @Override
-    public CfLabel getUninitializedLabel() {
-      return label;
-    }
-
-    @Override
-    public DexType getUninitializedNewType() {
-      return type;
-    }
-  }
-
-  private static class UninitializedThis extends SingletonFrameType implements SingleFrameType {
-
-    private static final UninitializedThis SINGLETON = new UninitializedThis();
-
-    private UninitializedThis() {}
-
-    @Override
-    public SingleFrameType asSingle() {
-      return this;
-    }
-
-    @Override
-    public SingleFrameType join(SingleFrameType frameType) {
-      if (this == frameType) {
-        return this;
-      }
-      return oneWord();
-    }
-
-    @Override
-    Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
-      return Opcodes.UNINITIALIZED_THIS;
-    }
-
-    @Override
-    public String toString() {
-      return "uninitialized this";
-    }
-
-    @Override
-    public boolean isObject() {
-      return true;
-    }
-
-    @Override
-    public DexType getObjectType(DexType context) {
-      return context;
-    }
-
-    @Override
-    public boolean isUninitializedObject() {
-      return true;
-    }
-
-    @Override
-    public boolean isUninitializedThis() {
-      return true;
-    }
-  }
-
-  private static class OneWord extends SingletonFrameType implements SingleFrameType {
-
-    private static final OneWord SINGLETON = new OneWord();
-
-    private OneWord() {}
-
-    @Override
-    public boolean isOneWord() {
-      return true;
-    }
-
-    @Override
-    public boolean isSpecific() {
-      return false;
-    }
-
-    @Override
-    public SingleFrameType asSingle() {
-      return this;
-    }
-
-    @Override
-    public SingleFrameType join(SingleFrameType frameType) {
-      return this;
-    }
-
-    @Override
-    Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
-      return Opcodes.TOP;
-    }
-
-    @Override
-    public String toString() {
-      return "oneword";
-    }
-  }
-
-  private static class TwoWord extends SingletonFrameType implements WideFrameType {
-
-    private static final TwoWord SINGLETON = new TwoWord();
-
-    private TwoWord() {}
-
-    @Override
-    public boolean isSpecific() {
-      return false;
-    }
-
-    @Override
-    public boolean isTwoWord() {
-      return true;
-    }
-
-    @Override
-    public boolean isWide() {
-      return true;
-    }
-
-    @Override
-    public WideFrameType asWide() {
-      return this;
-    }
-
-    @Override
-    public int getWidth() {
-      return 2;
-    }
-
-    @Override
-    public WideFrameType join(WideFrameType frameType) {
-      // The join of wide with one of {double, long, wide} is wide.
-      return this;
-    }
-
-    @Override
-    Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
-      throw new Unreachable("Should only be used for verification");
-    }
-
-    @Override
-    public String toString() {
-      return "twoword";
-    }
-  }
-
   private final Int2ObjectSortedMap<FrameType> locals;
-  private final Deque<FrameType> stack;
+  private final Deque<PreciseFrameType> stack;
 
   // Constructor used by CfCodePrinter.
   public CfFrame() {
@@ -1037,20 +84,20 @@
   }
 
   // Constructor used by CfCodePrinter.
-  public CfFrame(Deque<FrameType> stack) {
+  public CfFrame(Deque<PreciseFrameType> stack) {
     this(EMPTY_LOCALS, stack);
     assert !stack.isEmpty() || stack == EMPTY_STACK : "Should use EMPTY_STACK instead";
   }
 
   // Constructor used by CfCodePrinter.
-  public CfFrame(Int2ObjectAVLTreeMap<FrameType> locals, Deque<FrameType> stack) {
+  public CfFrame(Int2ObjectAVLTreeMap<FrameType> locals, Deque<PreciseFrameType> stack) {
     this((Int2ObjectSortedMap<FrameType>) locals, stack);
     assert !locals.isEmpty() || locals == EMPTY_LOCALS : "Should use EMPTY_LOCALS instead";
     assert !stack.isEmpty() || stack == EMPTY_STACK : "Should use EMPTY_STACK instead";
   }
 
   // Internal constructor that does not require locals to be of the type Int2ObjectAVLTreeMap.
-  private CfFrame(Int2ObjectSortedMap<FrameType> locals, Deque<FrameType> stack) {
+  private CfFrame(Int2ObjectSortedMap<FrameType> locals, Deque<PreciseFrameType> stack) {
     assert locals.values().stream().allMatch(Objects::nonNull);
     assert stack.stream().allMatch(Objects::nonNull);
     this.locals = locals;
@@ -1087,13 +134,13 @@
     return (Int2ObjectAVLTreeMap<FrameType>) locals;
   }
 
-  public Deque<FrameType> getStack() {
+  public Deque<PreciseFrameType> getStack() {
     return stack;
   }
 
-  public ArrayDeque<FrameType> getMutableStack() {
+  public ArrayDeque<PreciseFrameType> getMutableStack() {
     assert stack instanceof ArrayDeque<?>;
-    return (ArrayDeque<FrameType>) stack;
+    return (ArrayDeque<PreciseFrameType>) stack;
   }
 
   @Override
@@ -1112,7 +159,7 @@
   public int hashCode() {
     // Generates a hash that is identical to Objects.hash(locals, stack[0], ..., stack[n]).
     int result = 31 + locals.hashCode();
-    for (FrameType frameType : stack) {
+    for (PreciseFrameType frameType : stack) {
       result = 31 * result + frameType.hashCode();
     }
     return result;
@@ -1146,7 +193,7 @@
 
   public int computeStackSize() {
     int size = 0;
-    for (FrameType frameType : stack) {
+    for (PreciseFrameType frameType : stack) {
       size += frameType.getWidth();
     }
     return size;
@@ -1159,7 +206,7 @@
     }
     Object[] stackTypes = new Object[stackCount];
     int index = 0;
-    for (FrameType frameType : stack) {
+    for (PreciseFrameType frameType : stack) {
       stackTypes[index++] = frameType.getTypeOpcode(graphLens, namingLens);
     }
     return stackTypes;
@@ -1227,15 +274,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    frameBuilder.checkFrameAndSet(this);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
@@ -1244,28 +282,8 @@
     return frame.check(appView, this);
   }
 
-  public CfFrame markInstantiated(FrameType uninitializedType, DexType initType) {
-    if (uninitializedType.isInitialized()) {
-      throw CfCodeStackMapValidatingException.error(
-          "Cannot instantiate already instantiated type " + uninitializedType);
-    }
-    CfFrame.Builder builder = CfFrame.builder().allocateStack(stack.size());
-    forEachLocal(
-        (localIndex, frameType) ->
-            builder.store(
-                localIndex, getInitializedFrameType(uninitializedType, frameType, initType)));
-    for (FrameType frameType : stack) {
-      builder.push(getInitializedFrameType(uninitializedType, frameType, initType));
-    }
-    return builder.build();
-  }
-
-  public static FrameType getInitializedFrameType(
-      FrameType unInit, FrameType other, DexType newType) {
-    assert !unInit.isInitialized();
-    if (other.isInitialized()) {
-      return other;
-    }
+  public static PreciseFrameType getInitializedFrameType(
+      UninitializedFrameType unInit, UninitializedFrameType other, DexType newType) {
     if (unInit.isUninitializedThis() && other.isUninitializedThis()) {
       return FrameType.initialized(newType);
     }
@@ -1280,16 +298,16 @@
   public CfFrame map(java.util.function.Function<DexType, DexType> func) {
     boolean mapped = false;
     for (int var : locals.keySet()) {
-      CfFrame.FrameType originalType = locals.get(var);
-      CfFrame.FrameType mappedType = originalType.map(func);
+      FrameType originalType = locals.get(var);
+      FrameType mappedType = originalType.map(func);
       mapped = originalType != mappedType;
       if (mapped) {
         break;
       }
     }
     if (!mapped) {
-      for (FrameType frameType : stack) {
-        CfFrame.FrameType mappedType = frameType.map(func);
+      for (PreciseFrameType frameType : stack) {
+        PreciseFrameType mappedType = frameType.map(func);
         mapped = frameType != mappedType;
         if (mapped) {
           break;
@@ -1303,7 +321,7 @@
     for (Int2ObjectMap.Entry<FrameType> entry : locals.int2ObjectEntrySet()) {
       builder.store(entry.getIntKey(), entry.getValue().map(func));
     }
-    for (FrameType frameType : stack) {
+    for (PreciseFrameType frameType : stack) {
       builder.push(frameType.map(func));
     }
     return builder.build();
@@ -1312,7 +330,7 @@
   public static class Builder {
 
     private Int2ObjectSortedMap<FrameType> locals = EMPTY_LOCALS;
-    private Deque<FrameType> stack = EMPTY_STACK;
+    private Deque<PreciseFrameType> stack = EMPTY_STACK;
 
     private boolean hasIncompleteUninitializedNew = false;
     private boolean seenStore = false;
@@ -1355,7 +373,7 @@
       return locals.get(localIndex);
     }
 
-    public Builder push(FrameType frameType) {
+    public Builder push(PreciseFrameType frameType) {
       ensureMutableStack();
       stack.addLast(frameType);
       return this;
@@ -1366,7 +384,7 @@
       return this;
     }
 
-    public Builder setStack(Deque<FrameType> stack) {
+    public Builder setStack(Deque<PreciseFrameType> stack) {
       this.stack = stack;
       return this;
     }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
index be448f1..7c73bbb 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
@@ -4,52 +4,60 @@
 
 package com.android.tools.r8.cf.code;
 
-import static com.android.tools.r8.utils.BiPredicateUtils.or;
-
 import com.android.tools.r8.cf.code.CfAssignability.AssignabilityResult;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.frame.FrameType;
+import com.android.tools.r8.cf.code.frame.PreciseFrameType;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.CfCodeDiagnostics;
 import com.android.tools.r8.graph.CfCodeStackMapValidatingException;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
+import com.android.tools.r8.optimize.interfaces.analysis.ConcreteCfFrameState;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.collections.ImmutableDeque;
 import com.google.common.collect.Sets;
-import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
 import java.util.ArrayDeque;
 import java.util.Deque;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.BiPredicate;
 
-public class CfFrameVerificationHelper {
-
-  private static final CfFrame NO_FRAME = new CfFrame();
+public class CfFrameVerificationHelper implements CfAnalysisConfig {
 
   private final AppView<?> appView;
+  private final CfCode code;
+  private final GraphLens codeLens;
   private final DexItemFactory factory;
+  private final ProgramMethod method;
+  private final DexMethod previousMethod;
 
-  private CfFrame currentFrame = NO_FRAME;
-  private final DexType context;
   private final Map<CfLabel, CfFrame> stateMap;
   private final List<CfTryCatch> tryCatchRanges;
-  private final int maxStackHeight;
 
   private final Deque<CfTryCatch> currentCatchRanges = new ArrayDeque<>();
   private final Set<CfLabel> tryCatchRangeLabels;
 
   public CfFrameVerificationHelper(
       AppView<?> appView,
-      DexType context,
+      CfCode code,
+      ProgramMethod method,
       Map<CfLabel, CfFrame> stateMap,
-      List<CfTryCatch> tryCatchRanges,
-      int maxStackHeight) {
+      List<CfTryCatch> tryCatchRanges) {
     this.appView = appView;
-    this.context = context;
+    this.code = code;
+    this.codeLens = code.getCodeLens(appView);
+    this.method = method;
+    this.previousMethod =
+        appView.graphLens().getOriginalMethodSignature(method.getReference(), codeLens);
     this.stateMap = stateMap;
     this.tryCatchRanges = tryCatchRanges;
     this.factory = appView.dexItemFactory();
-    this.maxStackHeight = maxStackHeight;
     // Compute all labels that marks a start or end to catch ranges.
     tryCatchRangeLabels = Sets.newIdentityHashSet();
     for (CfTryCatch tryCatchRange : tryCatchRanges) {
@@ -58,106 +66,36 @@
     }
   }
 
-  public FrameType readLocal(int index, DexType expectedType) {
-    checkFrameIsSet();
-    FrameType frameType = currentFrame.getLocals().get(index);
-    if (frameType == null) {
-      throw CfCodeStackMapValidatingException.error("No local at index " + index);
+  @Override
+  public DexMethod getCurrentContext() {
+    return previousMethod;
+  }
+
+  @Override
+  public int getMaxLocals() {
+    return code.getMaxLocals();
+  }
+
+  @Override
+  public int getMaxStack() {
+    return code.getMaxStack();
+  }
+
+  @Override
+  public boolean isImmediateSuperClassOfCurrentContext(DexType type) {
+    // If the code is rewritten according to the graph lens, we perform a strict check that the
+    // given type is the same as the current holder's super class.
+    if (codeLens == appView.graphLens()) {
+      return type == method.getHolder().getSuperType();
     }
-    checkIsAssignable(
-        frameType,
-        expectedType,
-        or(
-            this::isUninitializedThisAndTarget,
-            this::isUninitializedNewAndTarget,
-            this::isAssignableAndInitialized));
-    return frameType;
+    // Otherwise, we don't know what the super class of the current class was at the point of the
+    // code lens. We return true, which has the consequence that we may accept a constructor call
+    // for an uninitialized-this value where the constructor is not defined in the immediate parent
+    // class.
+    return true;
   }
 
-  public void storeLocal(int index, FrameType frameType) {
-    checkFrameIsSet();
-    currentFrame.getLocals().put(index, frameType);
-  }
-
-  public FrameType pop() {
-    checkFrameIsSet();
-    if (currentFrame.getStack().isEmpty()) {
-      throw CfCodeStackMapValidatingException.error("Cannot pop() from an empty stack");
-    }
-    return currentFrame.getStack().removeLast();
-  }
-
-  public FrameType popInitialized(DexType expectedType) {
-    return pop(expectedType, this::isAssignableAndInitialized);
-  }
-
-  public FrameType pop(DexType expectedType, BiPredicate<FrameType, DexType> isAssignable) {
-    FrameType frameType = pop();
-    checkIsAssignable(frameType, expectedType, isAssignable);
-    return frameType;
-  }
-
-  public CfFrameVerificationHelper popAndDiscardInitialized(DexType expectedType) {
-    checkFrameIsSet();
-    popInitialized(expectedType);
-    return this;
-  }
-
-  public CfFrameVerificationHelper popAndDiscardInitialized(DexType... expectedTypes) {
-    checkFrameIsSet();
-    for (int i = expectedTypes.length - 1; i >= 0; i--) {
-      popInitialized(expectedTypes[i]);
-    }
-    return this;
-  }
-
-  public FrameType pop(FrameType expectedType) {
-    FrameType frameType = pop();
-    checkIsAssignable(frameType, expectedType);
-    return frameType;
-  }
-
-  public CfFrameVerificationHelper popAndDiscard(FrameType... expectedTypes) {
-    checkFrameIsSet();
-    for (int i = expectedTypes.length - 1; i >= 0; i--) {
-      pop(expectedTypes[i]);
-    }
-    return this;
-  }
-
-  public void popAndInitialize(DexType context, DexType methodHolder) {
-    checkFrameIsSet();
-    FrameType objectRef =
-        pop(
-            factory.objectType,
-            or(this::isUninitializedThisAndTarget, this::isUninitializedNewAndTarget));
-    CfFrame newFrame =
-        currentFrame.markInstantiated(
-            objectRef, objectRef.isUninitializedNew() ? methodHolder : context);
-    setNoFrame();
-    checkFrameAndSet(newFrame);
-  }
-
-  public CfFrameVerificationHelper push(FrameType type) {
-    checkFrameIsSet();
-    currentFrame.getStack().addLast(type);
-    if (currentFrame.computeStackSize() > maxStackHeight) {
-      throw CfCodeStackMapValidatingException.error(
-          "The max stack height of "
-              + maxStackHeight
-              + " is violated when pushing type "
-              + type
-              + " to existing stack of size "
-              + currentFrame.getStack().size());
-    }
-    return this;
-  }
-
-  public CfFrameVerificationHelper push(DexType type) {
-    return push(FrameType.initialized(type));
-  }
-
-  public CfFrameVerificationHelper seenLabel(CfLabel label) {
+  public void seenLabel(CfLabel label) {
     if (tryCatchRangeLabels.contains(label)) {
       for (CfTryCatch tryCatchRange : tryCatchRanges) {
         if (tryCatchRange.start == label) {
@@ -166,148 +104,109 @@
       }
       currentCatchRanges.removeIf(currentRange -> currentRange.end == label);
     }
-    return this;
   }
 
-  public void checkTryCatchRange(CfTryCatch tryCatchRange) {
+  public CfCodeDiagnostics checkTryCatchRanges() {
+    for (CfTryCatch tryCatchRange : tryCatchRanges) {
+      CfCodeDiagnostics diagnostics = checkTryCatchRange(tryCatchRange);
+      if (diagnostics != null) {
+        return diagnostics;
+      }
+    }
+    return null;
+  }
+
+  public CfCodeDiagnostics checkTryCatchRange(CfTryCatch tryCatchRange) {
     // According to the spec:
     // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1
     // saying ` and the handler's target (the initial instruction of the handler code) is type
     // safe assuming an incoming type state T. The type state T is derived from ExcStackFrame
     // by replacing the operand stack with a stack whose sole element is the handler's
     // exception class.
-    tryCatchRange.targets.forEach(
-        target -> {
-          CfFrame destinationFrame = stateMap.get(target);
-          if (destinationFrame == null) {
-            throw CfCodeStackMapValidatingException.error("No frame for target catch range target");
-          }
-          // From the spec: the handler's exception class is assignable to the class Throwable.
-          tryCatchRange.guards.forEach(
-              guard -> {
-                if (!CfAssignability.isAssignable(guard, factory.throwableType, appView)) {
-                  throw CfCodeStackMapValidatingException.error(
-                      "Could not assign '" + guard.toSourceString() + "' to throwable.");
-                }
-                checkStackIsAssignable(
-                    ImmutableDeque.of(FrameType.initialized(guard)), destinationFrame.getStack());
-              });
-        });
-  }
-
-  private void checkFrameIsSet() {
-    if (currentFrame == NO_FRAME) {
-      throw CfCodeStackMapValidatingException.error("Unexpected state change");
-    }
-  }
-
-  public void checkFrameAndSet(CfFrame newFrame) {
-    if (currentFrame != NO_FRAME) {
-      checkFrame(newFrame);
-    }
-    setFrame(newFrame);
-  }
-
-  private void setFrame(CfFrame frame) {
-    assert frame != NO_FRAME;
-    currentFrame = frame.mutableCopy();
-  }
-
-  public void checkExceptionEdges() {
-    for (CfTryCatch currentCatchRange : currentCatchRanges) {
-      for (CfLabel target : currentCatchRange.targets) {
-        CfFrame destinationFrame = stateMap.get(target);
-        if (destinationFrame == null) {
-          throw CfCodeStackMapValidatingException.error("No frame for target catch range target");
+    for (CfLabel target : tryCatchRange.getTargets()) {
+      CfFrame destinationFrame = stateMap.get(target);
+      if (destinationFrame == null) {
+        return CfCodeStackMapValidatingException.invalidTryCatchRange(
+            method, tryCatchRange, "No frame for target catch range target", appView);
+      }
+      // From the spec: the handler's exception class is assignable to the class Throwable.
+      for (DexType guard : tryCatchRange.guards) {
+        if (!CfAssignability.isAssignable(guard, factory.throwableType, appView)) {
+          return CfCodeStackMapValidatingException.invalidTryCatchRange(
+              method,
+              tryCatchRange,
+              "Could not assign " + guard.getTypeName() + " to java.lang.Throwable",
+              appView);
         }
-        checkLocalsIsAssignable(currentFrame.getLocals(), destinationFrame.getLocals());
+        Deque<PreciseFrameType> sourceStack = ImmutableDeque.of(FrameType.initialized(guard));
+        AssignabilityResult assignabilityResult =
+            CfAssignability.isStackAssignable(sourceStack, destinationFrame.getStack(), appView);
+        if (assignabilityResult.isFailed()) {
+          return CfCodeStackMapValidatingException.invalidTryCatchRange(
+              method, tryCatchRange, assignabilityResult.asFailed().getMessage(), appView);
+        }
       }
     }
+    return null;
   }
 
-  public CfFrame getFrame() {
-    return currentFrame;
-  }
-
-  public void checkTarget(CfLabel label) {
-    checkFrame(stateMap.get(label));
-  }
-
-  public void checkFrame(CfFrame destinationFrame) {
-    if (destinationFrame == null) {
-      throw CfCodeStackMapValidatingException.error("No destination frame");
+  public CfFrameState checkExceptionEdges(CfFrameState state) {
+    for (CfTryCatch currentCatchRange : currentCatchRanges) {
+      for (CfLabel target : currentCatchRange.getTargets()) {
+        CfFrame destinationFrame = stateMap.get(target);
+        if (destinationFrame == null) {
+          return CfFrameState.error("No frame for target catch range target");
+        }
+        state = state.checkLocals(appView, destinationFrame);
+      }
     }
-    checkFrame(destinationFrame.getLocals(), destinationFrame.getStack());
+    return state;
   }
 
-  public void checkFrame(Int2ObjectSortedMap<FrameType> locals, Deque<FrameType> stack) {
-    checkIsAssignable(currentFrame.getLocals(), currentFrame.getStack(), locals, stack);
+  public CfFrameState checkTarget(CfFrameState state, CfLabel label) {
+    CfFrame destinationFrame = getDestinationFrame(label);
+    return destinationFrame != null
+        ? state.checkLocals(appView, destinationFrame).checkStack(appView, destinationFrame)
+        : CfFrameState.error("No destination frame");
   }
 
-  public void setNoFrame() {
-    currentFrame = NO_FRAME;
+  private CfFrame getDestinationFrame(CfLabel label) {
+    return stateMap.get(label);
   }
 
-  public boolean isUninitializedThisAndTarget(FrameType source, DexType target) {
-    if (!source.isUninitializedThis()) {
-      return false;
+  public TraversalContinuation<CfCodeDiagnostics, CfFrameState> computeStateForNextInstruction(
+      CfInstruction instruction, int instructionIndex, CfFrameState state) {
+    if (!instruction.isJump()) {
+      return TraversalContinuation.doContinue(state);
     }
-    return target == factory.objectType || target == context;
-  }
-
-  public boolean isUninitializedNewAndTarget(FrameType source, DexType target) {
-    if (!source.isUninitializedNew()) {
-      return false;
+    if (instructionIndex == code.getInstructions().size() - 1) {
+      return TraversalContinuation.doContinue(CfFrameState.bottom());
     }
-    return target == factory.objectType || target == context;
-  }
-
-  public boolean isAssignableAndInitialized(FrameType source, DexType target) {
-    if (!source.isInitialized()) {
-      return false;
+    if (instructionIndex == code.getInstructions().size() - 2
+        && code.getInstructions().get(instructionIndex + 1).isLabel()) {
+      return TraversalContinuation.doContinue(CfFrameState.bottom());
     }
-    return CfAssignability.isAssignable(source.getInitializedType(factory), target, appView);
-  }
-
-  public void checkIsAssignable(
-      FrameType source, DexType target, BiPredicate<FrameType, DexType> predicate) {
-    if (predicate.test(source, target)) {
-      return;
+    if (instruction.asJump().hasFallthrough()) {
+      return TraversalContinuation.doContinue(state);
     }
-    throw CfCodeStackMapValidatingException.error(
-        "The expected type " + source + " is not assignable to " + target.toSourceString());
-  }
-
-  public void checkIsAssignable(FrameType source, FrameType target) {
-    if (!CfAssignability.isFrameTypeAssignable(source, target, appView)) {
-      throw CfCodeStackMapValidatingException.error(
-          "The expected type " + source + " is not assignable to " + target);
+    int nextInstructionIndex = instructionIndex + 1;
+    CfInstruction nextInstruction = code.getInstructions().get(nextInstructionIndex);
+    CfFrame nextFrame = null;
+    if (nextInstruction.isFrame()) {
+      nextFrame = nextInstruction.asFrame();
+    } else if (nextInstruction.isLabel()) {
+      nextFrame = getDestinationFrame(nextInstruction.asLabel());
     }
-  }
-
-  // Based on https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.4.
-  private void checkIsAssignable(
-      Int2ObjectSortedMap<FrameType> sourceLocals,
-      Deque<FrameType> sourceStack,
-      Int2ObjectSortedMap<FrameType> destLocals,
-      Deque<FrameType> destStack) {
-    checkLocalsIsAssignable(sourceLocals, destLocals);
-    checkStackIsAssignable(sourceStack, destStack);
-  }
-
-  private void checkLocalsIsAssignable(
-      Int2ObjectSortedMap<FrameType> sourceLocals, Int2ObjectSortedMap<FrameType> destLocals) {
-    AssignabilityResult result =
-        CfAssignability.isLocalsAssignable(sourceLocals, destLocals, appView);
-    if (result.isFailed()) {
-      throw CfCodeStackMapValidatingException.error(result.asFailed().getMessage());
+    if (nextFrame != null) {
+      CfFrame currentFrameCopy = nextFrame.mutableCopy();
+      return TraversalContinuation.doContinue(
+          new ConcreteCfFrameState(
+              currentFrameCopy.getMutableLocals(),
+              currentFrameCopy.getMutableStack(),
+              currentFrameCopy.computeStackSize()));
     }
-  }
-
-  private void checkStackIsAssignable(Deque<FrameType> sourceStack, Deque<FrameType> destStack) {
-    AssignabilityResult result = CfAssignability.isStackAssignable(sourceStack, destStack, appView);
-    if (result.isFailed()) {
-      throw CfCodeStackMapValidatingException.error(result.asFailed().getMessage());
-    }
+    return TraversalContinuation.doBreak(
+        CfCodeStackMapValidatingException.invalidStackMapForInstruction(
+            method, nextInstructionIndex, nextInstruction, "Expected frame instruction", appView));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
index 490de77..e00ee93 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -109,15 +108,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // Intentionally empty.
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIf.java b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
index 78acd72..216543f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -102,20 +101,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., value →
-    // ...
-    frameBuilder.popAndDiscardInitialized(
-        type.isObject()
-            ? dexItemFactory.objectType
-            : type.toPrimitiveType().toDexType(dexItemFactory));
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
index 96f5832..2a2e561 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
@@ -8,8 +8,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -105,18 +103,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., value1, value2 →
-    // ...
-    DexType type = this.type.toDexType(dexItemFactory);
-    frameBuilder.popAndDiscardInitialized(type, type);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
index 8135aa9..3267d60 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -99,15 +98,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    frameBuilder.readLocal(var, dexItemFactory.intType);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
index fe8f36e..e253413 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -117,17 +116,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., →
-    // ..., value
-    frameBuilder.push(dexItemFactory.intType);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
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 1eb4b5d..cfd7afa 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
@@ -10,7 +10,6 @@
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -68,17 +67,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., objectref →
-    // ..., value
-    frameBuilder.popAndDiscardInitialized(getField().getHolderType()).push(getField().getType());
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java
index b7bf3ec..d18681f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java
@@ -5,15 +5,13 @@
 package com.android.tools.r8.cf.code;
 
 import static com.android.tools.r8.optimize.interfaces.analysis.ErroneousCfFrameState.formatActual;
-import static com.android.tools.r8.utils.BiPredicateUtils.or;
 
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.frame.PreciseFrameType;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -68,23 +66,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., objectref, value →
-    // ...
-    frameBuilder
-        .popAndDiscardInitialized(getField().getType())
-        .pop(
-            getField().getHolderType(),
-            or(
-                frameBuilder::isUninitializedThisAndTarget,
-                frameBuilder::isAssignableAndInitialized));
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
@@ -101,7 +82,7 @@
             (state, head) -> head.isUninitializedNew() ? error(head) : state);
   }
 
-  private ErroneousCfFrameState error(FrameType objectType) {
+  private ErroneousCfFrameState error(PreciseFrameType objectType) {
     return CfFrameState.error(
         "Frame type "
             + formatActual(objectType)
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
index e42e7e3..80da4fe 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -127,17 +126,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., objectref →
-    // ..., result
-    frameBuilder.popAndDiscardInitialized(dexItemFactory.objectType).push(dexItemFactory.intType);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
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 dc1f4de..932a283 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
@@ -13,7 +13,6 @@
 import com.android.tools.r8.graph.ClasspathMethod;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -354,12 +353,6 @@
   public abstract ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, CfCode code, ProgramMethod context);
 
-  public abstract void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory);
-
   public abstract CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index bbaf8b5..ffbd427 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -316,30 +316,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., objectref, [arg1, [arg2 ...]] →
-    // ... [ returnType ]
-    // OR, for static method calls:
-    // ..., [arg1, [arg2 ...]] →
-    // ...
-    frameBuilder.popAndDiscardInitialized(method.proto.parameters.values);
-    if (opcode == Opcodes.INVOKESPECIAL
-        && (method.isInstanceInitializer(dexItemFactory)
-            || method.mustBeInlinedIntoInstanceInitializer(appView))) {
-      frameBuilder.popAndInitialize(context.getHolderType(), method.holder);
-    } else if (opcode != Opcodes.INVOKESTATIC) {
-      frameBuilder.popInitialized(method.holder);
-    }
-    if (!method.getReturnType().isVoidType()) {
-      frameBuilder.push(method.getReturnType());
-    }
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index dc81cfc..a716425 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -168,20 +167,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., [arg1, [arg2 ...]] →
-    // ...
-    frameBuilder.popAndDiscardInitialized(callSite.methodProto.parameters.values);
-    if (callSite.methodProto.returnType != dexItemFactory.voidType) {
-      frameBuilder.push(callSite.methodProto.returnType);
-    }
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
index f132e94..698e91d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
@@ -7,10 +7,8 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.CfCodeStackMapValidatingException;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -85,17 +83,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // JSR/RET instructions cannot be verified since we have not type-checking way for addresses
-    // on the stack/locals. We have to abandon.
-    throw CfCodeStackMapValidatingException.error("Unexpected JSR/RET instruction");
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
index 7705bce..011f97e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -97,15 +96,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // Intentionally empty.
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
index 22e8e44..6831f06 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
@@ -4,12 +4,12 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -24,6 +24,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
+import com.android.tools.r8.optimize.interfaces.analysis.ErroneousCfFrameState;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -127,22 +128,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ... →
-    // ..., objectref
-    frameBuilder.push(
-        frameBuilder.readLocal(
-            getLocalIndex(),
-            type.isObject()
-                ? dexItemFactory.objectType
-                : type.toPrimitiveType().toDexType(dexItemFactory)));
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
@@ -151,6 +136,21 @@
     // ... →
     // ..., objectref
     return frame.readLocal(
-        appView, getLocalIndex(), type, (state, frameType) -> state.push(config, frameType));
+        appView,
+        getLocalIndex(),
+        type,
+        (state, frameType) ->
+            frameType.isPrecise() ? state.push(config, frameType.asPrecise()) : error(frameType));
+  }
+
+  private ErroneousCfFrameState error(FrameType frameType) {
+    assert frameType.isOneWord() || frameType.isTwoWord();
+    StringBuilder message =
+        new StringBuilder("Unexpected attempt to read local of type top at index ")
+            .append(getLocalIndex());
+    if (type.isWide()) {
+      message.append(" and ").append(getLocalIndex() + 1);
+    }
+    return CfFrameState.error(message.toString());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
index 18bf0d3..8109ca3 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
@@ -4,13 +4,11 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -179,28 +177,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., value1, value2 →
-    // ..., result
-    FrameType value1Type = FrameType.fromNumericType(type, dexItemFactory);
-    FrameType value2Type;
-    switch (opcode) {
-      case And:
-      case Or:
-      case Xor:
-        value2Type = value1Type;
-        break;
-      default:
-        value2Type = FrameType.initialized(dexItemFactory.intType);
-    }
-    frameBuilder.popAndDiscard(value1Type, value2Type).push(value1Type);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
index 9b6e582..f225050 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
@@ -4,12 +4,10 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -96,17 +94,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., objectref →
-    // ...
-    frameBuilder.pop(FrameType.initialized(dexItemFactory.objectType));
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
index 0d48a12..4c55cc4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -130,20 +129,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., count1, [count2, ...] →
-    // ..., arrayref
-    for (int i = 0; i < dimensions; i++) {
-      frameBuilder.popInitialized(dexItemFactory.intType);
-    }
-    frameBuilder.push(type);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
index 5150a97..bbed094 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
@@ -4,13 +4,11 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -121,18 +119,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., value →
-    // ..., result
-    FrameType frameType = FrameType.fromNumericType(type, dexItemFactory);
-    frameBuilder.popAndDiscard(frameType).push(frameType);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
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 94fbd54..ec6d665 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
@@ -4,13 +4,12 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -133,17 +132,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ... →
-    // ..., objectref
-    frameBuilder.push(FrameType.uninitializedNew(getLabel(), type));
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
index 6d176fa..55a451f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -166,17 +165,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., count →
-    // ..., arrayref
-    frameBuilder.popAndDiscardInitialized(dexItemFactory.intType).push(type);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java b/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
index 7814564..febd00e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
@@ -4,14 +4,12 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -119,17 +117,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ... →
-    // ..., objectref
-    frameBuilder.push(FrameType.initialized(type));
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNop.java b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
index db3ea6a..1e1cc75 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -78,15 +77,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // This is an actual Nop.
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
index f302df8..8b0e54e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
@@ -4,13 +4,11 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -192,19 +190,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., value →
-    // ..., result
-    frameBuilder
-        .popAndDiscard(FrameType.fromNumericType(from, dexItemFactory))
-        .push(FrameType.fromNumericType(to, dexItemFactory));
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
index 1df0826..f6d2d6c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -111,15 +110,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // This is a no-op.
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java b/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java
index 2d2456e..18728fd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -111,18 +110,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    for (DexField ignored : fields) {
-      frameBuilder.popInitialized(dexItemFactory.objectType);
-    }
-    frameBuilder.push(dexItemFactory.objectArrayType);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
index 6c007cc..1bb122a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -119,17 +118,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    assert !context.getReturnType().isVoidType();
-    frameBuilder.popAndDiscardInitialized(context.getReturnType());
-    frameBuilder.setNoFrame();
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
index e21e6c7..fa1227c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -93,15 +92,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    frameBuilder.setNoFrame();
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
index 345cc84..6971f49 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
@@ -4,14 +4,12 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -356,159 +354,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    switch (opcode) {
-      case Pop:
-        // ..., value →
-        // ...
-        frameBuilder.pop(FrameType.oneWord());
-        return;
-      case Pop2:
-        // ..., value2, value1 →
-        // ...
-        // or, for double and long:
-        // ..., value →
-        // ...
-        final FrameType pop = frameBuilder.pop();
-        if (pop.isSingle()) {
-          frameBuilder.pop(FrameType.oneWord());
-        }
-        return;
-      case Dup:
-        {
-          // ..., value →
-          // ..., value, value
-          FrameType topValue = frameBuilder.pop(FrameType.oneWord());
-          frameBuilder.push(topValue).push(topValue);
-          return;
-        }
-      case DupX1:
-        {
-          // ..., value2, value1 →
-          // ..., value1, value2, value1
-          FrameType value1 = frameBuilder.pop(FrameType.oneWord());
-          FrameType value2 = frameBuilder.pop(FrameType.oneWord());
-          frameBuilder.push(value1).push(value2).push(value1);
-          return;
-        }
-      case DupX2:
-        {
-          // ..., value3, value2, value1 →
-          // ..., value1, value3, value2, value1
-          // or, if value2 is double or long:
-          // ..., value2, value1 →
-          // ..., value1, value2, value1
-          FrameType value1 = frameBuilder.pop(FrameType.oneWord());
-          FrameType value2 = frameBuilder.pop();
-          if (value2.isSingle()) {
-            FrameType value3 = frameBuilder.pop(FrameType.oneWord());
-            frameBuilder.push(value1).push(value3);
-          } else {
-            frameBuilder.push(value1);
-          }
-          frameBuilder.push(value2).push(value1);
-          return;
-        }
-      case Dup2:
-        {
-          // ..., value2, value1 →
-          // ..., value2, value1, value2, value1
-          // or, for value1 being long or double:
-          // ..., value →
-          // ..., value, value
-          FrameType value1 = frameBuilder.pop();
-          if (value1.isSingle()) {
-            FrameType value2 = frameBuilder.pop(FrameType.oneWord());
-            frameBuilder.push(value2).push(value1).push(value2);
-          } else {
-            frameBuilder.push(value1);
-          }
-          frameBuilder.push(value1);
-          return;
-        }
-      case Dup2X1:
-        {
-          // ..., value3, value2, value1 →
-          // ..., value2, value1, value3, value2, value1
-          // or, for value1 being a long or double:
-          // ..., value2, value1 →
-          // ..., value1, value2, value1
-          FrameType value1 = frameBuilder.pop();
-          FrameType value2 = frameBuilder.pop(FrameType.oneWord());
-          if (value1.isSingle()) {
-            FrameType value3 = frameBuilder.pop(FrameType.oneWord());
-            frameBuilder.push(value2).push(value1).push(value3);
-          } else {
-            frameBuilder.push(value1);
-          }
-          frameBuilder.push(value2);
-          frameBuilder.push(value1);
-          return;
-        }
-      case Dup2X2:
-        {
-          // (1)
-          // ..., value4, value3, value2, value1 →
-          // ..., value2, value1, value4, value3, value2, value1
-          // (2) OR, if value1 is long or double
-          // ..., value3, value2, value1 →
-          // ..., value1, value3, value2, value1
-          // (3) OR if value3 is long or double
-          // ..., value3, value2, value1 →
-          // ..., value2, value1, value3, value2, value1
-          // (4) OR, where value1 and value2 is double or long:
-          // ..., value2, value1 →
-          // ..., value1, value2, value1
-          FrameType value1 = frameBuilder.pop();
-          if (value1.isSingle()) {
-            FrameType value2 = frameBuilder.pop(FrameType.oneWord());
-            FrameType value3 = frameBuilder.pop();
-            if (value3.isSingle()) {
-              // (1)
-              FrameType value4 = frameBuilder.pop(FrameType.oneWord());
-              frameBuilder
-                  .push(value2)
-                  .push(value1)
-                  .push(value4)
-                  .push(value3)
-                  .push(value2)
-                  .push(value1);
-            } else {
-              // (3)
-              frameBuilder.push(value2).push(value1).push(value3).push(value2).push(value1);
-            }
-          } else {
-            FrameType value2 = frameBuilder.pop();
-            if (value2.isSingle()) {
-              // (2)
-              FrameType value3 = frameBuilder.pop(FrameType.oneWord());
-              frameBuilder.push(value1).push(value3).push(value2).push(value1);
-            } else {
-              // (4)
-              frameBuilder.push(value1).push(value2).push(value1);
-            }
-          }
-          return;
-        }
-      case Swap:
-        {
-          // ..., value2, value1 →
-          // ..., value1, value2
-          FrameType value1 = frameBuilder.pop(FrameType.oneWord());
-          FrameType value2 = frameBuilder.pop(FrameType.oneWord());
-          frameBuilder.push(value1).push(value2);
-          return;
-        }
-      default:
-        throw new Unreachable("Invalid opcode for CfStackInstruction");
-    }
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
index e0d1ab1..c60a5fe 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -71,17 +70,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., →
-    // ..., value
-    frameBuilder.push(getField().getType());
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java
index dc25d3f..fa7df86 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -67,17 +66,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., value →
-    // ...
-    frameBuilder.popAndDiscardInitialized(getField().getType());
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStore.java b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
index e5406bb..1015a15 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
@@ -3,16 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
-import static com.android.tools.r8.utils.BiPredicateUtils.or;
-
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -129,53 +125,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., ref →
-    // ...
-    FrameType pop = frameBuilder.pop();
-    switch (type) {
-      case OBJECT:
-        frameBuilder.checkIsAssignable(
-            pop,
-            dexItemFactory.objectType,
-            or(
-                frameBuilder::isUninitializedThisAndTarget,
-                frameBuilder::isUninitializedNewAndTarget,
-                frameBuilder::isAssignableAndInitialized));
-        frameBuilder.storeLocal(var, pop);
-        return;
-      case INT:
-        frameBuilder.checkIsAssignable(
-            pop, dexItemFactory.intType, frameBuilder::isAssignableAndInitialized);
-        frameBuilder.storeLocal(var, FrameType.initialized(dexItemFactory.intType));
-        return;
-      case FLOAT:
-        frameBuilder.checkIsAssignable(
-            pop, dexItemFactory.floatType, frameBuilder::isAssignableAndInitialized);
-        frameBuilder.storeLocal(var, FrameType.initialized(dexItemFactory.floatType));
-        return;
-      case LONG:
-        frameBuilder.checkIsAssignable(
-            pop, dexItemFactory.longType, frameBuilder::isAssignableAndInitialized);
-        frameBuilder.storeLocal(var, FrameType.longType());
-        frameBuilder.storeLocal(var + 1, FrameType.longType());
-        return;
-      case DOUBLE:
-        frameBuilder.checkIsAssignable(
-            pop, dexItemFactory.doubleType, frameBuilder::isAssignableAndInitialized);
-        frameBuilder.storeLocal(var, FrameType.doubleType());
-        frameBuilder.storeLocal(var + 1, FrameType.doubleType());
-        return;
-      default:
-        throw new Unreachable("Unexpected type " + type);
-    }
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
index 44bb2c9..3c3f06c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -172,17 +171,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., index/key →
-    // ...
-    frameBuilder.popInitialized(dexItemFactory.intType);
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
index 126c0ad..c7bc17e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -100,19 +99,6 @@
   }
 
   @Override
-  public void evaluate(
-      CfFrameVerificationHelper frameBuilder,
-      DexMethod context,
-      AppView<?> appView,
-      DexItemFactory dexItemFactory) {
-    // ..., objectref →
-    // objectref
-    frameBuilder.popInitialized(dexItemFactory.throwableType);
-    // The exception edges are verified in CfCode since this is a throwing instruction.
-    frameBuilder.setNoFrame();
-  }
-
-  @Override
   public CfFrameState evaluate(
       CfFrameState frame,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/BaseFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/BaseFrameType.java
new file mode 100644
index 0000000..ce9f818
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/BaseFrameType.java
@@ -0,0 +1,194 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.code.frame;
+
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+
+public abstract class BaseFrameType implements FrameType {
+
+  @Override
+  public boolean isBoolean() {
+    return false;
+  }
+
+  @Override
+  public boolean isByte() {
+    return false;
+  }
+
+  @Override
+  public boolean isChar() {
+    return false;
+  }
+
+  @Override
+  public boolean isDouble() {
+    return false;
+  }
+
+  @Override
+  public boolean isFloat() {
+    return false;
+  }
+
+  @Override
+  public boolean isInt() {
+    return false;
+  }
+
+  @Override
+  public boolean isLong() {
+    return false;
+  }
+
+  @Override
+  public boolean isShort() {
+    return false;
+  }
+
+  @Override
+  public boolean isNullType() {
+    return false;
+  }
+
+  @Override
+  public boolean isObject() {
+    return false;
+  }
+
+  @Override
+  public DexType getObjectType(DexType context) {
+    assert false : "Unexpected use of getObjectType() for non-object FrameType";
+    return null;
+  }
+
+  @Override
+  public boolean isPrecise() {
+    assert isOneWord() || isTwoWord();
+    return false;
+  }
+
+  @Override
+  public PreciseFrameType asPrecise() {
+    assert isOneWord() || isTwoWord();
+    return null;
+  }
+
+  @Override
+  public boolean isPrimitive() {
+    return false;
+  }
+
+  @Override
+  public PrimitiveFrameType asPrimitive() {
+    return null;
+  }
+
+  @Override
+  public final boolean isSingle() {
+    return !isWide();
+  }
+
+  @Override
+  public SingleFrameType asSingle() {
+    return null;
+  }
+
+  @Override
+  public SinglePrimitiveFrameType asSinglePrimitive() {
+    return null;
+  }
+
+  @Override
+  public InitializedReferenceFrameType asInitializedReferenceType() {
+    return null;
+  }
+
+  @Override
+  public boolean isWide() {
+    return false;
+  }
+
+  @Override
+  public WideFrameType asWide() {
+    return null;
+  }
+
+  @Override
+  public int getWidth() {
+    assert isSingle();
+    return 1;
+  }
+
+  @Override
+  public boolean isUninitializedNew() {
+    return false;
+  }
+
+  @Override
+  public UninitializedNew asUninitializedNew() {
+    return null;
+  }
+
+  @Override
+  public boolean isUninitialized() {
+    return false;
+  }
+
+  @Override
+  public UninitializedFrameType asUninitialized() {
+    return null;
+  }
+
+  @Override
+  public CfLabel getUninitializedLabel() {
+    return null;
+  }
+
+  @Override
+  public boolean isUninitializedThis() {
+    return false;
+  }
+
+  @Override
+  public UninitializedThis asUninitializedThis() {
+    return null;
+  }
+
+  @Override
+  public boolean isInitialized() {
+    return false;
+  }
+
+  @Override
+  public DexType getInitializedType(DexItemFactory dexItemFactory) {
+    return null;
+  }
+
+  @Override
+  public DexType getUninitializedNewType() {
+    return null;
+  }
+
+  @Override
+  public boolean isOneWord() {
+    return false;
+  }
+
+  @Override
+  public boolean isTwoWord() {
+    return false;
+  }
+
+  BaseFrameType() {}
+
+  @Override
+  public abstract boolean equals(Object obj);
+
+  @Override
+  public abstract int hashCode();
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/BooleanFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/BooleanFrameType.java
new file mode 100644
index 0000000..faca255
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/BooleanFrameType.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.code.frame;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.naming.NamingLens;
+
+public class BooleanFrameType extends SinglePrimitiveFrameType {
+
+  static final BooleanFrameType SINGLETON = new BooleanFrameType();
+
+  private BooleanFrameType() {}
+
+  @Override
+  public DexType getInitializedType(DexItemFactory dexItemFactory) {
+    return dexItemFactory.booleanType;
+  }
+
+  @Override
+  public String getTypeName() {
+    return "boolean";
+  }
+
+  @Override
+  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+    throw new Unreachable("Unexpected value type: " + this);
+  }
+
+  @Override
+  public boolean hasIntVerificationType() {
+    return true;
+  }
+
+  @Override
+  public boolean isBoolean() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/ByteFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/ByteFrameType.java
new file mode 100644
index 0000000..c55f98f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/ByteFrameType.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.code.frame;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.naming.NamingLens;
+
+public class ByteFrameType extends SinglePrimitiveFrameType {
+
+  static final ByteFrameType SINGLETON = new ByteFrameType();
+
+  private ByteFrameType() {}
+
+  @Override
+  public DexType getInitializedType(DexItemFactory dexItemFactory) {
+    return dexItemFactory.byteType;
+  }
+
+  @Override
+  public String getTypeName() {
+    return "byte";
+  }
+
+  @Override
+  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+    throw new Unreachable("Unexpected value type: " + this);
+  }
+
+  @Override
+  public boolean hasIntVerificationType() {
+    return true;
+  }
+
+  @Override
+  public boolean isByte() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/CharFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/CharFrameType.java
new file mode 100644
index 0000000..99fadc1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/CharFrameType.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.code.frame;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.naming.NamingLens;
+
+public class CharFrameType extends SinglePrimitiveFrameType {
+
+  static final CharFrameType SINGLETON = new CharFrameType();
+
+  private CharFrameType() {}
+
+  @Override
+  public DexType getInitializedType(DexItemFactory dexItemFactory) {
+    return dexItemFactory.charType;
+  }
+
+  @Override
+  public String getTypeName() {
+    return "char";
+  }
+
+  @Override
+  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+    throw new Unreachable("Unexpected value type: " + this);
+  }
+
+  @Override
+  public boolean hasIntVerificationType() {
+    return true;
+  }
+
+  @Override
+  public boolean isChar() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/DoubleFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/DoubleFrameType.java
new file mode 100644
index 0000000..93c0687
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/DoubleFrameType.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.code.frame;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.Opcodes;
+
+public class DoubleFrameType extends WidePrimitiveFrameType {
+
+  static final DoubleFrameType SINGLETON = new DoubleFrameType();
+
+  private DoubleFrameType() {}
+
+  @Override
+  public boolean isDouble() {
+    return true;
+  }
+
+  @Override
+  public DexType getInitializedType(DexItemFactory dexItemFactory) {
+    return dexItemFactory.doubleType;
+  }
+
+  @Override
+  public String getTypeName() {
+    return "double";
+  }
+
+  @Override
+  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+    return Opcodes.DOUBLE;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/FloatFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/FloatFrameType.java
new file mode 100644
index 0000000..6789e2e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/FloatFrameType.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.code.frame;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.Opcodes;
+
+public class FloatFrameType extends SinglePrimitiveFrameType {
+
+  static final FloatFrameType SINGLETON = new FloatFrameType();
+
+  private FloatFrameType() {}
+
+  @Override
+  public DexType getInitializedType(DexItemFactory dexItemFactory) {
+    return dexItemFactory.floatType;
+  }
+
+  @Override
+  public String getTypeName() {
+    return "float";
+  }
+
+  @Override
+  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+    return Opcodes.FLOAT;
+  }
+
+  @Override
+  public boolean isFloat() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/FrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/FrameType.java
new file mode 100644
index 0000000..6522c7e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/FrameType.java
@@ -0,0 +1,198 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.code.frame;
+
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.naming.NamingLens;
+import java.util.function.Function;
+
+public interface FrameType {
+
+  static BooleanFrameType booleanType() {
+    return BooleanFrameType.SINGLETON;
+  }
+
+  static ByteFrameType byteType() {
+    return ByteFrameType.SINGLETON;
+  }
+
+  static CharFrameType charType() {
+    return CharFrameType.SINGLETON;
+  }
+
+  static DoubleFrameType doubleType() {
+    return DoubleFrameType.SINGLETON;
+  }
+
+  static FloatFrameType floatType() {
+    return FloatFrameType.SINGLETON;
+  }
+
+  static IntFrameType intType() {
+    return IntFrameType.SINGLETON;
+  }
+
+  static LongFrameType longType() {
+    return LongFrameType.SINGLETON;
+  }
+
+  static ShortFrameType shortType() {
+    return ShortFrameType.SINGLETON;
+  }
+
+  static InitializedFrameType initialized(DexType type) {
+    if (type.isPrimitiveType()) {
+      return primitive(type);
+    }
+    return new InitializedReferenceFrameType(type);
+  }
+
+  static PrimitiveFrameType primitive(DexType type) {
+    assert type.isPrimitiveType();
+    char c = (char) type.getDescriptor().content[0];
+    switch (c) {
+      case 'Z':
+        return booleanType();
+      case 'B':
+        return byteType();
+      case 'C':
+        return charType();
+      case 'D':
+        return doubleType();
+      case 'F':
+        return floatType();
+      case 'I':
+        return intType();
+      case 'J':
+        return longType();
+      case 'S':
+        return shortType();
+      default:
+        throw new Unreachable("Unexpected primitive type: " + type.getTypeName());
+    }
+  }
+
+  static UninitializedNew uninitializedNew(CfLabel label, DexType typeToInitialize) {
+    return new UninitializedNew(label, typeToInitialize);
+  }
+
+  static UninitializedThis uninitializedThis() {
+    return UninitializedThis.SINGLETON;
+  }
+
+  static OneWord oneWord() {
+    return OneWord.SINGLETON;
+  }
+
+  static TwoWord twoWord() {
+    return TwoWord.SINGLETON;
+  }
+
+  static PrimitiveFrameType fromNumericType(NumericType numericType, DexItemFactory factory) {
+    return FrameType.primitive(numericType.toDexType(factory));
+  }
+
+  static InitializedFrameType fromPreciseMemberType(MemberType memberType, DexItemFactory factory) {
+    assert memberType.isPrecise();
+    switch (memberType) {
+      case OBJECT:
+        return FrameType.initialized(factory.objectType);
+      case BOOLEAN_OR_BYTE:
+      case CHAR:
+      case SHORT:
+      case INT:
+        return intType();
+      case FLOAT:
+        return floatType();
+      case LONG:
+        return longType();
+      case DOUBLE:
+        return doubleType();
+      default:
+        throw new Unreachable("Unexpected MemberType: " + memberType);
+    }
+  }
+
+  DexType getInitializedType(DexItemFactory dexItemFactory);
+
+  DexType getObjectType(DexType context);
+
+  Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens);
+
+  CfLabel getUninitializedLabel();
+
+  DexType getUninitializedNewType();
+
+  int getWidth();
+
+  boolean isBoolean();
+
+  boolean isByte();
+
+  boolean isChar();
+
+  boolean isDouble();
+
+  boolean isFloat();
+
+  boolean isInitialized();
+
+  InitializedReferenceFrameType asInitializedReferenceType();
+
+  boolean isInt();
+
+  boolean isLong();
+
+  boolean isNullType();
+
+  boolean isObject();
+
+  boolean isOneWord();
+
+  boolean isPrecise();
+
+  PreciseFrameType asPrecise();
+
+  boolean isPrimitive();
+
+  PrimitiveFrameType asPrimitive();
+
+  boolean isShort();
+
+  boolean isSingle();
+
+  SingleFrameType asSingle();
+
+  SinglePrimitiveFrameType asSinglePrimitive();
+
+  boolean isTwoWord();
+
+  boolean isUninitialized();
+
+  UninitializedFrameType asUninitialized();
+
+  boolean isUninitializedNew();
+
+  UninitializedNew asUninitializedNew();
+
+  boolean isUninitializedThis();
+
+  UninitializedThis asUninitializedThis();
+
+  boolean isWide();
+
+  WideFrameType asWide();
+
+  default FrameType map(Function<DexType, DexType> fn) {
+    assert !isPrecise();
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/InitializedFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedFrameType.java
new file mode 100644
index 0000000..f2a3d21
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedFrameType.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 com.android.tools.r8.cf.code.frame;
+
+public interface InitializedFrameType extends PreciseFrameType {}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/InitializedReferenceFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedReferenceFrameType.java
new file mode 100644
index 0000000..c2a2152
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedReferenceFrameType.java
@@ -0,0 +1,147 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.code.frame;
+
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.Opcodes;
+
+public class InitializedReferenceFrameType extends BaseFrameType
+    implements InitializedFrameType, SingleFrameType {
+
+  private final DexType type;
+
+  public InitializedReferenceFrameType(DexType type) {
+    assert type != null;
+    assert type.isReferenceType();
+    this.type = type;
+  }
+
+  @Override
+  public boolean isPrecise() {
+    return true;
+  }
+
+  @Override
+  public PreciseFrameType asPrecise() {
+    return this;
+  }
+
+  @Override
+  public InitializedReferenceFrameType asInitializedReferenceType() {
+    return this;
+  }
+
+  @Override
+  public SingleFrameType join(SingleFrameType frameType) {
+    if (equals(frameType)) {
+      return this;
+    }
+    if (frameType.isOneWord() || frameType.isPrimitive() || frameType.isUninitialized()) {
+      return FrameType.oneWord();
+    }
+    DexType otherType = frameType.asInitializedReferenceType().getInitializedType();
+    assert type != otherType;
+    assert type.isReferenceType();
+    if (isNullType()) {
+      return otherType.isReferenceType() ? frameType : FrameType.oneWord();
+    }
+    if (frameType.isNullType()) {
+      return this;
+    }
+    assert type.isArrayType() || type.isClassType();
+    assert otherType.isArrayType() || otherType.isClassType();
+    // TODO(b/214496607): Implement join of different reference types using class hierarchy.
+    throw new Unimplemented();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj == null || getClass() != obj.getClass()) {
+      return false;
+    }
+    InitializedReferenceFrameType initializedType = (InitializedReferenceFrameType) obj;
+    return type == initializedType.type;
+  }
+
+  @Override
+  public int hashCode() {
+    return type.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return "Initialized(" + type.toString() + ")";
+  }
+
+  @Override
+  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+    DexType rewrittenType = graphLens.lookupType(type);
+    if (rewrittenType == DexItemFactory.nullValueType) {
+      return Opcodes.NULL;
+    }
+    switch (rewrittenType.toShorty()) {
+      case 'L':
+        return namingLens.lookupInternalName(rewrittenType);
+      case 'I':
+        return Opcodes.INTEGER;
+      case 'F':
+        return Opcodes.FLOAT;
+      case 'J':
+        return Opcodes.LONG;
+      case 'D':
+        return Opcodes.DOUBLE;
+      default:
+        throw new Unreachable("Unexpected value type: " + rewrittenType);
+    }
+  }
+
+  @Override
+  public SingleFrameType asSingle() {
+    return this;
+  }
+
+  @Override
+  public boolean isWide() {
+    return false;
+  }
+
+  @Override
+  public boolean isInitialized() {
+    return true;
+  }
+
+  public DexType getInitializedType() {
+    return type;
+  }
+
+  @Override
+  public DexType getInitializedType(DexItemFactory dexItemFactory) {
+    return getInitializedType();
+  }
+
+  @Override
+  public boolean isNullType() {
+    return type.isNullValueType();
+  }
+
+  @Override
+  public boolean isObject() {
+    return type.isReferenceType();
+  }
+
+  @Override
+  public DexType getObjectType(DexType context) {
+    assert isObject() : "Unexpected use of getObjectType() for non-object FrameType";
+    return type;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/IntFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/IntFrameType.java
new file mode 100644
index 0000000..23fd119
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/IntFrameType.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.code.frame;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.Opcodes;
+
+public class IntFrameType extends SinglePrimitiveFrameType {
+
+  static final IntFrameType SINGLETON = new IntFrameType();
+
+  private IntFrameType() {}
+
+  @Override
+  public DexType getInitializedType(DexItemFactory dexItemFactory) {
+    return dexItemFactory.intType;
+  }
+
+  @Override
+  public String getTypeName() {
+    return "int";
+  }
+
+  @Override
+  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+    return Opcodes.INTEGER;
+  }
+
+  @Override
+  public boolean hasIntVerificationType() {
+    return true;
+  }
+
+  @Override
+  public boolean isInt() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/LongFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/LongFrameType.java
new file mode 100644
index 0000000..1fb59c7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/LongFrameType.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.code.frame;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.Opcodes;
+
+public class LongFrameType extends WidePrimitiveFrameType {
+
+  static final LongFrameType SINGLETON = new LongFrameType();
+
+  private LongFrameType() {}
+
+  @Override
+  public boolean isLong() {
+    return true;
+  }
+
+  @Override
+  public DexType getInitializedType(DexItemFactory dexItemFactory) {
+    return dexItemFactory.longType;
+  }
+
+  @Override
+  public String getTypeName() {
+    return "long";
+  }
+
+  @Override
+  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+    return Opcodes.LONG;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/OneWord.java b/src/main/java/com/android/tools/r8/cf/code/frame/OneWord.java
new file mode 100644
index 0000000..9ac5082
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/OneWord.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.cf.code.frame;
+
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.Opcodes;
+
+public class OneWord extends SingletonFrameType implements SingleFrameType {
+
+  static final OneWord SINGLETON = new OneWord();
+
+  private OneWord() {}
+
+  @Override
+  public boolean isOneWord() {
+    return true;
+  }
+
+  @Override
+  public SingleFrameType asSingle() {
+    return this;
+  }
+
+  @Override
+  public SingleFrameType join(SingleFrameType frameType) {
+    return this;
+  }
+
+  @Override
+  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+    return Opcodes.TOP;
+  }
+
+  @Override
+  public String toString() {
+    return "oneword";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/PreciseFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/PreciseFrameType.java
new file mode 100644
index 0000000..aa105e0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/PreciseFrameType.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.code.frame;
+
+import com.android.tools.r8.graph.DexType;
+import java.util.function.Function;
+
+public interface PreciseFrameType extends FrameType {
+
+  @Override
+  default PreciseFrameType map(Function<DexType, DexType> fn) {
+    if (isObject()) {
+      if (isInitialized()) {
+        DexType type = asInitializedReferenceType().getInitializedType();
+        DexType newType = fn.apply(type);
+        if (type != newType) {
+          return FrameType.initialized(newType);
+        }
+      }
+      if (isUninitializedNew()) {
+        DexType type = getUninitializedNewType();
+        DexType newType = fn.apply(type);
+        if (type != newType) {
+          return FrameType.uninitializedNew(getUninitializedLabel(), newType);
+        }
+      }
+    }
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/PrimitiveFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/PrimitiveFrameType.java
index 2db1925..446261a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/PrimitiveFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/PrimitiveFrameType.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.cf.code.frame;
 
-public interface PrimitiveFrameType {
+public interface PrimitiveFrameType extends InitializedFrameType {
 
   String getTypeName();
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/ShortFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/ShortFrameType.java
new file mode 100644
index 0000000..0b64a52
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/ShortFrameType.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.code.frame;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.naming.NamingLens;
+
+public class ShortFrameType extends SinglePrimitiveFrameType {
+
+  static final ShortFrameType SINGLETON = new ShortFrameType();
+
+  private ShortFrameType() {}
+
+  @Override
+  public DexType getInitializedType(DexItemFactory dexItemFactory) {
+    return dexItemFactory.shortType;
+  }
+
+  @Override
+  public String getTypeName() {
+    return "short";
+  }
+
+  @Override
+  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+    throw new Unreachable("Unexpected value type: " + this);
+  }
+
+  @Override
+  public boolean hasIntVerificationType() {
+    return true;
+  }
+
+  @Override
+  public boolean isShort() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/SingleFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/SingleFrameType.java
index ce215b6..657d493 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/SingleFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/SingleFrameType.java
@@ -4,37 +4,8 @@
 
 package com.android.tools.r8.cf.code.frame;
 
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
-import com.android.tools.r8.cf.code.CfFrame.SingleInitializedType;
-import com.android.tools.r8.cf.code.CfFrame.SinglePrimitiveFrameType;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
 
-public interface SingleFrameType {
-
-  FrameType asFrameType();
-
-  boolean isInitialized();
-
-  DexType getInitializedType(DexItemFactory dexItemFactory);
-
-  boolean isInt();
-
-  SingleInitializedType asSingleInitializedType();
-
-  boolean isNullType();
-
-  boolean isOneWord();
-
-  boolean isPrimitive();
-
-  SinglePrimitiveFrameType asSinglePrimitive();
-
-  boolean isUninitializedNew();
-
-  DexType getUninitializedNewType();
-
-  boolean isUninitializedObject();
+public interface SingleFrameType extends FrameType {
 
   SingleFrameType join(SingleFrameType frameType);
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/SinglePrimitiveFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/SinglePrimitiveFrameType.java
new file mode 100644
index 0000000..69ba4b6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/SinglePrimitiveFrameType.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.code.frame;
+
+public abstract class SinglePrimitiveFrameType extends SingletonFrameType
+    implements PrimitiveFrameType, SingleFrameType {
+
+  public boolean hasIntVerificationType() {
+    return false;
+  }
+
+  @Override
+  public final boolean isInitialized() {
+    return true;
+  }
+
+  @Override
+  public final boolean isPrecise() {
+    return true;
+  }
+
+  @Override
+  public PreciseFrameType asPrecise() {
+    return this;
+  }
+
+  @Override
+  public final boolean isPrimitive() {
+    return true;
+  }
+
+  @Override
+  public PrimitiveFrameType asPrimitive() {
+    return this;
+  }
+
+  @Override
+  public final SingleFrameType asSingle() {
+    return this;
+  }
+
+  @Override
+  public final SinglePrimitiveFrameType asSinglePrimitive() {
+    return this;
+  }
+
+  @Override
+  public final SingleFrameType join(SingleFrameType frameType) {
+    if (this == frameType) {
+      return this;
+    }
+    if (hasIntVerificationType()
+        && frameType.isPrimitive()
+        && frameType.asSinglePrimitive().hasIntVerificationType()) {
+      return FrameType.intType();
+    }
+    return FrameType.oneWord();
+  }
+
+  @Override
+  public final String toString() {
+    return getTypeName();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/SingletonFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/SingletonFrameType.java
new file mode 100644
index 0000000..a8759af
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/SingletonFrameType.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.code.frame;
+
+public abstract class SingletonFrameType extends BaseFrameType {
+
+  @Override
+  public final boolean equals(Object obj) {
+    return this == obj;
+  }
+
+  @Override
+  public final int hashCode() {
+    return System.identityHashCode(this);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/TwoWord.java b/src/main/java/com/android/tools/r8/cf/code/frame/TwoWord.java
new file mode 100644
index 0000000..23a4ca1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/TwoWord.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.code.frame;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.naming.NamingLens;
+
+public class TwoWord extends SingletonFrameType implements WideFrameType {
+
+  static final TwoWord SINGLETON = new TwoWord();
+
+  private TwoWord() {}
+
+  @Override
+  public boolean isTwoWord() {
+    return true;
+  }
+
+  @Override
+  public boolean isWide() {
+    return true;
+  }
+
+  @Override
+  public WideFrameType asWide() {
+    return this;
+  }
+
+  @Override
+  public int getWidth() {
+    return 2;
+  }
+
+  @Override
+  public WideFrameType join(WideFrameType frameType) {
+    // The join of wide with one of {double, long, wide} is wide.
+    return this;
+  }
+
+  @Override
+  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+    throw new Unreachable("Should only be used for verification");
+  }
+
+  @Override
+  public String toString() {
+    return "twoword";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedFrameType.java
new file mode 100644
index 0000000..a31453d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedFrameType.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.code.frame;
+
+public abstract class UninitializedFrameType extends BaseFrameType
+    implements PreciseFrameType, SingleFrameType {
+
+  @Override
+  public boolean isObject() {
+    return true;
+  }
+
+  @Override
+  public boolean isPrecise() {
+    return true;
+  }
+
+  @Override
+  public PreciseFrameType asPrecise() {
+    return this;
+  }
+
+  @Override
+  public SingleFrameType asSingle() {
+    return this;
+  }
+
+  @Override
+  public boolean isUninitialized() {
+    return true;
+  }
+
+  @Override
+  public UninitializedFrameType asUninitialized() {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedNew.java b/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedNew.java
new file mode 100644
index 0000000..03eff42
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedNew.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.cf.code.frame;
+
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.naming.NamingLens;
+import java.util.Objects;
+
+public class UninitializedNew extends UninitializedFrameType {
+
+  private final CfLabel label;
+  private final DexType type;
+
+  public UninitializedNew(CfLabel label, DexType type) {
+    this.label = label;
+    this.type = type;
+  }
+
+  @Override
+  public DexType getObjectType(DexType context) {
+    return type;
+  }
+
+  @Override
+  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+    return label.getLabel();
+  }
+
+  @Override
+  public CfLabel getUninitializedLabel() {
+    return label;
+  }
+
+  @Override
+  public DexType getUninitializedNewType() {
+    return type;
+  }
+
+  @Override
+  public boolean isUninitializedNew() {
+    return true;
+  }
+
+  @Override
+  public UninitializedNew asUninitializedNew() {
+    return this;
+  }
+
+  @Override
+  public SingleFrameType join(SingleFrameType frameType) {
+    return equals(frameType) ? this : FrameType.oneWord();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    UninitializedNew uninitializedNew = (UninitializedNew) o;
+    return label == uninitializedNew.label && type == uninitializedNew.type;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(label, type);
+  }
+
+  @Override
+  public String toString() {
+    return "uninitialized new";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedThis.java b/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedThis.java
new file mode 100644
index 0000000..69d1ecb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedThis.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.code.frame;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.Opcodes;
+
+public class UninitializedThis extends UninitializedFrameType {
+
+  static final UninitializedThis SINGLETON = new UninitializedThis();
+
+  private UninitializedThis() {}
+
+  @Override
+  public DexType getObjectType(DexType context) {
+    return context;
+  }
+
+  @Override
+  public Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+    return Opcodes.UNINITIALIZED_THIS;
+  }
+
+  @Override
+  public boolean isUninitializedThis() {
+    return true;
+  }
+
+  @Override
+  public UninitializedThis asUninitializedThis() {
+    return this;
+  }
+
+  @Override
+  public SingleFrameType join(SingleFrameType frameType) {
+    if (this == frameType) {
+      return this;
+    }
+    return FrameType.oneWord();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    return this == obj;
+  }
+
+  @Override
+  public int hashCode() {
+    return System.identityHashCode(this);
+  }
+
+  @Override
+  public String toString() {
+    return "uninitialized this";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/WideFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/WideFrameType.java
index 027c2537..d780cf4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/WideFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/WideFrameType.java
@@ -4,17 +4,8 @@
 
 package com.android.tools.r8.cf.code.frame;
 
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 
-public interface WideFrameType {
-
-  FrameType asFrameType();
-
-  boolean isDouble();
-
-  boolean isLong();
-
-  boolean isTwoWord();
+public interface WideFrameType extends FrameType {
 
   default boolean lessThanOrEqualTo(WideFrameType frameType) {
     return join(frameType) == frameType;
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/WidePrimitiveFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/WidePrimitiveFrameType.java
new file mode 100644
index 0000000..11f7b75
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/WidePrimitiveFrameType.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.code.frame;
+
+public abstract class WidePrimitiveFrameType extends SingletonFrameType
+    implements PrimitiveFrameType, WideFrameType {
+
+  @Override
+  public boolean isInitialized() {
+    return true;
+  }
+
+  @Override
+  public boolean isPrecise() {
+    return true;
+  }
+
+  @Override
+  public PreciseFrameType asPrecise() {
+    return this;
+  }
+
+  @Override
+  public boolean isPrimitive() {
+    return true;
+  }
+
+  @Override
+  public PrimitiveFrameType asPrimitive() {
+    return this;
+  }
+
+  @Override
+  public boolean isWide() {
+    return true;
+  }
+
+  @Override
+  public WideFrameType asWide() {
+    return this;
+  }
+
+  @Override
+  public int getWidth() {
+    return 2;
+  }
+
+  @Override
+  public WideFrameType join(WideFrameType frameType) {
+    return this == frameType ? this : FrameType.twoWord();
+  }
+
+  @Override
+  public final String toString() {
+    return getTypeName();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index d2cff23..95358b2 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -727,6 +727,18 @@
               options.itemFactory));
     }
 
+    if (clazz.isNestHost()) {
+      annotations.add(
+          DexAnnotation.createNestMembersAnnotation(
+              clazz.getNestMembersClassAttributes(), options.itemFactory));
+    }
+
+    if (clazz.isNestMember()) {
+      annotations.add(
+          DexAnnotation.createNestHostAnnotation(
+              clazz.getNestHostClassAttribute(), options.itemFactory));
+    }
+
     if (!annotations.isEmpty()) {
       // Append the annotations to annotations array of the class.
       DexAnnotation[] copy =
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 8705bb4..10132f3 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -61,6 +61,8 @@
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.NestHostClassAttribute;
+import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.logging.Log;
@@ -855,8 +857,8 @@
               superclass,
               typeListAt(interfacesOffsets[i]),
               source,
-              null,
-              Collections.emptyList(),
+              attrs.nestHostAttribute,
+              attrs.nestMembersAttribute,
               attrs.getEnclosingMethodAttribute(),
               attrs.getInnerClasses(),
               attrs.classSignature,
@@ -1423,6 +1425,8 @@
     private List<InnerClassAttribute> innerClasses = null;
     private List<DexAnnotation> lazyAnnotations = null;
     private ClassSignature classSignature = ClassSignature.noSignature();
+    private NestHostClassAttribute nestHostAttribute;
+    private List<NestMemberClassAttribute> nestMembersAttribute = Collections.emptyList();
 
     public DexAnnotationSet getAnnotations() {
       if (lazyAnnotations != null) {
@@ -1486,6 +1490,20 @@
           classSignature =
               GenericSignature.parseClassSignature(
                   type.getName(), signature, origin, factory, options.reporter);
+        } else if (DexAnnotation.isNestHostAnnotation(annotation, factory)) {
+          ensureAnnotations(i);
+          nestHostAttribute =
+              new NestHostClassAttribute(
+                  DexAnnotation.getNestHostFromAnnotation(annotation, factory));
+        } else if (DexAnnotation.isNestMembersAnnotation(annotation, factory)) {
+          ensureAnnotations(i);
+          List<DexType> members = DexAnnotation.getNestMembersFromAnnotation(annotation, factory);
+          if (members != null) {
+            nestMembersAttribute = new ArrayList<>(members.size());
+            for (DexType member : members) {
+              nestMembersAttribute.add(new NestMemberClassAttribute(member));
+            }
+          }
         } else {
           copyAnnotation(annotation);
         }
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 321c775..42d0bcd 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfFrameVerificationHelper;
 import com.android.tools.r8.cf.code.CfIinc;
 import com.android.tools.r8.cf.code.CfInstruction;
@@ -18,6 +17,7 @@
 import com.android.tools.r8.cf.code.CfPosition;
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.dex.code.CfOrDexInstruction;
 import com.android.tools.r8.dex.code.DexBase5Format;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
@@ -31,7 +31,6 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
-import com.android.tools.r8.ir.conversion.ExtraParameter;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
@@ -40,9 +39,12 @@
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
+import com.android.tools.r8.optimize.interfaces.analysis.ConcreteCfFrameState;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralItem;
@@ -950,60 +952,60 @@
       return reportStackMapError(
           CfCodeStackMapValidatingException.noFramesForMethodWithJumps(method, appView), appView);
     }
-    CfFrameVerificationHelper builder =
-        new CfFrameVerificationHelper(
-            appView, previousMethodSignature.getHolderType(), stateMap, tryCatchRanges, maxStack);
-    for (CfTryCatch tryCatchRange : tryCatchRanges) {
-      try {
-        builder.checkTryCatchRange(tryCatchRange);
-      } catch (CfCodeStackMapValidatingException ex) {
-        return reportStackMapError(
-            CfCodeStackMapValidatingException.invalidTryCatchRange(
-                method, tryCatchRange, ex.getMessage(), appView),
-            appView);
-      }
+    CfFrameVerificationHelper helper =
+        new CfFrameVerificationHelper(appView, this, method, stateMap, tryCatchRanges);
+    CfCodeDiagnostics diagnostics = helper.checkTryCatchRanges();
+    if (diagnostics != null) {
+      return reportStackMapError(diagnostics, appView);
     }
-    if (stateMap.containsKey(null)) {
-      assert !shouldComputeInitialFrame();
-      builder.checkFrameAndSet(stateMap.get(null));
-    } else if (shouldComputeInitialFrame()) {
-      builder.checkFrameAndSet(
-          computeInitialFrame(
-              appView, previousMethodSignature, previousMethodSignatureIsInstance, protoChanges));
+    TraversalContinuation<CfCodeDiagnostics, CfFrameState> initialState =
+        computeInitialState(
+            appView, helper, method, previousMethodSignature, previousMethodSignatureIsInstance);
+    if (initialState.shouldBreak()) {
+      return reportStackMapError(initialState.asBreak().getValue(), appView);
     }
+    CfFrameState state = initialState.asContinue().getValue();
     for (int i = 0; i < instructions.size(); i++) {
       CfInstruction instruction = instructions.get(i);
-      try {
-        // Check the exceptional edge prior to evaluating the instruction. The local state is stable
-        // at this point as store operations are not throwing and the current stack does not
-        // affect the exceptional transfer (the exception edge is always a singleton stack).
-        if (instruction.canThrow()) {
-          assert !instruction.isStore();
-          builder.checkExceptionEdges();
-        }
-        if (instruction.isLabel()) {
-          builder.seenLabel(instruction.asLabel());
-        }
-        instruction.evaluate(builder, previousMethodSignature, appView, appView.dexItemFactory());
-        if (instruction.isJumpWithNormalTarget()) {
-          CfInstruction fallthroughInstruction =
-              (i + 1) < instructions.size() ? instructions.get(i + 1) : null;
-          instruction.forEachNormalTarget(
-              target -> {
-                if (target != fallthroughInstruction) {
-                  assert target.isLabel();
-                  builder.checkTarget(target.asLabel());
-                }
-              },
-              fallthroughInstruction);
-          if (!instruction.asJump().hasFallthrough()) {
-            builder.setNoFrame();
-          }
-        }
-      } catch (CfCodeStackMapValidatingException ex) {
+      assert !state.isError();
+      // Check the exceptional edge prior to evaluating the instruction. The local state is stable
+      // at this point as store operations are not throwing and the current stack does not
+      // affect the exceptional transfer (the exception edge is always a singleton stack).
+      if (instruction.canThrow()) {
+        assert !instruction.isStore();
+        state = helper.checkExceptionEdges(state);
+      }
+      if (instruction.isLabel()) {
+        helper.seenLabel(instruction.asLabel());
+      }
+      state = instruction.evaluate(state, appView, helper, appView.dexItemFactory());
+      if (instruction.isJumpWithNormalTarget()) {
+        CfInstruction fallthroughInstruction =
+            (i + 1) < instructions.size() ? instructions.get(i + 1) : null;
+        TraversalContinuation<CfCodeDiagnostics, CfFrameState> traversalContinuation =
+            instruction.traverseNormalTargets(
+                (target, currentState) -> {
+                  if (target != fallthroughInstruction) {
+                    assert target.isLabel();
+                    currentState = helper.checkTarget(currentState, target.asLabel());
+                  }
+                  return TraversalContinuation.doContinue(currentState);
+                },
+                fallthroughInstruction,
+                state);
+        state = traversalContinuation.asContinue().getValue();
+      }
+      TraversalContinuation<CfCodeDiagnostics, CfFrameState> traversalContinuation =
+          helper.computeStateForNextInstruction(instruction, i, state);
+      if (traversalContinuation.isContinue()) {
+        state = traversalContinuation.asContinue().getValue();
+      } else {
+        return reportStackMapError(traversalContinuation.asBreak().getValue(), appView);
+      }
+      if (state.isError()) {
         return reportStackMapError(
             CfCodeStackMapValidatingException.invalidStackMapForInstruction(
-                method, i, instruction, ex.getMessage(), appView),
+                method, i, instruction, state.asError().getMessage(), appView),
             appView);
       }
     }
@@ -1037,40 +1039,37 @@
     throw new Unreachable("Instruction " + instruction + " should be in instructions");
   }
 
-  private boolean shouldComputeInitialFrame() {
-    for (CfInstruction instruction : instructions) {
-      if (instruction.isFrame()) {
-        return false;
-      } else if (!instruction.isLabel() && !instruction.isPosition()) {
-        return true;
-      }
-    }
-    // We should never see a method with only labels and positions.
-    assert false;
-    return true;
-  }
-
-  private CfFrame computeInitialFrame(
+  private TraversalContinuation<CfCodeDiagnostics, CfFrameState> computeInitialState(
       AppView<?> appView,
-      DexMethod method,
-      boolean isInstance,
-      RewrittenPrototypeDescription prototypeChanges) {
+      CfFrameVerificationHelper helper,
+      ProgramMethod method,
+      DexMethod previousMethodSignature,
+      boolean previousMethodSignatureIsInstance) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
-    CfFrame.Builder builder = CfFrame.builder();
-    if (isInstance) {
-      builder.appendLocal(
-          method.isInstanceInitializer(dexItemFactory)
-                  || method.mustBeInlinedIntoInstanceInitializer(appView)
-                  || method.isHorizontallyMergedInstanceInitializer(dexItemFactory)
-              ? FrameType.uninitializedThis()
-              : FrameType.initialized(method.getHolderType()));
+    CfFrameState state = ConcreteCfFrameState.bottom();
+    int localIndex = 0;
+    if (previousMethodSignatureIsInstance) {
+      state =
+          state.storeLocal(
+              localIndex,
+              previousMethodSignature.isInstanceInitializer(dexItemFactory)
+                      || previousMethodSignature.mustBeInlinedIntoInstanceInitializer(appView)
+                      || previousMethodSignature.isHorizontallyMergedInstanceInitializer(
+                          dexItemFactory)
+                  ? FrameType.uninitializedThis()
+                  : FrameType.initialized(previousMethodSignature.getHolderType()),
+              helper);
+      localIndex++;
     }
-    for (DexType parameter : method.getParameters()) {
-      builder.appendLocal(FrameType.initialized(parameter));
+    for (DexType parameter : previousMethodSignature.getParameters()) {
+      state = state.storeLocal(localIndex, FrameType.initialized(parameter), helper);
+      localIndex += parameter.getRequiredRegisters();
     }
-    for (ExtraParameter extraParameter : prototypeChanges.getExtraParameters()) {
-      builder.appendLocal(FrameType.initialized(extraParameter.getType(dexItemFactory)));
+    if (state.isError()) {
+      return TraversalContinuation.doBreak(
+          CfCodeStackMapValidatingException.invalidStackMapForInstruction(
+              method, 0, instructions.get(0), state.asError().getMessage(), appView));
     }
-    return builder.build();
+    return TraversalContinuation.doContinue(state);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java b/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java
index 4b417bf..cc3f47c 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java
@@ -8,15 +8,7 @@
 import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.utils.StringUtils;
 
-public class CfCodeStackMapValidatingException extends RuntimeException {
-
-  private CfCodeStackMapValidatingException(String message) {
-    super(message);
-  }
-
-  public static CfCodeStackMapValidatingException error(String message) {
-    return new CfCodeStackMapValidatingException(message);
-  }
+public class CfCodeStackMapValidatingException {
 
   public static CfCodeDiagnostics unexpectedStackMapFrame(
       ProgramMethod method, AppView<?> appView) {
@@ -78,7 +70,7 @@
       String detailMessage,
       AppView<?> appView) {
     StringBuilder sb =
-        new StringBuilder("Invalid stack map table at ")
+        new StringBuilder("Invalid stack map table at instruction ")
             .append(instructionIndex)
             .append(": ")
             .append(instruction)
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index 44ac005..c43d396 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -178,6 +178,14 @@
     return annotation.annotation.type == factory.annotationMemberClasses;
   }
 
+  public static boolean isNestHostAnnotation(DexAnnotation annotation, DexItemFactory factory) {
+    return annotation.annotation.type == factory.annotationNestHost;
+  }
+
+  public static boolean isNestMembersAnnotation(DexAnnotation annotation, DexItemFactory factory) {
+    return annotation.annotation.type == factory.annotationNestMembers;
+  }
+
   public static DexAnnotation createInnerClassAnnotation(
       DexString clazz, int access, DexItemFactory factory) {
     return new DexAnnotation(
@@ -235,6 +243,29 @@
     return types;
   }
 
+  public static DexType getNestHostFromAnnotation(
+      DexAnnotation annotation, DexItemFactory factory) {
+    DexValue value = getSystemValueAnnotationValue(factory.annotationNestHost, annotation);
+    if (value == null) {
+      return null;
+    }
+    return value.asDexValueType().getValue();
+  }
+
+  public static List<DexType> getNestMembersFromAnnotation(
+      DexAnnotation annotation, DexItemFactory factory) {
+    DexValue value = getSystemValueAnnotationValue(factory.annotationNestMembers, annotation);
+    if (value == null) {
+      return null;
+    }
+    DexValueArray membersArray = value.asDexValueArray();
+    List<DexType> types = new ArrayList<>(membersArray.getValues().length);
+    for (DexValue elementValue : membersArray.getValues()) {
+      types.add(elementValue.asDexValueType().value);
+    }
+    return types;
+  }
+
   public static DexAnnotation createSourceDebugExtensionAnnotation(DexValue value,
       DexItemFactory factory) {
     return new DexAnnotation(VISIBILITY_SYSTEM,
@@ -273,6 +304,24 @@
         compressSignature(signature, factory));
   }
 
+  public static DexAnnotation createNestHostAnnotation(
+      NestHostClassAttribute host, DexItemFactory factory) {
+    return createSystemValueAnnotation(
+        factory.annotationNestHost, factory, new DexValue.DexValueType(host.getNestHost()));
+  }
+
+  public static DexAnnotation createNestMembersAnnotation(
+      List<NestMemberClassAttribute> members, DexItemFactory factory) {
+    List<DexValueType> list = new ArrayList<>(members.size());
+    for (NestMemberClassAttribute member : members) {
+      list.add(new DexValue.DexValueType(member.getNestMember()));
+    }
+    return createSystemValueAnnotation(
+        factory.annotationNestMembers,
+        factory,
+        new DexValue.DexValueArray(list.toArray(DexValue.EMPTY_ARRAY)));
+  }
+
   public static String getSignature(DexAnnotation signatureAnnotation) {
     DexValueArray elements = signatureAnnotation.annotation.elements[0].value.asDexValueArray();
     StringBuilder signature = new StringBuilder();
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 7c0e5d2..36f8d0f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -642,6 +642,10 @@
       createStaticallyKnownType("Ldalvik/annotation/MethodParameters;");
   public final DexType annotationSignature =
       createStaticallyKnownType(dalvikAnnotationSignatureString);
+  public final DexType annotationNestHost =
+      createStaticallyKnownType("Ldalvik/annotation/NestHost;");
+  public final DexType annotationNestMembers =
+      createStaticallyKnownType("Ldalvik/annotation/NestMembers;");
   public final DexType annotationSourceDebugExtension =
       createStaticallyKnownType("Ldalvik/annotation/SourceDebugExtension;");
   public final DexType annotationThrows = createStaticallyKnownType("Ldalvik/annotation/Throws;");
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 6797285..d18e823 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-
 import com.android.tools.r8.cf.code.CfArithmeticBinop;
 import com.android.tools.r8.cf.code.CfArrayLength;
 import com.android.tools.r8.cf.code.CfArrayLoad;
@@ -19,7 +18,6 @@
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfGoto;
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfIfCmp;
@@ -47,6 +45,9 @@
 import com.android.tools.r8.cf.code.CfSwitch;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.cf.code.frame.FrameType;
+import com.android.tools.r8.cf.code.frame.PreciseFrameType;
+import com.android.tools.r8.cf.code.frame.UninitializedNew;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
@@ -72,6 +73,8 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -452,12 +455,12 @@
         for (Int2ObjectMap.Entry<FrameType> entry : frame.getLocals().int2ObjectEntrySet()) {
           FrameType frameType = entry.getValue();
           if (frameType.isUninitializedNew() && frameType.getUninitializedNewType() == null) {
-            entry.setValue(fixupUninitializedNew(frameType));
+            entry.setValue(fixupUninitializedNew(frameType.asUninitializedNew()));
           }
         }
-        for (FrameType frameType : frame.getStack()) {
+        for (PreciseFrameType frameType : frame.getStack()) {
           if (frameType.isUninitializedNew() && frameType.getUninitializedNewType() == null) {
-            builder.push(fixupUninitializedNew(frameType));
+            builder.push(fixupUninitializedNew(frameType.asUninitializedNew()));
           } else {
             builder.push(frameType);
           }
@@ -466,7 +469,7 @@
       }
     }
 
-    private FrameType fixupUninitializedNew(FrameType frameType) {
+    private UninitializedNew fixupUninitializedNew(UninitializedNew frameType) {
       CfLabel label = frameType.getUninitializedLabel();
       CfNew cfNew = labelToNewMap.get(label);
       return cfNew != null ? FrameType.uninitializedNew(label, cfNew.getType()) : frameType;
@@ -490,7 +493,9 @@
       assert frameType == Opcodes.F_NEW;
       CfFrame.Builder builder = CfFrame.builder();
       parseLocals(nLocals, localTypes, builder);
-      parseStack(nStack, stackTypes, builder);
+      if (!parseStack(nStack, stackTypes, builder)) {
+        return;
+      }
       if (builder.hasIncompleteUninitializedNew()) {
         framesWithIncompleteUninitializedNew.add(instructions.size());
       }
@@ -505,11 +510,20 @@
       }
     }
 
-    private void parseStack(int nStack, Object[] stackTypes, CfFrame.Builder builder) {
+    private boolean parseStack(int nStack, Object[] stackTypes, CfFrame.Builder builder) {
       builder.allocateStack(nStack);
       for (int i = 0; i < nStack; i++) {
-        builder.push(getFrameType(stackTypes[i], builder));
+        FrameType frameType = getFrameType(stackTypes[i], builder);
+        if (frameType.isPrecise()) {
+          builder.push(frameType.asPrecise());
+        } else {
+          Reporter reporter = application.options.reporter;
+          reporter.warning(
+              new StringDiagnostic("Unexpected frame with imprecise value on stack", origin));
+          return false;
+        }
       }
+      return true;
     }
 
     private FrameType getFrameType(Object localType, CfFrame.Builder builder) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
index 464db91..25933dd 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
@@ -35,7 +35,7 @@
       return new ClassInstanceFieldsMergerImpl(appView.withClassHierarchy(), lensBuilder, group);
     } else {
       assert group.getInstanceFieldMap().isEmpty();
-      appView.options().horizontalClassMergerOptions().isRestrictedToSynthetics();
+      assert appView.options().horizontalClassMergerOptions().isRestrictedToSynthetics();
       return new ClassInstanceFieldsMerger() {
         @Override
         public void setClassIdField(DexEncodedField classIdField) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteVirtuallyMergedMethodCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteVirtuallyMergedMethodCode.java
index be68650..2bd4d6e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteVirtuallyMergedMethodCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteVirtuallyMergedMethodCode.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.horizontalclassmerging;
 
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfInstanceFieldRead;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
@@ -15,6 +14,7 @@
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.cf.code.CfSwitch;
 import com.android.tools.r8.cf.code.CfSwitch.Kind;
+import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -23,6 +23,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.horizontalclassmerging.VirtualMethodMerger.SuperMethodReference;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.IterableUtils;
@@ -42,13 +43,13 @@
   private final DexField classIdField;
   private final Int2ReferenceSortedMap<DexMethod> mappedMethods;
   private final DexMethod originalMethod;
-  private final DexMethod superMethod;
+  private final SuperMethodReference superMethod;
 
   public IncompleteVirtuallyMergedMethodCode(
       DexField classIdField,
       Int2ReferenceSortedMap<DexMethod> mappedMethods,
       DexMethod originalMethod,
-      DexMethod superMethod) {
+      SuperMethodReference superMethod) {
     this.mappedMethods = mappedMethods;
     this.classIdField = classIdField;
     this.superMethod = superMethod;
@@ -136,7 +137,12 @@
       fallthroughTarget =
           lens.getNextMethodSignature(mappedMethods.get(mappedMethods.lastIntKey()));
     } else {
-      fallthroughTarget = lens.lookupInvokeSuper(superMethod, method).getReference();
+      DexMethod reboundFallthroughTarget =
+          lens.lookupInvokeSuper(superMethod.getReboundReference(), method).getReference();
+      fallthroughTarget =
+          reboundFallthroughTarget.withHolder(
+              lens.lookupClassType(superMethod.getReference().getHolderType()),
+              appView.dexItemFactory());
     }
     instructions.add(
         new CfInvoke(Opcodes.INVOKESPECIAL, fallthroughTarget, method.getHolder().isInterface()));
@@ -167,8 +173,7 @@
     for (int argumentIndex = 0;
         argumentIndex < representative.getDefinition().getNumberOfArguments();
         argumentIndex++) {
-      FrameType frameType = FrameType.initialized(representative.getArgumentType(argumentIndex));
-      builder.appendLocal(frameType);
+      builder.appendLocal(FrameType.initialized(representative.getArgumentType(argumentIndex)));
     }
     CfFrame frame = builder.build();
     assert frame.getLocals().size() == localsSize;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
index 6abb160..4e3227a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -27,17 +27,36 @@
 
 public class VirtualMethodMerger {
 
+  static class SuperMethodReference {
+
+    DexMethod reference;
+    DexMethod reboundReference;
+
+    SuperMethodReference(DexMethod reference, DexMethod reboundReference) {
+      this.reference = reference;
+      this.reboundReference = reboundReference;
+    }
+
+    public DexMethod getReference() {
+      return reference;
+    }
+
+    public DexMethod getReboundReference() {
+      return reboundReference;
+    }
+  }
+
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final DexItemFactory dexItemFactory;
   private final MergeGroup group;
   private final List<ProgramMethod> methods;
-  private final DexMethod superMethod;
+  private final SuperMethodReference superMethod;
 
   public VirtualMethodMerger(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       MergeGroup group,
       List<ProgramMethod> methods,
-      DexMethod superMethod) {
+      SuperMethodReference superMethod) {
     this.appView = appView;
     this.dexItemFactory = appView.dexItemFactory();
     this.group = group;
@@ -54,7 +73,7 @@
     }
 
     /** Get the super method handle if this method overrides a parent method. */
-    private DexMethod superMethod(
+    private SuperMethodReference superMethod(
         AppView<? extends AppInfoWithClassHierarchy> appView, MergeGroup group) {
       DexMethod template = methods.iterator().next().getReference();
       SingleResolutionResult<?> resolutionResult =
@@ -66,20 +85,22 @@
         // If there is no super method or the method is abstract it should not be called.
         return null;
       }
-      if (resolutionResult.getResolvedHolder().isInterface()) {
-        // Ensure that invoke virtual isn't called on an interface method.
-        return resolutionResult
-            .getResolvedMethod()
-            .getReference()
-            .withHolder(group.getSuperType(), appView.dexItemFactory());
-      }
-      return resolutionResult.getResolvedMethod().getReference();
+      DexMethod reboundReference = resolutionResult.getResolvedMethod().getReference();
+      DexMethod reference =
+          resolutionResult.getResolvedHolder().isInterface()
+              ? resolutionResult
+                  .getResolvedMethod()
+                  .getReference()
+                  .withHolder(group.getSuperType(), appView.dexItemFactory())
+              : reboundReference;
+      return new SuperMethodReference(reference, reboundReference);
     }
 
     public VirtualMethodMerger build(
         AppView<? extends AppInfoWithClassHierarchy> appView, MergeGroup group) {
       // If not all the classes are in the merge group, find the fallback super method to call.
-      DexMethod superMethod = methods.size() < group.size() ? superMethod(appView, group) : null;
+      SuperMethodReference superMethod =
+          methods.size() < group.size() ? superMethod(appView, group) : null;
       return new VirtualMethodMerger(appView, group, methods, superMethod);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckSyntheticClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckSyntheticClasses.java
index 4d2554a..c9c3569 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckSyntheticClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckSyntheticClasses.java
@@ -25,7 +25,8 @@
     if (!options.isSyntheticMergingEnabled() && syntheticItems.isSyntheticClass(clazz)) {
       return false;
     }
-    if (options.isRestrictedToSynthetics() && !syntheticItems.isSyntheticClass(clazz)) {
+    if (options.isRestrictedToSynthetics()
+        && !syntheticItems.isSyntheticClassEligibleForMerging(clazz)) {
       return false;
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/MemberType.java b/src/main/java/com/android/tools/r8/ir/code/MemberType.java
index fe1048a..c85a687 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MemberType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MemberType.java
@@ -75,6 +75,7 @@
   public static MemberType fromTypeDescriptorChar(char descriptor) {
     switch (descriptor) {
       case 'L':
+      case 'N':
       case '[':
         return MemberType.OBJECT;
       case 'Z':
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 13c1982..e6228f7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -13,12 +13,14 @@
 import com.android.tools.r8.cf.TypeVerificationHelper.ThisInstanceInfo;
 import com.android.tools.r8.cf.TypeVerificationHelper.TypeInfo;
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfPosition;
 import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.cf.code.frame.FrameType;
+import com.android.tools.r8.cf.code.frame.PreciseFrameType;
+import com.android.tools.r8.cf.code.frame.UninitializedFrameType;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -622,16 +624,16 @@
     instructions.add(frame);
   }
 
-  private FrameType getFrameType(BasicBlock liveBlock, TypeInfo typeInfo) {
+  private PreciseFrameType getFrameType(BasicBlock liveBlock, TypeInfo typeInfo) {
     if (typeInfo instanceof InitializedTypeInfo) {
       return FrameType.initialized(typeInfo.getDexType());
     }
-    FrameType type = findAllocator(liveBlock, typeInfo);
+    UninitializedFrameType type = findAllocator(liveBlock, typeInfo);
     return type != null ? type : FrameType.initialized(typeInfo.getDexType());
   }
 
-  private FrameType findAllocator(BasicBlock liveBlock, TypeInfo typeInfo) {
-    FrameType res;
+  private UninitializedFrameType findAllocator(BasicBlock liveBlock, TypeInfo typeInfo) {
+    UninitializedFrameType res;
     Instruction definition;
     if (typeInfo instanceof NewInstanceInfo) {
       NewInstanceInfo newInstanceInfo = (NewInstanceInfo) typeInfo;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 4500da3..86fa768 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -6,7 +6,6 @@
 import static it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMaps.emptyMap;
 
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfGoto;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfLabel;
@@ -15,6 +14,8 @@
 import com.android.tools.r8.cf.code.CfSwitch;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.cf.code.frame.FrameType;
+import com.android.tools.r8.cf.code.frame.PreciseFrameType;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
@@ -610,7 +611,7 @@
     frame.forEachLocal(
         (localIndex, frameType) -> locals[localIndex] = convertUninitialized(frameType));
     int index = 0;
-    for (FrameType frameType : frame.getStack()) {
+    for (PreciseFrameType frameType : frame.getStack()) {
       stack[index++] = convertUninitialized(frameType);
     }
     // TODO(b/169135126) Assert that all values are precise.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java
index 57de6e1..0aa439a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.cf.code.CfArrayStore;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLabel;
@@ -24,6 +23,7 @@
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexItemFactory;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 38682e7..324ecfa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfGoto;
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfIfCmp;
@@ -39,6 +38,7 @@
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
index 3378f64..00892fa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfGoto;
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfInstruction;
@@ -31,6 +30,7 @@
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibrarySpecification.java
index 08b98e6..ba352b1 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibrarySpecification.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.Collection;
 import java.util.List;
 
 public interface DesugaredLibrarySpecification {
@@ -39,11 +40,14 @@
       throws IOException;
 
   MachineDesugaredLibrarySpecification toMachineSpecification(
-      InternalOptions options, Path library, Timing timing, Path desugaredJDKLib)
+      InternalOptions options,
+      Collection<Path> library,
+      Timing timing,
+      Collection<Path> desugaredJDKLib)
       throws IOException;
 
   default MachineDesugaredLibrarySpecification toMachineSpecification(
-      InternalOptions options, Path library, Timing timing) throws IOException {
+      InternalOptions options, Collection<Path> library, Timing timing) throws IOException {
     assert !isLibraryCompilation();
     return toMachineSpecification(options, library, timing, null);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
index ea0f8c0..29ecfd2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
@@ -41,6 +41,8 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer;
 import com.android.tools.r8.ir.synthetic.apiconverter.APIConversionCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.apiconverter.EqualsCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.apiconverter.HashCodeCfCodeProvider;
 import com.android.tools.r8.utils.OptionalBool;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -97,6 +99,33 @@
     return wrapperSynthesizer.newSynthesizedMethod(methodToInstall, cfCode);
   }
 
+  public DexEncodedMethod generateWrapperHashCode(DexField wrapperField) {
+    return wrapperSynthesizer.newSynthesizedMethod(
+        appView
+            .dexItemFactory()
+            .createMethod(
+                wrapperField.getHolderType(),
+                appView.dexItemFactory().createProto(appView.dexItemFactory().intType),
+                appView.dexItemFactory().hashCodeMethodName),
+        new HashCodeCfCodeProvider(appView, wrapperField.getHolderType(), wrapperField)
+            .generateCfCode());
+  }
+
+  public DexEncodedMethod generateWrapperEquals(DexField wrapperField) {
+    return wrapperSynthesizer.newSynthesizedMethod(
+        appView
+            .dexItemFactory()
+            .createMethod(
+                wrapperField.getHolderType(),
+                appView
+                    .dexItemFactory()
+                    .createProto(
+                        appView.dexItemFactory().booleanType, appView.dexItemFactory().objectType),
+                appView.dexItemFactory().equalsMethodName),
+        new EqualsCfCodeProvider(appView, wrapperField.getHolderType(), wrapperField)
+            .generateCfCode());
+  }
+
   public DexEncodedMethod generateVivifiedWrapperConversionWithoutCode(
       DexMethod method, DexField wrapperField) {
     DexMethod methodToInstall =
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
index 5a34df5..a930b12 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
@@ -42,6 +42,7 @@
 import com.android.tools.r8.synthesis.SyntheticItems.SyntheticKindSelector;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -632,6 +633,13 @@
     return generatedMethods;
   }
 
+  private Collection<DexEncodedMethod> synthesizeHashCodeAndEquals(
+      DexProgramClass wrapper, DexEncodedField wrapperField) {
+    return ImmutableList.of(
+        conversionCfProvider.generateWrapperHashCode(wrapperField.getReference()),
+        conversionCfProvider.generateWrapperEquals(wrapperField.getReference()));
+  }
+
   DexEncodedMethod newSynthesizedMethod(DexMethod methodToInstall, Code code) {
     MethodAccessFlags newFlags =
         MethodAccessFlags.fromSharedAccessFlags(
@@ -752,6 +760,7 @@
                     field,
                     eventConsumer,
                     () -> processingContext.createUniqueContext(wrapper))));
+    wrapper.addVirtualMethods(synthesizeHashCodeAndEquals(wrapper, wrapperField));
     DexProgramClass vivifiedWrapper =
         getExistingProgramWrapper(context, kinds -> kinds.VIVIFIED_WRAPPER);
     DexEncodedField vivifiedWrapperField = getWrapperUniqueEncodedField(vivifiedWrapper);
@@ -765,5 +774,7 @@
                     field,
                     eventConsumer,
                     () -> processingContext.createUniqueContext(wrapper))));
+    vivifiedWrapper.addVirtualMethods(
+        synthesizeHashCodeAndEquals(vivifiedWrapper, vivifiedWrapperField));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java
index 2a5f49f..e3696af 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.Collection;
 import java.util.List;
 
 public class HumanDesugaredLibrarySpecification implements DesugaredLibrarySpecification {
@@ -97,7 +98,10 @@
 
   @Override
   public MachineDesugaredLibrarySpecification toMachineSpecification(
-      InternalOptions options, Path library, Timing timing, Path desugaredJDKLib)
+      InternalOptions options,
+      Collection<Path> library,
+      Timing timing,
+      Collection<Path> desugaredJDKLib)
       throws IOException {
     return new HumanToMachineSpecificationConverter(timing)
         .convertForTesting(this, desugaredJDKLib, library, options);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java
index 01fda4a..e669bfb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -127,7 +128,10 @@
 
   @Override
   public MachineDesugaredLibrarySpecification toMachineSpecification(
-      InternalOptions options, Path library, Timing timing, Path desugaredJDKLib)
+      InternalOptions options,
+      Collection<Path> library,
+      Timing timing,
+      Collection<Path> desugaredJDKLib)
       throws IOException {
     return new LegacyToHumanSpecificationConverter(timing)
         .convertForTesting(this, desugaredJDKLib, library, options)
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/AppForSpecConversion.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/AppForSpecConversion.java
index 0df69b0..74c31b4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/AppForSpecConversion.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/AppForSpecConversion.java
@@ -12,13 +12,14 @@
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.Collection;
 import java.util.concurrent.ExecutorService;
 
 public class AppForSpecConversion {
 
   public static DexApplication readAppForTesting(
-      Path desugaredJDKLib,
-      Path androidLib,
+      Collection<Path> desugaredJDKLib,
+      Collection<Path> androidLib,
       InternalOptions options,
       boolean libraryCompilation,
       Timing timing)
@@ -27,9 +28,9 @@
     assert !libraryCompilation || desugaredJDKLib != null;
     AndroidApp.Builder builder = AndroidApp.builder();
     if (libraryCompilation) {
-      builder.addProgramFile(desugaredJDKLib);
+      builder.addProgramFiles(desugaredJDKLib);
     }
-    AndroidApp inputApp = builder.addLibraryFile(androidLib).build();
+    AndroidApp inputApp = builder.addLibraryFiles(androidLib).build();
     DexApplication app = internalReadApp(inputApp, options, timing);
     timing.end();
     return app;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
index 99d816e..dea440e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
@@ -29,6 +29,7 @@
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 
@@ -45,8 +46,8 @@
 
   public MachineDesugaredLibrarySpecification convertForTesting(
       HumanDesugaredLibrarySpecification humanSpec,
-      Path desugaredJDKLib,
-      Path androidLib,
+      Collection<Path> desugaredJDKLib,
+      Collection<Path> androidLib,
       InternalOptions options)
       throws IOException {
     DexApplication app =
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineWrapperConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineWrapperConverter.java
index 18147b5..c0d9157 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineWrapperConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineWrapperConverter.java
@@ -70,7 +70,11 @@
     while (!workList.isEmpty()) {
       DexClass dexClass = workList.removeFirst();
       for (DexEncodedMethod virtualMethod : dexClass.virtualMethods()) {
-        if (!virtualMethod.isPrivateMethod()) {
+        if (!virtualMethod.isPrivateMethod()
+            // Don't include hashCode and equals overrides, as hashCode and equals are added to
+            // all wrappers regardless.
+            && (!appInfo.dexItemFactory().objectMembers.hashCode.match(virtualMethod))
+            && (!appInfo.dexItemFactory().objectMembers.equals.match(virtualMethod))) {
           assert virtualMethod.isProtectedMethod() || virtualMethod.isPublicMethod();
           boolean alreadyAdded = wrappers.contains(equivalence.wrap(virtualMethod.getReference()));
           // This looks quadratic but given the size of the collections met in practice for
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
index baa4178..9ac3369 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
@@ -41,6 +41,7 @@
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -61,8 +62,8 @@
 
   public void convertAllAPILevels(
       StringResource inputSpecification,
-      Path desugaredJDKLib,
-      Path androidLib,
+      Collection<Path> desugaredJDKLib,
+      Collection<Path> androidLib,
       StringConsumer output)
       throws IOException {
     InternalOptions options = new InternalOptions();
@@ -77,8 +78,8 @@
 
   public MultiAPILevelHumanDesugaredLibrarySpecification convertAllAPILevels(
       MultiAPILevelLegacyDesugaredLibrarySpecification legacySpec,
-      Path desugaredJDKLib,
-      Path androidLib,
+      Collection<Path> desugaredJDKLib,
+      Collection<Path> androidLib,
       InternalOptions options)
       throws IOException {
     timing.begin("Legacy to human all API convert");
@@ -108,8 +109,8 @@
 
   public HumanDesugaredLibrarySpecification convertForTesting(
       LegacyDesugaredLibrarySpecification legacySpec,
-      Path desugaredJDKLib,
-      Path androidLib,
+      Collection<Path> desugaredJDKLib,
+      Collection<Path> androidLib,
       InternalOptions options)
       throws IOException {
     DexApplication app =
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java
index a6384e1..b288d9a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.DesugarDescription;
+import com.android.tools.r8.ir.desugar.DesugarDescription.ScanCallback;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations;
@@ -105,46 +106,46 @@
                 getThrowInstructions(
                     appView,
                     invoke,
-                    resolutionResult,
                     localStackAllocator,
                     eventConsumer,
                     context,
-                    methodProcessingContext))
+                    methodProcessingContext,
+                    getMethodSynthesizerForThrowing(appView, invoke, resolutionResult, context)))
         .build();
   }
 
+  public static DesugarDescription computeInvokeAsThrowNSMERewrite(
+      AppView<?> appView, CfInvoke invoke, ScanCallback scanCallback) {
+    DesugarDescription.Builder builder =
+        DesugarDescription.builder()
+            .setDesugarRewrite(
+                (freshLocalProvider,
+                    localStackAllocator,
+                    eventConsumer,
+                    context,
+                    methodProcessingContext,
+                    dexItemFactory) ->
+                    getThrowInstructions(
+                        appView,
+                        invoke,
+                        localStackAllocator,
+                        eventConsumer,
+                        context,
+                        methodProcessingContext,
+                        UtilityMethodsForCodeOptimizations
+                            ::synthesizeThrowNoSuchMethodErrorMethod));
+    builder.addScanEffect(scanCallback);
+    return builder.build();
+  }
+
   private static Collection<CfInstruction> getThrowInstructions(
       AppView<?> appView,
       CfInvoke invoke,
-      MethodResolutionResult resolutionResult,
       LocalStackAllocator localStackAllocator,
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
-      MethodProcessingContext methodProcessingContext) {
-    MethodSynthesizerConsumer methodSynthesizerConsumer = null;
-    if (resolutionResult == null) {
-      methodSynthesizerConsumer =
-          UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
-    } else if (resolutionResult.isSingleResolution()) {
-      if (resolutionResult.getResolvedMethod().isStatic() != invoke.isInvokeStatic()) {
-        methodSynthesizerConsumer =
-            UtilityMethodsForCodeOptimizations::synthesizeThrowIncompatibleClassChangeErrorMethod;
-      }
-    } else if (resolutionResult.isFailedResolution()) {
-      FailedResolutionResult failedResolutionResult = resolutionResult.asFailedResolution();
-      AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
-      if (failedResolutionResult.isIllegalAccessErrorResult(context.getHolder(), appInfo)) {
-        methodSynthesizerConsumer =
-            UtilityMethodsForCodeOptimizations::synthesizeThrowIllegalAccessErrorMethod;
-      } else if (failedResolutionResult.isNoSuchMethodErrorResult(context.getHolder(), appInfo)) {
-        methodSynthesizerConsumer =
-            UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
-      } else if (failedResolutionResult.isIncompatibleClassChangeErrorResult()) {
-        methodSynthesizerConsumer =
-            UtilityMethodsForCodeOptimizations::synthesizeThrowIncompatibleClassChangeErrorMethod;
-      }
-    }
-
+      MethodProcessingContext methodProcessingContext,
+      MethodSynthesizerConsumer methodSynthesizerConsumer) {
     if (methodSynthesizerConsumer == null) {
       assert false;
       return null;
@@ -198,4 +199,32 @@
     }
     return replacement;
   }
+
+  private static MethodSynthesizerConsumer getMethodSynthesizerForThrowing(
+      AppView<?> appView,
+      CfInvoke invoke,
+      MethodResolutionResult resolutionResult,
+      ProgramMethod context) {
+    if (resolutionResult == null) {
+      return UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
+    } else if (resolutionResult.isSingleResolution()) {
+      if (resolutionResult.getResolvedMethod().isStatic() != invoke.isInvokeStatic()) {
+        return UtilityMethodsForCodeOptimizations
+            ::synthesizeThrowIncompatibleClassChangeErrorMethod;
+      }
+    } else if (resolutionResult.isFailedResolution()) {
+      FailedResolutionResult failedResolutionResult = resolutionResult.asFailedResolution();
+      AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
+      if (failedResolutionResult.isIllegalAccessErrorResult(context.getHolder(), appInfo)) {
+        return UtilityMethodsForCodeOptimizations::synthesizeThrowIllegalAccessErrorMethod;
+      } else if (failedResolutionResult.isNoSuchMethodErrorResult(context.getHolder(), appInfo)) {
+        return UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
+      } else if (failedResolutionResult.isIncompatibleClassChangeErrorResult()) {
+        return UtilityMethodsForCodeOptimizations
+            ::synthesizeThrowIncompatibleClassChangeErrorMethod;
+      }
+    }
+
+    return null;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index bc8ebd0..8e8541b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -44,6 +44,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.structural.Ordered;
 import com.google.common.collect.Iterables;
@@ -711,7 +712,23 @@
       }
     }
 
-    return computeEmulatedInterfaceInvokeSpecial(clazz, invokedMethod, context);
+    DesugarDescription emulatedInterfaceDesugaring =
+        computeEmulatedInterfaceInvokeSpecial(clazz, invokedMethod, context);
+    if (!emulatedInterfaceDesugaring.needsDesugaring() && context.isDefaultMethod()) {
+      return AlwaysThrowingInstructionDesugaring.computeInvokeAsThrowNSMERewrite(
+          appView,
+          invoke,
+          () ->
+              appView
+                  .reporter()
+                  .warning(
+                      new StringDiagnostic(
+                          "Interface method desugaring has inserted NoSuchMethodError replacing a"
+                              + " super call in "
+                              + context.toSourceString(),
+                          context.getOrigin())));
+    }
+    return emulatedInterfaceDesugaring;
   }
 
   private DesugarDescription computeEmulatedInterfaceInvokeSpecial(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java
index 8e854ac..e46b8d6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfGoto;
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfIfCmp;
@@ -27,6 +26,7 @@
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
index 127b47b..527fb13 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.cf.code.CfArrayStore;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfGoto;
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfIfCmp;
@@ -28,6 +27,7 @@
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
index 2cdaa8e..162a1cc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
@@ -9,7 +9,6 @@
 package com.android.tools.r8.ir.optimize.templates;
 
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLabel;
@@ -18,6 +17,7 @@
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
index 480490c..2852ce9 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfInstanceOf;
 import com.android.tools.r8.cf.code.CfInstruction;
@@ -15,6 +14,7 @@
 import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
index 7de8d21..34e3f71 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfIfCmp;
 import com.android.tools.r8.cf.code.CfInstruction;
@@ -22,6 +21,7 @@
 import com.android.tools.r8.cf.code.CfStaticFieldRead;
 import com.android.tools.r8.cf.code.CfStaticFieldWrite;
 import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
index a6da57c..f328c2c 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfIfCmp;
 import com.android.tools.r8.cf.code.CfInstanceFieldRead;
 import com.android.tools.r8.cf.code.CfInstruction;
@@ -19,6 +18,7 @@
 import com.android.tools.r8.cf.code.CfRecordFieldValues;
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexField;
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/EqualsCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/EqualsCfCodeProvider.java
new file mode 100644
index 0000000..d1a4bf3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/EqualsCfCodeProvider.java
@@ -0,0 +1,88 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.synthetic.apiconverter;
+
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfInstanceFieldRead;
+import com.android.tools.r8.cf.code.CfInstanceOf;
+import com.android.tools.r8.cf.code.CfInstruction;
+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.CfReturn;
+import com.android.tools.r8.cf.code.frame.FrameType;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.synthetic.SyntheticCfCodeProvider;
+import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+public class EqualsCfCodeProvider extends SyntheticCfCodeProvider {
+
+  private final DexField wrapperField;
+
+  public EqualsCfCodeProvider(AppView<?> appView, DexType holder, DexField wrapperField) {
+    super(appView, holder);
+    this.wrapperField = wrapperField;
+  }
+
+  @Override
+  public CfCode generateCfCode() {
+    // return wrapperField.equals(
+    //     other instanceof WrapperType ? ((WrapperType) other).wrapperField : other);
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    List<CfInstruction> instructions = new ArrayList<>();
+    instructions.add(new CfLoad(ValueType.OBJECT, 0));
+    instructions.add(new CfInstanceFieldRead(wrapperField));
+    instructions.add(new CfLoad(ValueType.OBJECT, 1));
+    DexType wrapperType = wrapperField.getHolderType();
+    instructions.add(new CfInstanceOf(wrapperType));
+    instructions.add(new CfIf(If.Type.EQ, ValueType.INT, label1));
+    instructions.add(new CfLoad(ValueType.OBJECT, 1));
+    instructions.add(new CfCheckCast(wrapperType));
+    instructions.add(new CfInstanceFieldRead(wrapperField));
+    instructions.add(new CfGoto(label2));
+    instructions.add(label1);
+    instructions.add(
+        new CfFrame(
+            new Int2ObjectAVLTreeMap<>(
+                new int[] {0, 1},
+                new FrameType[] {
+                  FrameType.initialized(wrapperType),
+                  FrameType.initialized(appView.dexItemFactory().objectType)
+                }),
+            new ArrayDeque<>(Arrays.asList(FrameType.initialized(wrapperType)))));
+    instructions.add(new CfLoad(ValueType.OBJECT, 1));
+    instructions.add(label2);
+    instructions.add(
+        new CfFrame(
+            new Int2ObjectAVLTreeMap<>(
+                new int[] {0, 1},
+                new FrameType[] {
+                  FrameType.initialized(wrapperType),
+                  FrameType.initialized(appView.dexItemFactory().objectType)
+                }),
+            new ArrayDeque<>(
+                Arrays.asList(
+                    FrameType.initialized(wrapperType),
+                    FrameType.initialized(appView.dexItemFactory().objectType)))));
+    instructions.add(
+        new CfInvoke(Opcodes.INVOKEVIRTUAL, appView.dexItemFactory().objectMembers.equals, false));
+    instructions.add(new CfReturn(ValueType.INT));
+    return standardCfCodeFromInstructions(instructions);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/HashCodeCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/HashCodeCfCodeProvider.java
new file mode 100644
index 0000000..3c5f717
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/HashCodeCfCodeProvider.java
@@ -0,0 +1,42 @@
+// 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.apiconverter;
+
+import com.android.tools.r8.cf.code.CfInstanceFieldRead;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.synthetic.SyntheticCfCodeProvider;
+import java.util.ArrayList;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+public class HashCodeCfCodeProvider extends SyntheticCfCodeProvider {
+
+  private final DexField wrapperField;
+
+  public HashCodeCfCodeProvider(AppView<?> appView, DexType holder, DexField wrapperField) {
+    super(appView, holder);
+    this.wrapperField = wrapperField;
+  }
+
+  @Override
+  public CfCode generateCfCode() {
+    List<CfInstruction> instructions = new ArrayList<>();
+    instructions.add(new CfLoad(ValueType.OBJECT, 0));
+    instructions.add(new CfInstanceFieldRead(wrapperField));
+    instructions.add(
+        new CfInvoke(
+            Opcodes.INVOKEVIRTUAL, appView.dexItemFactory().objectMembers.hashCode, false));
+    instructions.add(new CfReturn(ValueType.INT));
+    return standardCfCodeFromInstructions(instructions);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java
index a4f9714..10e267b 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfGoto;
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfIfCmp;
@@ -29,6 +28,7 @@
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfStaticFieldRead;
 import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexEncodedField;
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java
index fc3c106..5c9e671 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java
@@ -5,7 +5,9 @@
 package com.android.tools.r8.optimize.interfaces.analysis;
 
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.frame.FrameType;
+import com.android.tools.r8.cf.code.frame.PreciseFrameType;
+import com.android.tools.r8.cf.code.frame.UninitializedFrameType;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -33,12 +35,23 @@
   }
 
   @Override
+  public CfFrameState checkLocals(AppView<?> appView, CfFrame frame) {
+    return new ConcreteCfFrameState().checkLocals(appView, frame);
+  }
+
+  @Override
+  public CfFrameState checkStack(AppView<?> appView, CfFrame frame) {
+    return new ConcreteCfFrameState().checkStack(appView, frame);
+  }
+
+  @Override
   public CfFrameState clear() {
     return this;
   }
 
   @Override
-  public CfFrameState markInitialized(FrameType uninitializedType, DexType initializedType) {
+  public CfFrameState markInitialized(
+      UninitializedFrameType uninitializedType, DexType initializedType) {
     // Initializing an uninitialized type is a no-op when the frame is empty.
     return this;
   }
@@ -49,7 +62,7 @@
   }
 
   @Override
-  public ErroneousCfFrameState pop(BiFunction<CfFrameState, FrameType, CfFrameState> fn) {
+  public ErroneousCfFrameState pop(BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn) {
     return pop();
   }
 
@@ -63,7 +76,7 @@
   public ErroneousCfFrameState popInitialized(
       AppView<?> appView,
       DexType expectedType,
-      BiFunction<CfFrameState, FrameType, CfFrameState> fn) {
+      BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn) {
     return pop();
   }
 
@@ -78,7 +91,7 @@
   }
 
   @Override
-  public CfFrameState push(CfAnalysisConfig config, FrameType frameType) {
+  public CfFrameState push(CfAnalysisConfig config, PreciseFrameType frameType) {
     return new ConcreteCfFrameState().push(config, frameType);
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java
index b12cc01..2131f02 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java
@@ -9,7 +9,9 @@
 
 import com.android.tools.r8.cf.code.CfAssignability;
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.frame.FrameType;
+import com.android.tools.r8.cf.code.frame.PreciseFrameType;
+import com.android.tools.r8.cf.code.frame.UninitializedFrameType;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -101,14 +103,18 @@
 
   public abstract CfFrameState check(AppView<?> appView, CfFrame frame);
 
+  public abstract CfFrameState checkLocals(AppView<?> appView, CfFrame frame);
+
+  public abstract CfFrameState checkStack(AppView<?> appView, CfFrame frame);
+
   public abstract CfFrameState clear();
 
   public abstract CfFrameState markInitialized(
-      FrameType uninitializedType, DexType initializedType);
+      UninitializedFrameType uninitializedType, DexType initializedType);
 
   public abstract CfFrameState pop();
 
-  public abstract CfFrameState pop(BiFunction<CfFrameState, FrameType, CfFrameState> fn);
+  public abstract CfFrameState pop(BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn);
 
   public abstract CfFrameState popAndInitialize(
       AppView<?> appView, DexMethod constructor, CfAnalysisConfig config);
@@ -120,7 +126,7 @@
   public abstract CfFrameState popInitialized(
       AppView<?> appView,
       DexType expectedType,
-      BiFunction<CfFrameState, FrameType, CfFrameState> fn);
+      BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn);
 
   public abstract CfFrameState popInitialized(AppView<?> appView, DexType... expectedTypes);
 
@@ -143,11 +149,11 @@
   public final CfFrameState popInitialized(
       AppView<?> appView,
       ValueType valueType,
-      BiFunction<CfFrameState, FrameType, CfFrameState> fn) {
+      BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn) {
     return popInitialized(appView, valueType.toDexType(appView.dexItemFactory()), fn);
   }
 
-  public final CfFrameState popObject(BiFunction<CfFrameState, FrameType, CfFrameState> fn) {
+  public final CfFrameState popObject(BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn) {
     return pop(
         (state, head) ->
             head.isObject() ? fn.apply(state, head) : errorUnexpectedStack(head, ValueType.OBJECT));
@@ -158,7 +164,7 @@
       AppView<?> appView,
       DexType expectedType,
       CfAnalysisConfig config,
-      BiFunction<CfFrameState, FrameType, CfFrameState> fn) {
+      BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn) {
     return pop(
         (state, head) ->
             head.isObject()
@@ -174,7 +180,7 @@
     return popSingle((state, single) -> state);
   }
 
-  public final CfFrameState popSingle(BiFunction<CfFrameState, FrameType, CfFrameState> fn) {
+  public final CfFrameState popSingle(BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn) {
     return pop(
         (state, single) ->
             single.isSingle()
@@ -183,22 +189,22 @@
   }
 
   public final CfFrameState popSingles(
-      TriFunction<CfFrameState, FrameType, FrameType, CfFrameState> fn) {
+      TriFunction<CfFrameState, PreciseFrameType, PreciseFrameType, CfFrameState> fn) {
     return popSingle(
         (state1, single1) ->
             state1.popSingle((state2, single2) -> fn.apply(state2, single2, single1)));
   }
 
   public final CfFrameState popSingleOrWide(
-      BiFunction<CfFrameState, FrameType, CfFrameState> singleFn,
-      BiFunction<CfFrameState, FrameType, CfFrameState> wideFn) {
+      BiFunction<CfFrameState, PreciseFrameType, CfFrameState> singleFn,
+      BiFunction<CfFrameState, PreciseFrameType, CfFrameState> wideFn) {
     return pop(
         (state, head) -> head.isSingle() ? singleFn.apply(state, head) : wideFn.apply(state, head));
   }
 
   public final CfFrameState popSingleSingleOrWide(
-      TriFunction<CfFrameState, FrameType, FrameType, CfFrameState> singleSingleFn,
-      BiFunction<CfFrameState, FrameType, CfFrameState> wideFn) {
+      TriFunction<CfFrameState, PreciseFrameType, PreciseFrameType, CfFrameState> singleSingleFn,
+      BiFunction<CfFrameState, PreciseFrameType, CfFrameState> wideFn) {
     return popSingleOrWide(
         (state1, single1) ->
             state1.popSingle((state2, single2) -> singleSingleFn.apply(state2, single2, single1)),
@@ -207,24 +213,27 @@
 
   public abstract CfFrameState push(CfAnalysisConfig config, DexType type);
 
-  public abstract CfFrameState push(CfAnalysisConfig config, FrameType frameType);
+  public abstract CfFrameState push(CfAnalysisConfig config, PreciseFrameType frameType);
 
   public final CfFrameState push(
-      CfAnalysisConfig config, FrameType frameType, FrameType frameType2) {
+      CfAnalysisConfig config, PreciseFrameType frameType, PreciseFrameType frameType2) {
     return push(config, frameType).push(config, frameType2);
   }
 
   public final CfFrameState push(
-      CfAnalysisConfig config, FrameType frameType, FrameType frameType2, FrameType frameType3) {
+      CfAnalysisConfig config,
+      PreciseFrameType frameType,
+      PreciseFrameType frameType2,
+      PreciseFrameType frameType3) {
     return push(config, frameType).push(config, frameType2).push(config, frameType3);
   }
 
   public final CfFrameState push(
       CfAnalysisConfig config,
-      FrameType frameType,
-      FrameType frameType2,
-      FrameType frameType3,
-      FrameType frameType4) {
+      PreciseFrameType frameType,
+      PreciseFrameType frameType2,
+      PreciseFrameType frameType3,
+      PreciseFrameType frameType4) {
     return push(config, frameType)
         .push(config, frameType2)
         .push(config, frameType3)
@@ -233,11 +242,11 @@
 
   public final CfFrameState push(
       CfAnalysisConfig config,
-      FrameType frameType,
-      FrameType frameType2,
-      FrameType frameType3,
-      FrameType frameType4,
-      FrameType frameType5) {
+      PreciseFrameType frameType,
+      PreciseFrameType frameType2,
+      PreciseFrameType frameType3,
+      PreciseFrameType frameType4,
+      PreciseFrameType frameType5) {
     return push(config, frameType)
         .push(config, frameType2)
         .push(config, frameType3)
@@ -247,12 +256,12 @@
 
   public final CfFrameState push(
       CfAnalysisConfig config,
-      FrameType frameType,
-      FrameType frameType2,
-      FrameType frameType3,
-      FrameType frameType4,
-      FrameType frameType5,
-      FrameType frameType6) {
+      PreciseFrameType frameType,
+      PreciseFrameType frameType2,
+      PreciseFrameType frameType3,
+      PreciseFrameType frameType4,
+      PreciseFrameType frameType5,
+      PreciseFrameType frameType6) {
     return push(config, frameType)
         .push(config, frameType2)
         .push(config, frameType3)
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java
index a76a6d3..e920d8e 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java
@@ -10,8 +10,10 @@
 import com.android.tools.r8.cf.code.CfAssignability;
 import com.android.tools.r8.cf.code.CfAssignability.AssignabilityResult;
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.frame.FrameType;
+import com.android.tools.r8.cf.code.frame.PreciseFrameType;
 import com.android.tools.r8.cf.code.frame.SingleFrameType;
+import com.android.tools.r8.cf.code.frame.UninitializedFrameType;
 import com.android.tools.r8.cf.code.frame.WideFrameType;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
@@ -35,15 +37,15 @@
 public class ConcreteCfFrameState extends CfFrameState {
 
   private final Int2ObjectAVLTreeMap<FrameType> locals;
-  private final ArrayDeque<FrameType> stack;
+  private final ArrayDeque<PreciseFrameType> stack;
   private int stackHeight;
 
   ConcreteCfFrameState() {
     this(new Int2ObjectAVLTreeMap<>(), new ArrayDeque<>(), 0);
   }
 
-  ConcreteCfFrameState(
-      Int2ObjectAVLTreeMap<FrameType> locals, ArrayDeque<FrameType> stack, int stackHeight) {
+  public ConcreteCfFrameState(
+      Int2ObjectAVLTreeMap<FrameType> locals, ArrayDeque<PreciseFrameType> stack, int stackHeight) {
     this.locals = locals;
     this.stack = stack;
     this.stackHeight = stackHeight;
@@ -78,28 +80,53 @@
   }
 
   @Override
+  public CfFrameState checkLocals(AppView<?> appView, CfFrame frame) {
+    AssignabilityResult assignabilityResult =
+        CfAssignability.isLocalsAssignable(locals, frame.getLocals(), appView);
+    if (assignabilityResult.isFailed()) {
+      return error(assignabilityResult.asFailed().getMessage());
+    }
+    return this;
+  }
+
+  @Override
+  public CfFrameState checkStack(AppView<?> appView, CfFrame frame) {
+    AssignabilityResult assignabilityResult =
+        CfAssignability.isStackAssignable(stack, frame.getStack(), appView);
+    if (assignabilityResult.isFailed()) {
+      return error(assignabilityResult.asFailed().getMessage());
+    }
+    return this;
+  }
+
+  @Override
   public CfFrameState clear() {
     return bottom();
   }
 
   @Override
-  public CfFrameState markInitialized(FrameType uninitializedType, DexType initializedType) {
+  public CfFrameState markInitialized(
+      UninitializedFrameType uninitializedType, DexType initializedType) {
     if (uninitializedType.isInitialized()) {
       return error("Unexpected attempt to initialize already initialized type");
     }
     for (Int2ObjectMap.Entry<FrameType> entry : locals.int2ObjectEntrySet()) {
       FrameType frameType = entry.getValue();
-      FrameType initializedFrameType =
-          getInitializedFrameType(uninitializedType, frameType, initializedType);
-      entry.setValue(initializedFrameType);
+      if (frameType.isUninitialized()) {
+        entry.setValue(
+            getInitializedFrameType(
+                uninitializedType, frameType.asUninitialized(), initializedType));
+      }
     }
     // TODO(b/214496607): By using a collection that supports element replacement this could mutate
     //  the existing stack instead of building a new one.
-    ArrayDeque<FrameType> newStack = new ArrayDeque<>();
-    for (FrameType frameType : stack) {
-      FrameType initializedFrameType =
-          getInitializedFrameType(uninitializedType, frameType, initializedType);
-      newStack.addLast(initializedFrameType);
+    ArrayDeque<PreciseFrameType> newStack = new ArrayDeque<>();
+    for (PreciseFrameType frameType : stack) {
+      newStack.addLast(
+          frameType.isUninitialized()
+              ? getInitializedFrameType(
+                  uninitializedType, frameType.asUninitialized(), initializedType)
+              : frameType);
     }
     return new ConcreteCfFrameState(locals, newStack, stackHeight);
   }
@@ -110,12 +137,12 @@
   }
 
   @Override
-  public CfFrameState pop(BiFunction<CfFrameState, FrameType, CfFrameState> fn) {
+  public CfFrameState pop(BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn) {
     if (stack.isEmpty()) {
       // Return the same error as when popping from the bottom state.
       return bottom().pop();
     }
-    FrameType frameType = stack.removeLast();
+    PreciseFrameType frameType = stack.removeLast();
     stackHeight -= frameType.getWidth();
     return fn.apply(this, frameType);
   }
@@ -125,16 +152,17 @@
       AppView<?> appView, DexMethod constructor, CfAnalysisConfig config) {
     return pop(
         (state, frameType) -> {
-          if (frameType.isUninitializedObject()) {
+          if (frameType.isUninitialized()) {
             if (frameType.isUninitializedThis()) {
               if (constructor.getHolderType() == config.getCurrentContext().getHolderType()
                   || config.isImmediateSuperClassOfCurrentContext(constructor.getHolderType())) {
-                return state.markInitialized(frameType, config.getCurrentContext().getHolderType());
+                return state.markInitialized(
+                    frameType.asUninitializedThis(), config.getCurrentContext().getHolderType());
               }
             } else if (frameType.isUninitializedNew()) {
               DexType uninitializedNewType = frameType.getUninitializedNewType();
               if (constructor.getHolderType() == uninitializedNewType) {
-                return state.markInitialized(frameType, uninitializedNewType);
+                return state.markInitialized(frameType.asUninitializedNew(), uninitializedNewType);
               }
             }
             return popAndInitializeConstructorMismatchError(frameType, constructor, config);
@@ -144,8 +172,8 @@
   }
 
   private ErroneousCfFrameState popAndInitializeConstructorMismatchError(
-      FrameType frameType, DexMethod constructor, CfAnalysisConfig config) {
-    assert frameType.isUninitializedObject();
+      PreciseFrameType frameType, DexMethod constructor, CfAnalysisConfig config) {
+    assert frameType.isUninitialized();
     StringBuilder message = new StringBuilder("Constructor mismatch, expected constructor from ");
     if (frameType.isUninitializedNew()) {
       message.append(frameType.getUninitializedNewType().getTypeName());
@@ -159,7 +187,7 @@
     return error(message.toString());
   }
 
-  private ErroneousCfFrameState popAndInitializeInitializedObjectError(FrameType frameType) {
+  private ErroneousCfFrameState popAndInitializeInitializedObjectError(PreciseFrameType frameType) {
     return error("Unexpected attempt to initialize " + formatActual(frameType));
   }
 
@@ -167,7 +195,7 @@
   public CfFrameState popInitialized(
       AppView<?> appView,
       DexType expectedType,
-      BiFunction<CfFrameState, FrameType, CfFrameState> fn) {
+      BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn) {
     return pop(
         (state, frameType) -> {
           if (frameType.isInitialized()) {
@@ -195,7 +223,7 @@
   }
 
   @Override
-  public CfFrameState push(CfAnalysisConfig config, FrameType frameType) {
+  public CfFrameState push(CfAnalysisConfig config, PreciseFrameType frameType) {
     int newStackHeight = stackHeight + frameType.getWidth();
     if (newStackHeight > config.getMaxStack()) {
       return pushError(config, frameType);
@@ -205,7 +233,7 @@
     return this;
   }
 
-  private ErroneousCfFrameState pushError(CfAnalysisConfig config, FrameType frameType) {
+  private ErroneousCfFrameState pushError(CfAnalysisConfig config, PreciseFrameType frameType) {
     return error(
         "The max stack height of "
             + config.getMaxStack()
@@ -230,7 +258,7 @@
           frameType.getInitializedType(appView.dexItemFactory()), expectedType, appView)) {
         return fn.apply(this, frameType);
       }
-    } else if (frameType.isUninitializedObject() && expectedType.isObject()) {
+    } else if (frameType.isUninitialized() && expectedType.isObject()) {
       return fn.apply(this, frameType);
     }
     return errorUnexpectedLocal(frameType, expectedType, localIndex);
@@ -380,7 +408,7 @@
       SingleFrameType frameType,
       SingleFrameType otherFrameType,
       CfFrame.Builder builder) {
-    builder.store(localIndex, frameType.join(otherFrameType).asFrameType());
+    builder.store(localIndex, frameType.join(otherFrameType));
   }
 
   private void joinWideLocalsWithSameIndex(
@@ -388,7 +416,7 @@
       WideFrameType frameType,
       WideFrameType otherFrameType,
       CfFrame.Builder builder) {
-    builder.store(localIndex, frameType.join(otherFrameType).asFrameType());
+    builder.store(localIndex, frameType.join(otherFrameType));
   }
 
   // TODO(b/231521474): By splitting each wide type into single left/right types, the join of each
@@ -493,13 +521,13 @@
     setSingleLocalToTop(localIndex + 1, builder);
   }
 
-  private ErroneousCfFrameState joinStack(Deque<FrameType> stack, CfFrame.Builder builder) {
-    Iterator<FrameType> iterator = this.stack.iterator();
-    Iterator<FrameType> otherIterator = stack.iterator();
+  private ErroneousCfFrameState joinStack(Deque<PreciseFrameType> stack, CfFrame.Builder builder) {
+    Iterator<PreciseFrameType> iterator = this.stack.iterator();
+    Iterator<PreciseFrameType> otherIterator = stack.iterator();
     int stackIndex = 0;
     while (iterator.hasNext() && otherIterator.hasNext()) {
-      FrameType frameType = iterator.next();
-      FrameType otherFrameType = otherIterator.next();
+      PreciseFrameType frameType = iterator.next();
+      PreciseFrameType otherFrameType = otherIterator.next();
       if (frameType.isSingle() != otherFrameType.isSingle()) {
         return error(
             "Cannot join stacks, expected frame types at stack index "
@@ -509,19 +537,23 @@
                 + " and "
                 + formatActual(otherFrameType));
       }
+      PreciseFrameType preciseJoin;
       if (frameType.isSingle()) {
         SingleFrameType join = frameType.asSingle().join(otherFrameType.asSingle());
         if (join.isOneWord()) {
           return joinStackImpreciseJoinError(stackIndex, frameType, otherFrameType);
         }
-        builder.push(join.asFrameType());
+        assert join.isPrecise();
+        preciseJoin = join.asPrecise();
       } else {
         WideFrameType join = frameType.asWide().join(otherFrameType.asWide());
         if (join.isTwoWord()) {
           return joinStackImpreciseJoinError(stackIndex, frameType, otherFrameType);
         }
-        builder.push(join.asFrameType());
+        assert join.isPrecise();
+        preciseJoin = join.asPrecise();
       }
+      builder.push(preciseJoin);
       stackIndex++;
     }
     if (iterator.hasNext() || otherIterator.hasNext()) {
@@ -531,7 +563,7 @@
   }
 
   private ErroneousCfFrameState joinStackImpreciseJoinError(
-      int stackIndex, FrameType first, FrameType second) {
+      int stackIndex, PreciseFrameType first, PreciseFrameType second) {
     return error(
         "Cannot join stacks, expected frame types at stack index "
             + stackIndex
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java
index 47e2d24..5f8569b 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java
@@ -5,7 +5,9 @@
 package com.android.tools.r8.optimize.interfaces.analysis;
 
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.frame.FrameType;
+import com.android.tools.r8.cf.code.frame.PreciseFrameType;
+import com.android.tools.r8.cf.code.frame.UninitializedFrameType;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -52,7 +54,7 @@
   private static String format(FrameType frameType, FormatKind formatKind) {
     if (frameType.isInitialized()) {
       if (frameType.isObject()) {
-        DexType initializedType = frameType.asSingleInitializedType().getInitializedType();
+        DexType initializedType = frameType.asInitializedReferenceType().getInitializedType();
         if (initializedType.isArrayType()) {
           return initializedType.getTypeName();
         } else if (initializedType.isClassType()) {
@@ -65,7 +67,7 @@
         assert frameType.isPrimitive();
         return "primitive " + frameType.asPrimitive().getTypeName();
       }
-    } else if (frameType.isUninitializedObject()) {
+    } else if (frameType.isUninitialized()) {
       if (frameType.isUninitializedNew()) {
         DexType uninitializedNewType = frameType.getUninitializedNewType();
         if (uninitializedNewType != null) {
@@ -117,12 +119,23 @@
   }
 
   @Override
+  public CfFrameState checkLocals(AppView<?> appView, CfFrame frame) {
+    return this;
+  }
+
+  @Override
+  public CfFrameState checkStack(AppView<?> appView, CfFrame frame) {
+    return this;
+  }
+
+  @Override
   public CfFrameState clear() {
     return this;
   }
 
   @Override
-  public CfFrameState markInitialized(FrameType uninitializedType, DexType initializedType) {
+  public CfFrameState markInitialized(
+      UninitializedFrameType uninitializedType, DexType initializedType) {
     return this;
   }
 
@@ -132,7 +145,7 @@
   }
 
   @Override
-  public CfFrameState pop(BiFunction<CfFrameState, FrameType, CfFrameState> fn) {
+  public CfFrameState pop(BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn) {
     return this;
   }
 
@@ -146,7 +159,7 @@
   public CfFrameState popInitialized(
       AppView<?> appView,
       DexType expectedType,
-      BiFunction<CfFrameState, FrameType, CfFrameState> fn) {
+      BiFunction<CfFrameState, PreciseFrameType, CfFrameState> fn) {
     return this;
   }
 
@@ -161,7 +174,7 @@
   }
 
   @Override
-  public CfFrameState push(CfAnalysisConfig config, FrameType frameType) {
+  public CfFrameState push(CfAnalysisConfig config, PreciseFrameType frameType) {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
index 046931d..78abb50 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
@@ -25,7 +25,9 @@
       return empty();
     }
     InternalOptions options = appView.options();
-    if (!options.isOptimizing() || !options.isShrinking()) {
+    if (!options.isOptimizing()
+        || !options.isShrinking()
+        || !options.enableEnqueuerDeferredTracing) {
       return empty();
     }
     return new EnqueuerDeferredTracingImpl(appView, enqueuer, mode);
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 5aa62c3..b5898c5 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -1157,6 +1157,20 @@
           add(virtualMethods, shadowedBy, MethodSignatureEquivalence.get());
         }
 
+        // Copy over any keep info from the original virtual method.
+        ProgramMethod programMethod = new ProgramMethod(target, shadowedBy);
+        appView
+            .getKeepInfo()
+            .mutate(
+                mutableKeepInfoCollection ->
+                    mutableKeepInfoCollection.joinMethod(
+                        programMethod,
+                        info ->
+                            info.merge(
+                                mutableKeepInfoCollection
+                                    .getMethodInfo(virtualMethod, source)
+                                    .joiner())));
+
         deferredRenamings.map(virtualMethod.getReference(), shadowedBy.getReference());
         deferredRenamings.recordMove(
             virtualMethod.getReference(),
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index ffba372..e3d52a4 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -62,6 +62,21 @@
 
 public class SyntheticItems implements SyntheticDefinitionsProvider {
 
+  public boolean isSyntheticClassEligibleForMerging(DexProgramClass clazz) {
+    SyntheticDefinition<?, ?, ?> definition = pending.definitions.get(clazz.type);
+    if (definition != null) {
+      return definition.getKind().isShareable();
+    }
+    Iterable<SyntheticReference<?, ?, ?>> references = committed.getItems(clazz.type);
+    Iterator<SyntheticReference<?, ?, ?>> iterator = references.iterator();
+    if (iterator.hasNext()) {
+      boolean sharable = iterator.next().getKind().isShareable();
+      assert Iterables.all(references, r -> sharable == r.getKind().isShareable());
+      return sharable;
+    }
+    return false;
+  }
+
   public interface GlobalSyntheticsStrategy {
     ContextsForGlobalSynthetics getStrategy();
 
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 95120b2..f3b82e4d 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -391,6 +391,10 @@
   public boolean createSingletonsForStatelessLambdas =
       System.getProperty("com.android.tools.r8.createSingletonsForStatelessLambdas") != null;
 
+  // Flag to allow nest annotations in DEX. See b/231930852 for context.
+  public boolean emitNestAnnotationsInDex =
+      System.getProperty("com.android.tools.r8.emitNestAnnotationsInDex") != null;
+
   // Contain the contents of the build properties file from the compiler command.
   public DumpOptions dumpOptions;
 
@@ -607,6 +611,9 @@
   // public boolean lookupLibraryBeforeProgram =
   //     System.getProperty("com.android.tools.r8.lookupProgramBeforeLibrary") == null;
 
+  public boolean enableEnqueuerDeferredTracing =
+      System.getProperty("com.android.tools.r8.disableEnqueuerDeferredTracing") == null;
+
   public boolean loadAllClassDefinitions = false;
 
   // Whether or not to check for valid multi-dex builds.
@@ -2016,7 +2023,7 @@
   }
 
   public boolean canUseNestBasedAccess() {
-    return !isDesugaring();
+    return !isDesugaring() || emitNestAnnotationsInDex;
   }
 
   public boolean canUseRecords() {
diff --git a/src/test/java/com/android/tools/r8/BackportedMethodListTest.java b/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
index b2b78d1..dcf816e 100644
--- a/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
+++ b/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import static com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.isJDK11DesugaredLibrary;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -28,7 +28,8 @@
   enum Mode {
     NO_LIBRARY,
     LIBRARY,
-    LIBRARY_DESUGAR
+    LIBRARY_DESUGAR,
+    LIBRARY_DESUGAR_11
   }
 
   @Parameterized.Parameters(name = "Mode: {0}")
@@ -42,6 +43,16 @@
     this.mode = mode;
   }
 
+  private LibraryDesugaringSpecification getLibraryDesugaringSpecification() {
+    if (mode == Mode.LIBRARY_DESUGAR) {
+      return LibraryDesugaringSpecification.JDK8;
+    }
+    if (mode == Mode.LIBRARY_DESUGAR_11) {
+      return LibraryDesugaringSpecification.JDK11;
+    }
+    return null;
+  }
+
   private static class ListStringConsumer implements StringConsumer {
     List<String> strings = new ArrayList<>();
     boolean finished = false;
@@ -71,17 +82,14 @@
     // Java 9, 10 and 11 Optional methods which require Android N or library desugaring.
     // The methods are not backported in desugared library JDK 11 (already present).
     assertEquals(
-        (mode == Mode.LIBRARY_DESUGAR && !isJDK11DesugaredLibrary())
-            || apiLevel >= AndroidApiLevel.N.getLevel(),
+        (mode == Mode.LIBRARY_DESUGAR) || apiLevel >= AndroidApiLevel.N.getLevel(),
         backports.contains(
             "java/util/Optional#or(Ljava/util/function/Supplier;)Ljava/util/Optional;"));
     assertEquals(
-        (mode == Mode.LIBRARY_DESUGAR && !isJDK11DesugaredLibrary())
-            || apiLevel >= AndroidApiLevel.N.getLevel(),
+        (mode == Mode.LIBRARY_DESUGAR) || apiLevel >= AndroidApiLevel.N.getLevel(),
         backports.contains("java/util/OptionalInt#orElseThrow()I"));
     assertEquals(
-        (mode == Mode.LIBRARY_DESUGAR && !isJDK11DesugaredLibrary())
-            || apiLevel >= AndroidApiLevel.N.getLevel(),
+        (mode == Mode.LIBRARY_DESUGAR) || apiLevel >= AndroidApiLevel.N.getLevel(),
         backports.contains("java/util/OptionalLong#isEmpty()Z"));
 
     // Java 9, 10 and 11 method added at API level S.
@@ -103,8 +111,8 @@
   private void addLibraryDesugaring(BackportedMethodListCommand.Builder builder) {
     builder
         .addDesugaredLibraryConfiguration(
-            StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()))
-        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.R.getLevel()));
+            StringResource.fromFile(getLibraryDesugaringSpecification().getSpecification()))
+        .addLibraryFiles(getLibraryDesugaringSpecification().getLibraryFiles());
   }
 
   @Test
@@ -115,7 +123,7 @@
           BackportedMethodListCommand.builder().setMinApiLevel(apiLevel).setConsumer(consumer);
       if (mode == Mode.LIBRARY) {
         builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P.getLevel()));
-      } else if (mode == Mode.LIBRARY_DESUGAR) {
+      } else if (mode == Mode.LIBRARY_DESUGAR || mode == Mode.LIBRARY_DESUGAR_11) {
         addLibraryDesugaring(builder);
       }
       BackportedMethodList.run(builder.build());
@@ -132,7 +140,7 @@
           BackportedMethodListCommand.builder().setMinApiLevel(apiLevel).setOutputPath(output);
       if (mode == Mode.LIBRARY) {
         builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P.getLevel()));
-      } else if (mode == Mode.LIBRARY_DESUGAR) {
+      } else if (mode == Mode.LIBRARY_DESUGAR || mode == Mode.LIBRARY_DESUGAR_11) {
         addLibraryDesugaring(builder);
       }
       BackportedMethodList.run(builder.build());
@@ -152,13 +160,13 @@
 
   @Test
   public void requireLibraryForDesugar() {
-    Assume.assumeTrue(mode == Mode.LIBRARY_DESUGAR);
+    Assume.assumeTrue(mode == Mode.LIBRARY_DESUGAR || mode == Mode.LIBRARY_DESUGAR_11);
     // Require library when a desugar configuration is passed.
     try {
       BackportedMethodList.run(
           BackportedMethodListCommand.builder()
               .addDesugaredLibraryConfiguration(
-                  StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()))
+                  StringResource.fromFile(getLibraryDesugaringSpecification().getSpecification()))
               .setConsumer(new ListStringConsumer())
               .build());
       fail("Expected failure");
diff --git a/src/test/java/com/android/tools/r8/CommandTestBase.java b/src/test/java/com/android/tools/r8/CommandTestBase.java
index 733fb9a..1c0c588 100644
--- a/src/test/java/com/android/tools/r8/CommandTestBase.java
+++ b/src/test/java/com/android/tools/r8/CommandTestBase.java
@@ -7,8 +7,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.AppForSpecConversion;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
@@ -311,11 +311,12 @@
   protected InternalOptions getOptionsWithLoadedDesugaredLibraryConfiguration(
       C command, boolean libraryCompilation) throws IOException {
     InternalOptions options = command.getInternalOptions();
+    LibraryDesugaringSpecification spec = LibraryDesugaringSpecification.JDK11;
     options.loadMachineDesugaredLibrarySpecification(
         Timing.empty(),
         AppForSpecConversion.readAppForTesting(
-            libraryCompilation ? ToolHelper.getDesugarJDKLibs() : null,
-            ToolHelper.getAndroidJar(AndroidApiLevel.R),
+            libraryCompilation ? spec.getDesugarJdkLibs() : null,
+            spec.getLibraryFiles(),
             options,
             libraryCompilation,
             Timing.empty()));
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index 1ec4d18..d14b01f 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -6,9 +6,7 @@
 import com.android.tools.r8.D8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.benchmarks.BenchmarkResults;
-import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import java.nio.file.Path;
@@ -90,15 +88,6 @@
     return self();
   }
 
-  @Override
-  public D8TestBuilder enableCoreLibraryDesugaring(
-      AndroidApiLevel minApiLevel,
-      KeepRuleConsumer keepRuleConsumer,
-      StringResource desugaredLibrarySpecification) {
-    super.enableCoreLibraryDesugaring(minApiLevel, keepRuleConsumer, desugaredLibrarySpecification);
-    return self();
-  }
-
   public D8TestBuilder addMainDexRulesFiles(Path... mainDexRuleFiles) {
     builder.addMainDexRulesFiles(mainDexRuleFiles);
     return self();
diff --git a/src/test/java/com/android/tools/r8/L8CommandTest.java b/src/test/java/com/android/tools/r8/L8CommandTest.java
index dbc7ebd..3526d0c 100644
--- a/src/test/java/com/android/tools/r8/L8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/L8CommandTest.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.MarkerMatcher.assertMarkersMatch;
 import static com.android.tools.r8.MarkerMatcher.markerTool;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -15,11 +16,11 @@
 
 import com.android.tools.r8.AssertionsConfiguration.AssertionTransformationScope;
 import com.android.tools.r8.StringConsumer.FileConsumer;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.origin.EmbeddedOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -41,19 +42,27 @@
 @RunWith(Parameterized.class)
 public class L8CommandTest extends CommandTestBase<L8Command> {
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withNoneRuntime().build();
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withNoneRuntime().build(), getJdk8Jdk11());
   }
 
-  public L8CommandTest(TestParameters parameters) {
+  public L8CommandTest(
+      TestParameters parameters, LibraryDesugaringSpecification libraryDesugaringSpecification) {
     parameters.assertNoneRuntime();
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
   }
 
   protected final Matcher<Diagnostic> cfL8NotSupportedDiagnostic =
       diagnosticMessage(
           containsString("L8 does not support shrinking when generating class files"));
 
+  private StringResource getDesugaredLibraryConfiguration() {
+    return StringResource.fromFile(libraryDesugaringSpecification.getSpecification());
+  }
+
   @Test(expected = CompilationFailedException.class)
   public void emptyBuilder() throws Throwable {
     verifyEmptyCommand(L8Command.builder().build());
@@ -64,8 +73,7 @@
     verifyEmptyCommand(
         L8Command.builder()
             .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
-            .addDesugaredLibraryConfiguration(
-                StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()))
+            .addDesugaredLibraryConfiguration(getDesugaredLibraryConfiguration())
             .build());
   }
 
@@ -82,11 +90,10 @@
     Path output = temp.newFolder().toPath().resolve("desugar_jdk_libs.zip");
     L8.run(
         L8Command.builder()
-            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
-            .addProgramFiles(ToolHelper.getDesugarJDKLibs())
+            .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
+            .addProgramFiles(libraryDesugaringSpecification.getDesugarJdkLibs())
             .setMinApiLevel(20)
-            .addDesugaredLibraryConfiguration(
-                StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()))
+            .addDesugaredLibraryConfiguration(getDesugaredLibraryConfiguration())
             .setOutput(output, OutputMode.DexIndexed)
             .build());
     assertMarkersMatch(
@@ -99,30 +106,38 @@
     Path output = temp.newFolder().toPath().resolve("desugar_jdk_libs.zip");
     L8.run(
         L8Command.builder()
-            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
-            .addProgramFiles(ToolHelper.getDesugarJDKLibs())
+            .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
+            .addProgramFiles(libraryDesugaringSpecification.getDesugarJdkLibs())
             .setMinApiLevel(20)
-            .addDesugaredLibraryConfiguration(
-                StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()))
+            .addDesugaredLibraryConfiguration(getDesugaredLibraryConfiguration())
             .setOutput(output, OutputMode.ClassFile)
             .build());
     assertMarkersMatch(ExtractMarker.extractMarkerFromDexFile(output), markerTool(Tool.L8));
   }
 
+  private List<String> buildCommand(int minAPI, Path output) {
+    ArrayList<String> command = new ArrayList<>();
+    for (Path desugarJDKLib : libraryDesugaringSpecification.getDesugarJdkLibs()) {
+      command.add(desugarJDKLib.toString());
+    }
+    for (Path libraryFile : libraryDesugaringSpecification.getLibraryFiles()) {
+      command.add("--lib");
+      command.add(libraryFile.toString());
+    }
+    command.add("--min-api");
+    command.add(Integer.toString(minAPI));
+    command.add("--desugared-lib");
+    command.add(libraryDesugaringSpecification.getSpecification().toString());
+    command.add("--output");
+    command.add(output.toString());
+    return command;
+  }
+
   @Test
   public void testDexMarkerCommandLine() throws Throwable {
     Path output = temp.newFolder().toPath().resolve("desugar_jdk_libs.zip");
-    L8Command l8Command =
-        parse(
-            ToolHelper.getDesugarJDKLibs().toString(),
-            "--lib",
-            ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
-            "--min-api",
-            "20",
-            "--desugared-lib",
-            ToolHelper.getDesugarLibJsonForTesting().toString(),
-            "--output",
-            output.toString());
+    List<String> command = buildCommand(20, output);
+    L8Command l8Command = parse(command.toArray(new String[0]));
     L8.run(l8Command);
     assertMarkersMatch(
         ExtractMarker.extractMarkerFromDexFile(output),
@@ -132,18 +147,9 @@
   @Test
   public void testClassFileMarkerCommandLine() throws Throwable {
     Path output = temp.newFolder().toPath().resolve("desugar_jdk_libs.zip");
-    L8Command l8Command =
-        parse(
-            ToolHelper.getDesugarJDKLibs().toString(),
-            "--lib",
-            ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
-            "--min-api",
-            "20",
-            "--desugared-lib",
-            ToolHelper.getDesugarLibJsonForTesting().toString(),
-            "--output",
-            output.toString(),
-            "--classfile");
+    List<String> command = buildCommand(20, output);
+    command.add("--classfile");
+    L8Command l8Command = parse(command.toArray(new String[0]));
     L8.run(l8Command);
     assertMarkersMatch(ExtractMarker.extractMarkerFromDexFile(output), markerTool(Tool.L8));
   }
@@ -156,7 +162,7 @@
     parse(
         diagnostics,
         "--desugared-lib",
-        ToolHelper.getDesugarLibJsonForTesting().toString(),
+        libraryDesugaringSpecification.getSpecification().toString(),
         "--pg-conf",
         pgconf.toString());
   }
@@ -171,7 +177,7 @@
         parse(
             diagnostics,
             "--desugared-lib",
-            ToolHelper.getDesugarLibJsonForTesting().toString(),
+            libraryDesugaringSpecification.getSpecification().toString(),
             "--pg-conf",
             pgconf.toString(),
             "--pg-map-output",
@@ -193,7 +199,7 @@
       parse(
           diagnostics,
           "--desugared-lib",
-          ToolHelper.getDesugarLibJsonForTesting().toString(),
+          libraryDesugaringSpecification.getSpecification().toString(),
           "--pg-conf",
           pgconf.toString(),
           "--classfile");
@@ -210,7 +216,7 @@
       parse(
           diagnostics,
           "--desugared-lib",
-          ToolHelper.getDesugarLibJsonForTesting().toString(),
+          libraryDesugaringSpecification.getSpecification().toString(),
           "--pg-conf");
       fail("Expected parse error");
     } catch (CompilationFailedException e) {
@@ -220,8 +226,8 @@
 
   private L8Command.Builder prepareBuilder(DiagnosticsHandler handler) {
     return L8Command.builder(handler)
-        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
-        .addProgramFiles(ToolHelper.getDesugarJDKLibs())
+        .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
+        .addProgramFiles(libraryDesugaringSpecification.getDesugarJdkLibs())
         .setMinApiLevel(20);
   }
 
@@ -286,8 +292,7 @@
     L8Command.Builder builder =
         prepareBuilder(diagnostics)
             .setProgramConsumer(programConsumer)
-            .addDesugaredLibraryConfiguration(
-                StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()))
+            .addDesugaredLibraryConfiguration(getDesugaredLibraryConfiguration())
             .addProguardConfiguration(keepRules, Origin.unknown());
     assertTrue(builder.isShrinking());
     assertNotNull(builder.build().getR8Command());
@@ -319,8 +324,7 @@
     L8Command.Builder builder1 =
         prepareBuilder(diagnostics)
             .setProgramConsumer(programConsumer)
-            .addDesugaredLibraryConfiguration(
-                StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()))
+            .addDesugaredLibraryConfiguration(getDesugaredLibraryConfiguration())
             .addProguardConfigurationFiles(keepRuleFile);
     assertTrue(builder1.isShrinking());
     assertNotNull(builder1.build().getR8Command());
@@ -330,8 +334,7 @@
     L8Command.Builder builder2 =
         prepareBuilder(new TestDiagnosticMessagesImpl())
             .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
-            .addDesugaredLibraryConfiguration(
-                StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()))
+            .addDesugaredLibraryConfiguration(getDesugaredLibraryConfiguration())
             .addProguardConfigurationFiles(keepRuleFiles);
     assertTrue(builder2.isShrinking());
     assertNotNull(builder2.build().getR8Command());
@@ -356,12 +359,14 @@
 
   @Test
   public void desugaredLibrary() throws CompilationFailedException, IOException {
-    L8Command l8Command =
-        parse(
-            "--desugared-lib",
-            ToolHelper.getDesugarLibJsonForTesting().toString(),
-            "--lib",
-            ToolHelper.getAndroidJar(AndroidApiLevel.R).toString());
+    ArrayList<String> command = new ArrayList<>();
+    command.add("--desugared-lib");
+    command.add(libraryDesugaringSpecification.getSpecification().toString());
+    for (Path libraryFile : libraryDesugaringSpecification.getLibraryFiles()) {
+      command.add("--lib");
+      command.add(libraryFile.toString());
+    }
+    L8Command l8Command = parse(command.toArray(new String[0]));
     InternalOptions options = getOptionsWithLoadedDesugaredLibraryConfiguration(l8Command, true);
     assertFalse(options.machineDesugaredLibrarySpecification.getRewriteType().isEmpty());
   }
@@ -403,21 +408,21 @@
         parse(
                 "--force-enable-assertions",
                 "--desugared-lib",
-                ToolHelper.getDesugarLibJsonForTesting().toString())
+                libraryDesugaringSpecification.getSpecification().toString())
             .getAssertionsConfiguration(),
         AssertionsConfiguration::isCompileTimeEnabled);
     checkSingleForceAllAssertion(
         parse(
                 "--force-disable-assertions",
                 "--desugared-lib",
-                ToolHelper.getDesugarLibJsonForTesting().toString())
+                libraryDesugaringSpecification.getSpecification().toString())
             .getAssertionsConfiguration(),
         AssertionsConfiguration::isCompileTimeDisabled);
     checkSingleForceAllAssertion(
         parse(
                 "--force-passthrough-assertions",
                 "--desugared-lib",
-                ToolHelper.getDesugarLibJsonForTesting().toString())
+                libraryDesugaringSpecification.getSpecification().toString())
             .getAssertionsConfiguration(),
         AssertionsConfiguration::isPassthrough);
     checkSingleForceClassAndPackageAssertion(
@@ -425,7 +430,7 @@
                 "--force-enable-assertions:ClassName",
                 "--force-enable-assertions:PackageName...",
                 "--desugared-lib",
-                ToolHelper.getDesugarLibJsonForTesting().toString())
+                libraryDesugaringSpecification.getSpecification().toString())
             .getAssertionsConfiguration(),
         AssertionsConfiguration::isCompileTimeEnabled);
     checkSingleForceClassAndPackageAssertion(
@@ -433,7 +438,7 @@
                 "--force-disable-assertions:ClassName",
                 "--force-disable-assertions:PackageName...",
                 "--desugared-lib",
-                ToolHelper.getDesugarLibJsonForTesting().toString())
+                libraryDesugaringSpecification.getSpecification().toString())
             .getAssertionsConfiguration(),
         AssertionsConfiguration::isCompileTimeDisabled);
     checkSingleForceClassAndPackageAssertion(
@@ -441,14 +446,14 @@
                 "--force-passthrough-assertions:ClassName",
                 "--force-passthrough-assertions:PackageName...",
                 "--desugared-lib",
-                ToolHelper.getDesugarLibJsonForTesting().toString())
+                libraryDesugaringSpecification.getSpecification().toString())
             .getAssertionsConfiguration(),
         AssertionsConfiguration::isPassthrough);
     checkSingleForceAllAssertion(
         parse(
                 "--force-assertions-handler:com.example.MyHandler.handler",
                 "--desugared-lib",
-                ToolHelper.getDesugarLibJsonForTesting().toString())
+                libraryDesugaringSpecification.getSpecification().toString())
             .getAssertionsConfiguration(),
         configuration ->
             configuration.isAssertionHandler()
@@ -466,7 +471,7 @@
                 "--force-assertions-handler:com.example.MyHandler.handler1:ClassName",
                 "--force-assertions-handler:com.example.MyHandler.handler2:PackageName...",
                 "--desugared-lib",
-                ToolHelper.getDesugarLibJsonForTesting().toString())
+                libraryDesugaringSpecification.getSpecification().toString())
             .getAssertionsConfiguration(),
         configuration ->
             configuration.isAssertionHandler()
@@ -496,7 +501,7 @@
   public void numThreadsOption() throws Exception {
     assertEquals(
         ThreadUtils.NOT_SPECIFIED,
-        parse("--desugared-lib", ToolHelper.getDesugarLibJsonForTesting().toString())
+        parse("--desugared-lib", libraryDesugaringSpecification.getSpecification().toString())
             .getThreadCount());
     assertEquals(
         1,
@@ -504,7 +509,7 @@
                 "--thread-count",
                 "1",
                 "--desugared-lib",
-                ToolHelper.getDesugarLibJsonForTesting().toString())
+                libraryDesugaringSpecification.getSpecification().toString())
             .getThreadCount());
     assertEquals(
         2,
@@ -512,7 +517,7 @@
                 "--thread-count",
                 "2",
                 "--desugared-lib",
-                ToolHelper.getDesugarLibJsonForTesting().toString())
+                libraryDesugaringSpecification.getSpecification().toString())
             .getThreadCount());
     assertEquals(
         10,
@@ -520,7 +525,7 @@
                 "--thread-count",
                 "10",
                 "--desugared-lib",
-                ToolHelper.getDesugarLibJsonForTesting().toString())
+                libraryDesugaringSpecification.getSpecification().toString())
             .getThreadCount());
   }
 
@@ -535,7 +540,7 @@
                   "--thread-count",
                   value,
                   "--desugared-lib",
-                  ToolHelper.getDesugarLibJsonForTesting().toString()));
+                  libraryDesugaringSpecification.getSpecification().toString()));
       fail("Expected failure");
     } catch (CompilationFailedException e) {
       // Expected.
@@ -551,7 +556,9 @@
 
   @Override
   String[] requiredArgsForTest() {
-    return new String[] {"--desugared-lib", ToolHelper.getDesugarLibJsonForTesting().toString()};
+    return new String[] {
+      "--desugared-lib", libraryDesugaringSpecification.getSpecification().toString()
+    };
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/MarkersTest.java b/src/test/java/com/android/tools/r8/MarkersTest.java
index 758b061..d5a61db 100644
--- a/src/test/java/com/android/tools/r8/MarkersTest.java
+++ b/src/test/java/com/android/tools/r8/MarkersTest.java
@@ -14,12 +14,15 @@
 import static com.android.tools.r8.MarkerMatcher.markerMinApi;
 import static com.android.tools.r8.MarkerMatcher.markerR8Mode;
 import static com.android.tools.r8.MarkerMatcher.markerTool;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
 import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 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.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.origin.Origin;
@@ -41,27 +44,27 @@
 public class MarkersTest extends DesugaredLibraryTestBase {
 
   @Parameterized.Parameters(
-      name = "{0}, compilationMode {1}, shrinkDesugaredLibrary {2}, noCfMarkerForDesugaredCode {3}")
+      name = "{0}, spec: {1}, compilationMode {2}, {3}, noCfMarkerForDesugaredCode {4}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withNoneRuntime().build(),
-        CompilationMode.values(),
-        BooleanUtils.values(),
+        LibraryDesugaringSpecification.getJdk8Jdk11(),
+        DEFAULT_SPECIFICATIONS,
         BooleanUtils.values());
   }
 
-  private final CompilationMode compilationMode;
-  private final boolean shrinkDesugaredLibrary;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
   private final boolean noCfMarkerForDesugaredCode;
 
   public MarkersTest(
       TestParameters parameters,
-      CompilationMode compilationMode,
-      boolean shrinkDesugaredLibrary,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification,
       boolean noCfMarkerForDesugaredCode) {
     parameters.assertNoneRuntime();
-    this.compilationMode = compilationMode;
-    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
     this.noCfMarkerForDesugaredCode = noCfMarkerForDesugaredCode;
   }
 
@@ -72,24 +75,27 @@
 
     AndroidApiLevel apiLevel = AndroidApiLevel.L;
     Path output = temp.newFolder().toPath().resolve("desugar_jdk_libs.zip");
+    CompilationMode compilationMode =
+        compilationSpecification.isL8Shrink() ? CompilationMode.RELEASE : CompilationMode.DEBUG;
     L8Command.Builder builder =
         L8Command.builder()
-            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
-            .addProgramFiles(ToolHelper.getDesugarJDKLibs())
-            .addProgramFiles(ToolHelper.DESUGAR_LIB_CONVERSIONS)
+            .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
+            .addProgramFiles(libraryDesugaringSpecification.getDesugarJdkLibs())
             .setMinApiLevel(apiLevel.getLevel())
             .setMode(compilationMode)
             .addDesugaredLibraryConfiguration(
-                StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()))
+                StringResource.fromFile(libraryDesugaringSpecification.getSpecification()))
             .setOutput(output, OutputMode.DexIndexed);
-    if (shrinkDesugaredLibrary) {
+    if (compilationSpecification.isL8Shrink()) {
       builder.addProguardConfiguration(ImmutableList.of("-keep class * { *; }"), Origin.unknown());
     }
     L8.run(builder.build());
     Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(output);
     JsonObject jsonObject =
         new JsonParser()
-            .parse(FileUtils.readTextFile(ToolHelper.getDesugarLibJsonForTesting(), Charsets.UTF_8))
+            .parse(
+                FileUtils.readTextFile(
+                    libraryDesugaringSpecification.getSpecification(), Charsets.UTF_8))
             .getAsJsonObject();
     String identifier =
         jsonObject.has("version")
@@ -111,7 +117,8 @@
     Matcher<Marker> d8Matcher =
         allOf(markerTool(Tool.D8), markerCompilationMode(compilationMode), markerMinApi(apiLevel));
     assertMarkersMatch(
-        markers, ImmutableList.of(l8Matcher, shrinkDesugaredLibrary ? r8Matcher : d8Matcher));
+        markers,
+        ImmutableList.of(l8Matcher, compilationSpecification.isL8Shrink() ? r8Matcher : d8Matcher));
   }
 
   @Test
@@ -122,7 +129,7 @@
         D8Command.builder()
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
             .addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class))
-            .setMode(compilationMode)
+            .setMode(compilationSpecification.getProgramCompilationMode())
             .setMinApiLevel(apiLevel.getLevel())
             .setOutput(output, OutputMode.DexIndexed);
     if (noCfMarkerForDesugaredCode) {
@@ -135,7 +142,7 @@
     Matcher<Marker> matcher =
         allOf(
             markerTool(Tool.D8),
-            markerCompilationMode(compilationMode),
+            markerCompilationMode(compilationSpecification.getProgramCompilationMode()),
             markerBackend(Backend.DEX),
             markerIsDesugared(),
             markerMinApi(apiLevel),
@@ -146,7 +153,7 @@
   @Test
   public void testD8MarkerInCf() throws Throwable {
     // Shrinking of desugared library is not affecting this test.
-    assumeTrue(shrinkDesugaredLibrary);
+    assumeTrue(compilationSpecification.isL8Shrink());
 
     AndroidApiLevel apiLevel = AndroidApiLevel.L;
     Path output = temp.newFolder().toPath().resolve("output.zip");
@@ -154,7 +161,7 @@
         D8Command.builder()
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
             .addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class))
-            .setMode(compilationMode)
+            .setMode(compilationSpecification.getProgramCompilationMode())
             .setMinApiLevel(apiLevel.getLevel())
             .setOutput(output, OutputMode.ClassFile);
     if (noCfMarkerForDesugaredCode) {
@@ -168,7 +175,7 @@
       Matcher<Marker> matcher =
           allOf(
               markerTool(Tool.D8),
-              markerCompilationMode(compilationMode),
+              markerCompilationMode(compilationSpecification.getProgramCompilationMode()),
               markerBackend(Backend.CF),
               markerIsDesugared(),
               markerMinApi(apiLevel),
@@ -180,7 +187,7 @@
   @Test
   public void testR8MarkerInDex() throws Throwable {
     // Shrinking of desugared library is not affecting this test.
-    assumeTrue(shrinkDesugaredLibrary);
+    assumeTrue(compilationSpecification.isL8Shrink());
 
     AndroidApiLevel apiLevel = AndroidApiLevel.L;
     Path output = temp.newFolder().toPath().resolve("output.zip");
@@ -189,7 +196,7 @@
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
             .addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class))
             .addProguardConfiguration(ImmutableList.of("-keep class * { *; }"), Origin.unknown())
-            .setMode(compilationMode)
+            .setMode(compilationSpecification.getProgramCompilationMode())
             .setMinApiLevel(apiLevel.getLevel())
             .setOutput(output, OutputMode.DexIndexed);
     if (noCfMarkerForDesugaredCode) {
@@ -203,7 +210,7 @@
     Matcher<Marker> matcher =
         allOf(
             markerTool(Tool.R8),
-            markerCompilationMode(compilationMode),
+            markerCompilationMode(compilationSpecification.getProgramCompilationMode()),
             markerBackend(Backend.DEX),
             markerIsDesugared(),
             markerMinApi(apiLevel),
@@ -214,7 +221,7 @@
   @Test
   public void testR8MarkerInCf() throws Throwable {
     // Shrinking of desugared library is not affecting this test.
-    assumeTrue(shrinkDesugaredLibrary);
+    assumeTrue(compilationSpecification.isL8Shrink());
 
     Path output = temp.newFolder().toPath().resolve("output.zip");
     R8Command.Builder builder =
@@ -222,7 +229,7 @@
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
             .addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class))
             .addProguardConfiguration(ImmutableList.of("-keep class * { *; }"), Origin.unknown())
-            .setMode(compilationMode)
+            .setMode(compilationSpecification.getProgramCompilationMode())
             .setOutput(output, OutputMode.ClassFile);
     if (noCfMarkerForDesugaredCode) {
       ToolHelper.runR8(
@@ -235,7 +242,7 @@
     Matcher<Marker> matcher =
         allOf(
             markerTool(Tool.R8),
-            markerCompilationMode(compilationMode),
+            markerCompilationMode(compilationSpecification.getProgramCompilationMode()),
             markerBackend(Backend.CF),
             not(markerIsDesugared()),
             not(markerHasMinApi()),
@@ -246,7 +253,7 @@
   @Test
   public void testR8MarkerInCfAfterD8CfDesugar() throws Throwable {
     // Shrinking of desugared library is not affecting this test.
-    assumeTrue(shrinkDesugaredLibrary);
+    assumeTrue(compilationSpecification.isL8Shrink());
 
     AndroidApiLevel apiLevel = AndroidApiLevel.L;
     Path d8DesugaredOutput = temp.newFolder().toPath().resolve("output.zip");
@@ -254,7 +261,7 @@
         D8Command.builder()
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
             .addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class))
-            .setMode(compilationMode)
+            .setMode(compilationSpecification.getProgramCompilationMode())
             .setMinApiLevel(apiLevel.getLevel())
             .setOutput(d8DesugaredOutput, OutputMode.ClassFile);
     if (noCfMarkerForDesugaredCode) {
@@ -268,7 +275,7 @@
           ExtractMarker.extractMarkerFromDexFile(d8DesugaredOutput),
           allOf(
               markerTool(Tool.D8),
-              markerCompilationMode(compilationMode),
+              markerCompilationMode(compilationSpecification.getProgramCompilationMode()),
               markerIsDesugared(),
               markerMinApi(apiLevel),
               not(markerHasDesugaredLibraryIdentifier())));
@@ -281,14 +288,14 @@
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
             .addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class))
             .addProguardConfiguration(ImmutableList.of("-keep class * { *; }"), Origin.unknown())
-            .setMode(compilationMode)
+            .setMode(compilationSpecification.getProgramCompilationMode())
             .setOutput(output, OutputMode.ClassFile)
             .build());
     assertMarkersMatch(
         ExtractMarker.extractMarkerFromDexFile(output),
         allOf(
             markerTool(Tool.R8),
-            markerCompilationMode(compilationMode),
+            markerCompilationMode(compilationSpecification.getProgramCompilationMode()),
             markerBackend(Backend.CF),
             not(markerIsDesugared()),
             not(markerHasMinApi()),
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 10bc820..ff90c43 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -696,15 +696,6 @@
     return enableProguardTestOptions();
   }
 
-  @Override
-  public T enableCoreLibraryDesugaring(
-      AndroidApiLevel minApiLevel,
-      KeepRuleConsumer keepRuleConsumer,
-      StringResource desugaredLibrarySpecification) {
-    super.enableCoreLibraryDesugaring(minApiLevel, keepRuleConsumer, desugaredLibrarySpecification);
-    return self();
-  }
-
   public T addFeatureSplitRuntime() {
     addProgramClasses(SplitRunner.class, RunInterface.class);
     addKeepClassAndMembersRules(SplitRunner.class, RunInterface.class);
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index ead3195..d7a6cc2 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -494,43 +494,11 @@
     return allowStderrMessages();
   }
 
-  public T enableCoreLibraryDesugaring(
-      AndroidApiLevel minApiLevel, KeepRuleConsumer keepRuleConsumer) {
-    return enableCoreLibraryDesugaring(
-        minApiLevel,
-        keepRuleConsumer,
-        StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()));
-  }
-
   public T enableCoreLibraryDesugaring(LibraryDesugaringTestConfiguration configuration) {
     this.libraryDesugaringTestConfiguration = configuration;
     return self();
   }
 
-  public T enableCoreLibraryDesugaring(
-      AndroidApiLevel minApiLevel,
-      KeepRuleConsumer keepRuleConsumer,
-      StringResource desugaredLibrarySpecification) {
-    return enableLibraryDesugaring(
-        LibraryDesugaringTestConfiguration.builder()
-            .setMinApi(minApiLevel)
-            .setKeepRuleConsumer(keepRuleConsumer)
-            .addDesugaredLibraryConfiguration(desugaredLibrarySpecification)
-            .dontAddRunClasspath()
-            .build());
-  }
-
-  public T enableLibraryDesugaring(AndroidApiLevel minApiLevel) {
-    this.libraryDesugaringTestConfiguration =
-        LibraryDesugaringTestConfiguration.builder().setMinApi(minApiLevel).build();
-    return self();
-  }
-
-  public T enableLibraryDesugaring(LibraryDesugaringTestConfiguration configuration) {
-    this.libraryDesugaringTestConfiguration = configuration;
-    return self();
-  }
-
   @Override
   public T addRunClasspathFiles(Collection<Path> files) {
     additionalRunClassPath.addAll(files);
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index bc5f2e4..e7c64cd 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -184,7 +184,7 @@
       OPEN_JDK_DIR + "desugar_jdk_libs_releases/";
   public static final Path DESUGARED_JDK_8_LIB_JAR =
       Paths.get(OPEN_JDK_DIR + "desugar_jdk_libs/desugar_jdk_libs.jar");
-  public static final Path DESUGARED_JDK_11_LIB_JAR =
+  public static final Path UNDESUGARED_JDK_11_LIB_JAR =
       DesugaredLibraryJDK11Undesugarer.undesugaredJarJDK11(
           Paths.get(OPEN_JDK_DIR + "desugar_jdk_libs_11/desugar_jdk_libs.jar"));
 
@@ -204,26 +204,10 @@
     return System.getProperty("desugar_jdk_json_dir", "src/library_desugar");
   }
 
-  public static Path getDesugarLibJsonMinimalForTesting() {
-    return Paths.get(getDesugarLibraryJsonDir(), "desugar_jdk_libs_minimal.json");
-  }
-
   public static Path getDesugarLibJsonForTesting() {
     return Paths.get(getDesugarLibraryJsonDir(), "desugar_jdk_libs.json");
   }
 
-  public static Path getDesugarLibJsonForTestingWithPath() {
-    return Paths.get(getDesugarLibraryJsonDir(), "desugar_jdk_libs_path.json");
-  }
-
-  public static Path getCHMOnlyDesugarLibJsonForTesting() {
-    return Paths.get(getDesugarLibraryJsonDir(), "chm_only_desugar_jdk_libs.json");
-  }
-
-  public static Path getDesugarLibJsonForTestingAlternative3() {
-    return Paths.get(getDesugarLibraryJsonDir(), "desugar_jdk_libs_path_alternative_3.json");
-  }
-
   public static boolean isLocalDevelopment() {
     return System.getProperty("local_development", "0").equals("1");
   }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelDesugaredLibraryReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelDesugaredLibraryReferenceTest.java
index dc66150..9ff8a9b 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelDesugaredLibraryReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelDesugaredLibraryReferenceTest.java
@@ -4,12 +4,17 @@
 
 package com.android.tools.r8.apimodel;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.R8_L8DEBUG;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.R8_L8SHRINK;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.google.common.collect.ImmutableList;
 import java.lang.reflect.Method;
 import java.time.Clock;
 import java.util.List;
@@ -22,45 +27,43 @@
 public class ApiModelDesugaredLibraryReferenceTest extends DesugaredLibraryTestBase {
 
   private final TestParameters parameters;
-  private final boolean shrinkDesugaredLibrary;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
 
-  @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+  @Parameters(name = "{0}, spec: {1}, {2}")
   public static List<Object[]> data() {
     return buildParameters(
-        getTestParameters().withDexRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        getJdk8Jdk11(),
+        ImmutableList.of(R8_L8DEBUG, R8_L8SHRINK));
   }
 
   public ApiModelDesugaredLibraryReferenceTest(
-      TestParameters parameters, boolean shrinkDesugaredLibrary) {
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
     this.parameters = parameters;
-    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
   }
 
   @Test
-  public void testClockR8() throws Exception {
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+  public void testClock() throws Throwable {
     Method printZone = DesugaredLibUser.class.getDeclaredMethod("printZone");
     Method main = Executor.class.getDeclaredMethod("main", String[].class);
-    testForR8(parameters.getBackend())
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
         .addProgramClasses(Executor.class, DesugaredLibUser.class)
-        .addLibraryFiles(getLibraryFile())
         .addKeepMainRule(Executor.class)
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
-        .apply(
-            ApiModelingTestHelper.addTracedApiReferenceLevelCallBack(
-                (reference, apiLevel) -> {
-                  if (reference.equals(Reference.methodFromMethod(printZone))) {
-                    assertEquals(parameters.getApiLevel(), apiLevel);
-                  }
-                }))
-        .compile()
-        .addDesugaredCoreLibraryRunClassPath(
-            this::buildDesugaredLibrary,
-            parameters.getApiLevel(),
-            keepRuleConsumer.get(),
-            shrinkDesugaredLibrary)
+        .applyOnBuilder(ApiModelingTestHelper::enableApiCallerIdentification)
+        .applyOnBuilder(
+            b ->
+                ApiModelingTestHelper.addTracedApiReferenceLevelCallBack(
+                        (reference, apiLevel) -> {
+                          if (reference.equals(Reference.methodFromMethod(printZone))) {
+                            assertEquals(parameters.getApiLevel(), apiLevel);
+                          }
+                        })
+                    .acceptWithRuntimeException(b))
         .run(parameters.getRuntime(), Executor.class)
         .assertSuccessWithOutputLines("Z")
         .inspect(
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoDesugaredLibraryReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoDesugaredLibraryReferenceTest.java
index 0457c25..c0cc0fc 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoDesugaredLibraryReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoDesugaredLibraryReferenceTest.java
@@ -5,13 +5,19 @@
 package com.android.tools.r8.apimodel;
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.R8_L8DEBUG;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.R8_L8SHRINK;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static org.junit.Assert.assertEquals;
 
 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.references.Reference;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
 import java.lang.reflect.Method;
 import java.nio.file.Paths;
 import java.util.List;
@@ -24,40 +30,45 @@
 public class ApiModelNoDesugaredLibraryReferenceTest extends DesugaredLibraryTestBase {
 
   private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
 
-  @Parameters(name = "{0}")
+  @Parameters(name = "{0}, spec: {1}, {2}")
   public static List<Object[]> data() {
-    return buildParameters(getTestParameters().withDexRuntimes().withAllApiLevels().build());
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        getJdk8Jdk11(),
+        ImmutableList.of(R8_L8DEBUG, R8_L8SHRINK));
   }
 
-  public ApiModelNoDesugaredLibraryReferenceTest(TestParameters parameters) {
+  public ApiModelNoDesugaredLibraryReferenceTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
     this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
   }
 
   @Test
-  public void testR8() throws Exception {
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+  public void test() throws Throwable {
     Method printZone = LibUser.class.getDeclaredMethod("printPath");
     Method main = Executor.class.getDeclaredMethod("main", String[].class);
-    testForR8(parameters.getBackend())
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
         .addProgramClasses(Executor.class, LibUser.class)
-        .addLibraryFiles(getLibraryFile())
         .addKeepMainRule(Executor.class)
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .applyOnBuilder(ApiModelingTestHelper::enableApiCallerIdentification)
         // We are testing that we do not inline/merge higher api-levels
-        .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
-        .apply(
-            ApiModelingTestHelper.addTracedApiReferenceLevelCallBack(
-                (reference, apiLevel) -> {
-                  if (reference.equals(Reference.methodFromMethod(printZone))) {
-                    assertEquals(AndroidApiLevel.O.max(parameters.getApiLevel()), apiLevel);
-                  }
-                }))
-        .compile()
-        .addDesugaredCoreLibraryRunClassPath(
-            this::buildDesugaredLibrary, parameters.getApiLevel(), keepRuleConsumer.get(), false)
+        .applyOnBuilder(ApiModelingTestHelper::disableOutliningAndStubbing)
+        .applyOnBuilder(
+            b ->
+                ApiModelingTestHelper.addTracedApiReferenceLevelCallBack(
+                        (reference, apiLevel) -> {
+                          if (reference.equals(Reference.methodFromMethod(printZone))) {
+                            assertEquals(AndroidApiLevel.O.max(parameters.getApiLevel()), apiLevel);
+                          }
+                        })
+                    .acceptWithRuntimeException(b))
         .run(parameters.getRuntime(), Executor.class)
         .applyIf(
             parameters.getDexRuntimeVersion().isDalvik(),
diff --git a/src/test/java/com/android/tools/r8/cf/FrameComparisonTest.java b/src/test/java/com/android/tools/r8/cf/FrameComparisonTest.java
index 5207198..095c7c4 100644
--- a/src/test/java/com/android/tools/r8/cf/FrameComparisonTest.java
+++ b/src/test/java/com/android/tools/r8/cf/FrameComparisonTest.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.graph.DexItemFactory;
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleDifferentNameArgumentPropagationTest.java b/src/test/java/com/android/tools/r8/cf/MethodHandleDifferentNameArgumentPropagationTest.java
new file mode 100644
index 0000000..5106ff8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleDifferentNameArgumentPropagationTest.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.util.function.Consumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/* This is a regression for b/231662249 */
+@RunWith(Parameterized.class)
+public class MethodHandleDifferentNameArgumentPropagationTest extends TestBase {
+
+  private static final String[] EXPECTED =
+      new String[] {"ImplementationInstanceMethod::forEachWithOtherName"};
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    testForJvm()
+        .addInnerClasses(MethodHandleDifferentNameArgumentPropagationTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(MethodHandleDifferentNameArgumentPropagationTest.class)
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.apiModelingOptions().disableApiCallerIdentification())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  public interface Foreachable {
+    void forEach(Consumer<String> consumer);
+  }
+
+  public static class ImplementationInstanceMethod {
+
+    public void forEachWithOtherName(Consumer<String> consumer) {
+      consumer.accept("ImplementationInstanceMethod::forEachWithOtherName");
+    }
+  }
+
+  public static class ImplementationInstanceMethodSub extends ImplementationInstanceMethod {}
+
+  public static class Main {
+
+    public static void test(Foreachable foreachable) {
+      foreachable.forEach(System.out::println);
+    }
+
+    public static void main(String[] args) {
+      ImplementationInstanceMethodSub impl = new ImplementationInstanceMethodSub();
+      test(impl::forEachWithOtherName);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/InvalidStackHeightTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/InvalidStackHeightTest.java
index 9fffa07..41c05a3 100644
--- a/src/test/java/com/android/tools/r8/cf/stackmap/InvalidStackHeightTest.java
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/InvalidStackHeightTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.cf.stackmap;
 
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -13,30 +12,29 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
 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 InvalidStackHeightTest extends TestBase {
 
-  private final String[] EXPECTED = new String[] {"42"};
+  private static final String[] EXPECTED = new String[] {"42"};
 
-  private final TestParameters parameters;
+  @Parameter(0)
+  public TestParameters parameters;
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
   }
 
-  public InvalidStackHeightTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
   @Test
   public void smokeTest() throws Exception {
     testForRuntime(parameters)
@@ -51,11 +49,7 @@
     testForD8(parameters.getBackend())
         .addProgramClassFileData(getMainWithChangedMaxStackHeight())
         .setMinApi(parameters.getApiLevel())
-        .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              diagnostics.assertWarningMessageThatMatches(
-                  containsString("The max stack height of 1 is violated"));
-            });
+        .compileWithExpectedDiagnostics(this::inspect);
   }
 
   @Test
@@ -64,16 +58,12 @@
     testForD8(parameters.getBackend())
         .addProgramClassFileData(getMainWithChangedMaxStackHeight())
         .setMinApi(parameters.getApiLevel())
-        .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              diagnostics.assertWarningMessageThatMatches(
-                  containsString("The max stack height of 1 is violated"));
-            })
+        .compileWithExpectedDiagnostics(this::inspect)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(EXPECTED);
   }
 
-  @Test()
+  @Test
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
         .addProgramClassFileData(getMainWithChangedMaxStackHeight())
@@ -81,15 +71,16 @@
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
         .allowDiagnosticWarningMessages()
-        .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              diagnostics.assertWarningsMatch(
-                  diagnosticMessage(containsString("The max stack height of 1 is violated")));
-            })
+        .compileWithExpectedDiagnostics(this::inspect)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(EXPECTED);
   }
 
+  private void inspect(TestDiagnosticMessages diagnostics) {
+    diagnostics.assertWarningMessageThatMatches(
+        containsString("The max stack height of 1 is violated"));
+  }
+
   @Test()
   public void testR8InputVerification() throws Exception {
     try {
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/StackMapVerificationNoFrameForHandlerTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/StackMapVerificationNoFrameForHandlerTest.java
index 7e8c85c..c3c79fb 100644
--- a/src/test/java/com/android/tools/r8/cf/stackmap/StackMapVerificationNoFrameForHandlerTest.java
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/StackMapVerificationNoFrameForHandlerTest.java
@@ -35,7 +35,7 @@
   private final String EXPECTED_JVM_ERROR =
       "java.lang.VerifyError: Expecting a stackmap frame at branch target";
 
-  @Parameters(name = "{0}")
+  @Parameters(name = "{0}, include frame in handler: {1}")
   public static List<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest.java
index 113a40f..00c3c60 100644
--- a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest.java
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest.java
@@ -11,11 +11,13 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
 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;
 import org.objectweb.asm.ClassWriter;
 import org.objectweb.asm.FieldVisitor;
@@ -25,18 +27,14 @@
 @RunWith(Parameterized.class)
 public class UninitializedGetFieldTest extends TestBase {
 
-  private final String[] EXPECTED = new String[] {"Main::foo"};
-  private final TestParameters parameters;
+  @Parameter(0)
+  public TestParameters parameters;
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
   }
 
-  public UninitializedGetFieldTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
   @Test
   public void testJvm() throws Exception {
     assumeTrue(parameters.isCfRuntime());
@@ -52,11 +50,7 @@
     testForD8(parameters.getBackend())
         .addProgramClassFileData(dump())
         .setMinApi(parameters.getApiLevel())
-        .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              diagnostics.assertWarningThatMatches(
-                  diagnosticMessage(containsString("The expected type uninitialized")));
-            });
+        .compileWithExpectedDiagnostics(this::inspect);
   }
 
   @Test
@@ -65,15 +59,20 @@
     testForD8(parameters.getBackend())
         .addProgramClassFileData(dump())
         .setMinApi(parameters.getApiLevel())
-        .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              diagnostics.assertWarningThatMatches(
-                  diagnosticMessage(containsString("The expected type uninitialized")));
-            })
+        .compileWithExpectedDiagnostics(this::inspect)
         .run(parameters.getRuntime(), Main.class)
         .assertFailureWithErrorThatThrows(VerifyError.class);
   }
 
+  private void inspect(TestDiagnosticMessages diagnostics) {
+    diagnostics.assertWarningThatMatches(
+        diagnosticMessage(
+            containsString(
+                "Expected initialized "
+                    + Main.class.getTypeName()
+                    + " on stack, but was uninitialized-this")));
+  }
+
   public static class Main {
 
     private Object object;
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java
index 492e21e..9e5411e 100644
--- a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRunResult;
@@ -17,6 +18,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 import org.objectweb.asm.ClassWriter;
 import org.objectweb.asm.MethodVisitor;
@@ -25,7 +27,8 @@
 @RunWith(Parameterized.class)
 public class UninitializedInstanceOfTest extends TestBase {
 
-  private final TestParameters parameters;
+  @Parameter(0)
+  public TestParameters parameters;
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -47,13 +50,7 @@
     testForD8(parameters.getBackend())
         .addProgramClassFileData(dump())
         .setMinApi(parameters.getApiLevel())
-        .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              diagnostics.assertWarningMessageThatMatches(
-                  containsString("The expected type uninitialized new is not assignable"));
-              diagnostics.assertErrorMessageThatMatches(
-                  containsString("Could not validate stack map frames"));
-            });
+        .compileWithExpectedDiagnostics(this::inspect);
   }
 
   @Test()
@@ -63,11 +60,7 @@
     testForD8(parameters.getBackend())
         .addProgramClassFileData(dump())
         .setMinApi(parameters.getApiLevel())
-        .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              diagnostics.assertWarningMessageThatMatches(
-                  containsString("The expected type uninitialized new is not assignable"));
-            })
+        .compileWithExpectedDiagnostics(this::inspect)
         .run(parameters.getRuntime(), Main.class)
         .applyIf(
             expectFailure,
@@ -75,8 +68,15 @@
             TestRunResult::assertSuccessWithOutputLines);
   }
 
-  public UninitializedInstanceOfTest(TestParameters parameters) {
-    this.parameters = parameters;
+  private void inspect(TestDiagnosticMessages diagnostics) {
+    diagnostics.assertWarningMessageThatMatches(
+        containsString(
+            "Expected initialized java.lang.Object on stack, but was uninitialized"
+                + " java.lang.Object"));
+    if (parameters.isCfRuntime()) {
+      diagnostics.assertErrorMessageThatMatches(
+          containsString("Could not validate stack map frames"));
+    }
   }
 
   public static class Main {
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest.java
index 86429af..669829b 100644
--- a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest.java
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest.java
@@ -10,11 +10,13 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
 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;
 import org.objectweb.asm.ClassWriter;
 import org.objectweb.asm.MethodVisitor;
@@ -23,7 +25,8 @@
 @RunWith(Parameterized.class)
 public class UninitializedNewCheckCastTest extends TestBase {
 
-  private final TestParameters parameters;
+  @Parameter(0)
+  public TestParameters parameters;
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -45,13 +48,7 @@
     testForD8(parameters.getBackend())
         .addProgramClassFileData(dump())
         .setMinApi(parameters.getApiLevel())
-        .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              diagnostics.assertWarningMessageThatMatches(
-                  containsString("The expected type uninitialized new is not assignable"));
-              diagnostics.assertErrorMessageThatMatches(
-                  containsString("Could not validate stack map frames"));
-            });
+        .compileWithExpectedDiagnostics(this::inspect);
   }
 
   @Test
@@ -60,17 +57,20 @@
     testForD8(parameters.getBackend())
         .addProgramClassFileData(dump())
         .setMinApi(parameters.getApiLevel())
-        .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              diagnostics.assertWarningMessageThatMatches(
-                  containsString("The expected type uninitialized new is not assignable"));
-            })
+        .compileWithExpectedDiagnostics(this::inspect)
         .run(parameters.getRuntime(), Main.class)
         .assertFailureWithErrorThatThrows(VerifyError.class);
   }
 
-  public UninitializedNewCheckCastTest(TestParameters parameters) {
-    this.parameters = parameters;
+  private void inspect(TestDiagnosticMessages diagnostics) {
+    diagnostics.assertWarningMessageThatMatches(
+        containsString(
+            "Expected initialized java.lang.Object on stack, but was uninitialized"
+                + " java.lang.Object"));
+    if (parameters.isCfRuntime()) {
+      diagnostics.assertErrorMessageThatMatches(
+          containsString("Could not validate stack map frames"));
+    }
   }
 
   public static class Main {
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest.java
index 9a52445..e4e918b 100644
--- a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest.java
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
@@ -47,13 +48,7 @@
     testForD8(parameters.getBackend())
         .addProgramClassFileData(dump())
         .setMinApi(parameters.getApiLevel())
-        .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              diagnostics.assertWarningMessageThatMatches(
-                  containsString("The expected type uninitialized this is not assignable"));
-              diagnostics.assertErrorMessageThatMatches(
-                  containsString("Could not validate stack map frames"));
-            });
+        .compileWithExpectedDiagnostics(this::inspect);
   }
 
   @Test
@@ -65,16 +60,24 @@
     testForD8(parameters.getBackend())
         .addProgramClassFileData(dump())
         .setMinApi(parameters.getApiLevel())
-        .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              diagnostics.assertWarningMessageThatMatches(
-                  containsString("The expected type uninitialized this is not assignable"));
-            })
+        .compileWithExpectedDiagnostics(this::inspect)
         .run(parameters.getRuntime(), Main.class)
         .assertFailureWithErrorThatThrowsIf(willFailVerification, VerifyError.class)
         .assertSuccessWithOutputLinesIf(!willFailVerification, "Main::foo");
   }
 
+  private void inspect(TestDiagnosticMessages diagnostics) {
+    diagnostics.assertWarningMessageThatMatches(
+        containsString(
+            "Expected initialized "
+                + Main.class.getTypeName()
+                + " on stack, but was uninitialized-this"));
+    if (parameters.isCfRuntime()) {
+      diagnostics.assertErrorMessageThatMatches(
+          containsString("Could not validate stack map frames"));
+    }
+  }
+
   public UninitializedPutFieldSelfTest(TestParameters parameters) {
     this.parameters = parameters;
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingInD8WithCompanionClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingInD8WithCompanionClassesTest.java
new file mode 100644
index 0000000..d143c60
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingInD8WithCompanionClassesTest.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.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * This is a regression test for b/227791663.
+ *
+ * <p>Non-sharable synthetics should not be deduplicated or merged in D8. Examples are the global
+ * synthetics such as RecordTag and API type stubs, but also fixed-suffix synthetics, such as
+ * companion classes for interfaces, which rely on a specific suffix in cases of separate
+ * compilation.
+ */
+@RunWith(Parameterized.class)
+public class HorizontalClassMergingInD8WithCompanionClassesTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsEndingAtExcluding(apiLevelWithDefaultInterfaceMethodsSupport())
+        .build();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMode(CompilationMode.RELEASE)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(
+            options -> {
+              options.horizontalClassMergerOptions().enable();
+              options.horizontalClassMergerOptions().setRestrictToSynthetics();
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("I::foo", "J::bar")
+        .inspect(
+            inspector -> {
+              ClassSubject companionI = inspector.clazz(I.class).toCompanionClass();
+              ClassSubject companionJ = inspector.clazz(J.class).toCompanionClass();
+              assertThat(companionI, isPresent());
+              assertThat(companionJ, isPresent());
+              assertNotEquals(companionI.getFinalName(), companionJ.getFinalName());
+            });
+  }
+
+  public interface I {
+    default void foo() {
+      System.out.println("I::foo");
+    }
+  }
+
+  public interface J {
+    default void bar() {
+      System.out.println("J::bar");
+    }
+  }
+
+  public static class A implements I {}
+
+  public static class B implements J {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new A().foo();
+      new B().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
index 33bd1a2..1730dc8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
@@ -4,17 +4,20 @@
 
 package com.android.tools.r8.classmerging.vertical;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.R8_L8DEBUG;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 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.google.common.collect.ImmutableList;
 import java.util.Arrays;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -24,29 +27,34 @@
 public class ForceInlineConstructorWithRetargetedLibMemberTest extends DesugaredLibraryTestBase {
 
   private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return TestBase.getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        getJdk8Jdk11(),
+        ImmutableList.of(R8_L8DEBUG));
   }
 
-  public ForceInlineConstructorWithRetargetedLibMemberTest(TestParameters parameters) {
+  public ForceInlineConstructorWithRetargetedLibMemberTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
     this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
   }
 
   @Test
-  public void test() throws Exception {
-    // Regression test for b/170677722.
-    testForR8(parameters.getBackend())
-        .addLibraryFiles(getLibraryFile())
+  public void test() throws Throwable {
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
+        .enableNeverClassInliningAnnotations()
         .addVerticallyMergedClassesInspector(
             inspector -> inspector.assertMergedIntoSubtype(A.class))
-        .enableCoreLibraryDesugaring(
-            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
-        .enableNeverClassInliningAnnotations()
-        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
             inspector -> {
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithMissingTypeArgsSubstitutionTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithMissingTypeArgsSubstitutionTest.java
index 4fad79d..d4d1355 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithMissingTypeArgsSubstitutionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithMissingTypeArgsSubstitutionTest.java
@@ -59,17 +59,21 @@
               MethodSubject bar = classSubject.uniqueMethodWithName("bar");
               assertThat(bar, isPresentAndRenamed());
               assertEquals("(TT;)V", bar.getFinalSignatureAttribute());
-              // The NeverInline is transferred to the private vertically merged method, making
-              // it hard to lookup.
-              MethodSubject movedFooSubject =
+              // The NeverInline is transferred to the private vertically merged method but also
+              // copied to the virtual bridge.
+              MethodSubject fooMovedFromB =
                   classSubject.uniqueMethodThatMatches(
-                      method ->
-                          method.getMethod().getReference() != bar.getMethod().getReference()
-                              && !method.isInstanceInitializer());
-              assertThat(movedFooSubject, isPresentAndRenamed());
+                      method -> !method.isVirtual() && method.getOriginalName(false).equals("foo"));
+              assertThat(fooMovedFromB, isPresentAndRenamed());
               assertEquals(
                   "(Ljava/lang/Object;)Ljava/lang/Object;",
-                  movedFooSubject.getFinalSignatureAttribute());
+                  fooMovedFromB.getFinalSignatureAttribute());
+              MethodSubject fooBridge =
+                  classSubject.uniqueMethodThatMatches(
+                      method -> method.isVirtual() && method.getOriginalName(false).equals("foo"));
+              assertThat(fooBridge, isPresentAndRenamed());
+              assertEquals(
+                  "(Ljava/lang/Object;)Ljava/lang/Object;", fooBridge.getFinalSignatureAttribute());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java
index c3a7cfc..c431ea1 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java
@@ -62,7 +62,10 @@
               // Assert that the merged class has no annotations from Base
               assertTrue(sub.getDexProgramClass().annotations().isEmpty());
               // Assert that foo has the private annotation from the Base.foo
-              MethodSubject foo = sub.uniqueMethodWithName("foo");
+              MethodSubject foo =
+                  sub.uniqueMethodThatMatches(
+                      method ->
+                          method.getOriginalName(false).equals("foo") && !method.isSynthetic());
               assertThat(foo, isPresent());
               AnnotationSubject privateMethodAnnotation =
                   foo.annotation(
diff --git a/src/test/java/com/android/tools/r8/desugar/InvokeSuperToEmulatedDefaultMethodTest.java b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToEmulatedDefaultMethodTest.java
index 8d914a8..2dd34a5 100644
--- a/src/test/java/com/android/tools/r8/desugar/InvokeSuperToEmulatedDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToEmulatedDefaultMethodTest.java
@@ -3,37 +3,52 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
-import com.android.tools.r8.utils.AndroidApiLevel;
+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.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 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 InvokeSuperToEmulatedDefaultMethodTest extends DesugaredLibraryTestBase {
 
   private static final String EXPECTED = StringUtils.lines("bar", "bar");
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  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(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        getJdk8Jdk11(),
+        ImmutableList.of(D8_L8DEBUG));
   }
 
-  private final TestParameters parameters;
-
-  public InvokeSuperToEmulatedDefaultMethodTest(TestParameters parameters) {
+  public InvokeSuperToEmulatedDefaultMethodTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
     this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
   }
 
   private boolean needsDefaultInterfaceMethodDesugaring() {
@@ -43,6 +58,7 @@
 
   @Test
   public void testReference() throws Exception {
+    assumeTrue("Run only once", libraryDesugaringSpecification == JDK11);
     assumeFalse(needsDefaultInterfaceMethodDesugaring());
     testForRuntime(parameters)
         .addInnerClasses(InvokeSuperToEmulatedDefaultMethodTest.class)
@@ -51,15 +67,10 @@
   }
 
   @Test
-  public void testDesugaring() throws Exception {
+  public void testDesugaring() throws Throwable {
     assumeTrue(needsDefaultInterfaceMethodDesugaring());
-
-    testForD8()
-        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
-        .addInnerClasses(InvokeSuperToEmulatedDefaultMethodTest.class)
-        .setMinApi(parameters.getApiLevel())
-        .enableLibraryDesugaring(parameters.getApiLevel())
-        .compile()
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED);
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java
index 342f9a4..eb211e6 100644
--- a/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java
@@ -3,35 +3,50 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.LibraryDesugaringTestConfiguration;
-import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.IntConsumer;
 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 InvokeSuperToRewrittenDefaultMethodTest extends DesugaredLibraryTestBase {
 
   private static final String EXPECTED = StringUtils.lines("Y", "89");
 
-  @Parameterized.Parameters(name = "{0}")
+  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(getTestParameters().withAllRuntimesAndApiLevels().build());
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        getJdk8Jdk11(),
+        ImmutableList.of(D8_L8DEBUG));
   }
 
-  private final TestParameters parameters;
-
-  public InvokeSuperToRewrittenDefaultMethodTest(TestParameters parameters) {
+  public InvokeSuperToRewrittenDefaultMethodTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
     this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
   }
 
   private boolean needsDefaultInterfaceMethodDesugaring() {
@@ -41,6 +56,7 @@
 
   @Test
   public void testReference() throws Exception {
+    assumeFalse("Run only once.", libraryDesugaringSpecification == JDK11);
     assumeFalse(needsDefaultInterfaceMethodDesugaring());
     testForRuntime(parameters)
         .addInnerClasses(InvokeSuperToRewrittenDefaultMethodTest.class)
@@ -49,19 +65,15 @@
   }
 
   @Test
-  public void testDesugaring() throws Exception {
+  public void testDesugaring() throws Throwable {
     assumeTrue(needsDefaultInterfaceMethodDesugaring());
-    testForD8()
-        .addInnerClasses(InvokeSuperToRewrittenDefaultMethodTest.class)
-        .setMinApi(parameters.getApiLevel())
-        .addLibraryFiles(getLibraryFile())
-        .enableCoreLibraryDesugaring(
-            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
-        .compileWithExpectedDiagnostics(TestDiagnosticMessages::assertNoMessages)
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED);
   }
 
+
   public interface CharConsumer extends Consumer<Character>, IntConsumer {
 
     void accept(char c);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BackwardsCompatibleSpecificationTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BackwardsCompatibleSpecificationTest.java
index 817268d..9fe7609 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BackwardsCompatibleSpecificationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BackwardsCompatibleSpecificationTest.java
@@ -3,16 +3,18 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.ArrayList;
 import java.util.List;
-import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -24,15 +26,19 @@
 
   @Parameterized.Parameters(name = "{1}")
   public static List<Object[]> data() {
-    return buildParameters(getTestParameters().withNoneRuntime().build(), RELEASES);
+    return buildParameters(
+        getTestParameters().withNoneRuntime().build(), ImmutableList.of(JDK8), RELEASES);
   }
 
-  private final Path desugaredLib = ToolHelper.getDesugarJDKLibs();
-  private final Path desugaredSpec = ToolHelper.getDesugarLibJsonForTesting();
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
   private final String release;
 
-  public BackwardsCompatibleSpecificationTest(TestParameters parameters, String release) {
+  public BackwardsCompatibleSpecificationTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      String release) {
     parameters.assertNoneRuntime();
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
     this.release = release;
   }
 
@@ -42,16 +48,14 @@
 
   @Test
   public void test() throws Exception {
-    Assume.assumeFalse(
-        "When using JDK11 desugared library, we're not backward compatible to 2.0.74.",
-        isJDK11DesugaredLibrary());
-    ProcessResult result =
-        ToolHelper.runJava(
-            getReleaseJar(),
-            "com.android.tools.r8.L8",
-            "--desugared-lib",
-            desugaredSpec.toString(),
-            desugaredLib.toString());
+    ArrayList<String> command = new ArrayList<>();
+    command.add("com.android.tools.r8.L8");
+    command.add("--desugared-lib");
+    command.add(libraryDesugaringSpecification.getSpecification().toString());
+    for (Path desugarJdkLib : libraryDesugaringSpecification.getDesugarJdkLibs()) {
+      command.add(desugarJdkLib.toString());
+    }
+    ProcessResult result = ToolHelper.runJava(getReleaseJar(), command.toArray(new String[0]));
     assertEquals(result.toString(), 0, result.exitCode);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
index 38845cc..716e073 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
@@ -9,17 +9,12 @@
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static junit.framework.TestCase.assertTrue;
 import static org.hamcrest.CoreMatchers.startsWith;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 
-import com.android.tools.r8.D8TestBuilder;
-import com.android.tools.r8.LibraryDesugaringTestConfiguration;
-import com.android.tools.r8.StringResource;
-import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
@@ -65,41 +60,10 @@
   }
 
   @Test
-  public void testInvalidLibrary() {
-    Assume.assumeTrue(requiresAnyCoreLibDesugaring(parameters));
-    // This is handwritten since this is really a special case: library desugaring with an
-    // invalid library file passed.
-    D8TestBuilder testBuilder =
-        testForD8()
-            .setMinApi(parameters.getApiLevel())
-            .addProgramClasses(GuineaPig.class)
-            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.L))
-            .enableCoreLibraryDesugaring(
-                LibraryDesugaringTestConfiguration.builder()
-                    .setMinApi(parameters.getApiLevel())
-                    .addDesugaredLibraryConfiguration(
-                        StringResource.fromFile(libraryDesugaringSpecification.getSpecification()))
-                    .dontAddRunClasspath()
-                    .build());
-    try {
-      testBuilder.compile();
-    } catch (Throwable t) {
-      // Expected since we are compiling with an invalid set-up.
-    }
-    TestDiagnosticMessages diagnosticMessages = testBuilder.getState().getDiagnosticsMessages();
-    assertTrue(
-        diagnosticMessages
-            .getWarnings()
-            .get(0)
-            .getDiagnosticMessage()
-            .contains(
-                "Desugared library requires to be compiled with a library file of API greater or"
-                    + " equal to"));
-  }
-
-  @Test
   public void testDesugaredLibraryContent() throws Exception {
-    Assume.assumeTrue(requiresAnyCoreLibDesugaring(parameters));
+    Assume.assumeTrue(
+        requiresAnyCoreLibDesugaring(
+            parameters.getApiLevel(), libraryDesugaringSpecification != JDK8));
     testForL8(parameters.getApiLevel())
         .apply(libraryDesugaringSpecification::configureL8TestBuilder)
         .compile()
@@ -108,7 +72,9 @@
 
   @Test
   public void testDesugaredLibraryContentWithCoreLambdaStubsAsProgram() throws Exception {
-    Assume.assumeTrue(requiresAnyCoreLibDesugaring(parameters));
+    Assume.assumeTrue(
+        requiresAnyCoreLibDesugaring(
+            parameters.getApiLevel(), libraryDesugaringSpecification != JDK8));
     ArrayList<Path> coreLambdaStubs = new ArrayList<>();
     coreLambdaStubs.add(ToolHelper.getCoreLambdaStubs());
     testForL8(parameters.getApiLevel())
@@ -120,7 +86,9 @@
 
   @Test
   public void testDesugaredLibraryContentWithCoreLambdaStubsAsLibrary() throws Exception {
-    Assume.assumeTrue(requiresAnyCoreLibDesugaring(parameters));
+    Assume.assumeTrue(
+        requiresAnyCoreLibDesugaring(
+            parameters.getApiLevel(), libraryDesugaringSpecification != JDK8));
     testForL8(parameters.getApiLevel())
         .apply(libraryDesugaringSpecification::configureL8TestBuilder)
         .addLibraryFiles(ToolHelper.getCoreLambdaStubs())
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDumpInputsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDumpInputsTest.java
index 46743c8..4a9b0a0 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDumpInputsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDumpInputsTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.desugar.desugaredlibrary;
 
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
@@ -53,7 +54,9 @@
 
   @Test
   public void testDumpToDirectory() throws Exception {
-    Assume.assumeTrue(requiresAnyCoreLibDesugaring(parameters));
+    Assume.assumeTrue(
+        requiresAnyCoreLibDesugaring(
+            parameters.getApiLevel(), libraryDesugaringSpecification != JDK8));
     Path dumpDir = temp.newFolder().toPath();
     testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
         .addProgramClasses(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvalidTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvalidTest.java
new file mode 100644
index 0000000..1834ea4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvalidTest.java
@@ -0,0 +1,96 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import static com.android.tools.r8.ToolHelper.DESUGARED_JDK_8_LIB_JAR;
+import static com.android.tools.r8.ToolHelper.UNDESUGARED_JDK_11_LIB_JAR;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.DesugaredLibraryTestBuilder;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DesugaredLibraryInvalidTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final CompilationSpecification compilationSpecification;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    LibraryDesugaringSpecification jdk8InvalidLib =
+        new LibraryDesugaringSpecification(
+            "JDK8_INVALID_LIB",
+            DESUGARED_JDK_8_LIB_JAR,
+            "desugar_jdk_libs.json",
+            AndroidApiLevel.L);
+    LibraryDesugaringSpecification jdk11InvalidLib =
+        new LibraryDesugaringSpecification(
+            "JDK11_INVALID_LIB",
+            UNDESUGARED_JDK_11_LIB_JAR,
+            "jdk11/desugar_jdk_libs.json",
+            AndroidApiLevel.L);
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+        ImmutableList.of(jdk8InvalidLib, jdk11InvalidLib),
+        ImmutableList.of(D8_L8DEBUG));
+  }
+
+  public DesugaredLibraryInvalidTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
+    this.parameters = parameters;
+    this.compilationSpecification = compilationSpecification;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+  }
+
+  @Test
+  public void testInvalidLibrary() throws IOException {
+    Assume.assumeTrue(
+        requiresAnyCoreLibDesugaring(
+            parameters.getApiLevel(), !libraryDesugaringSpecification.toString().contains("JDK8")));
+    DesugaredLibraryTestBuilder<?> testBuilder =
+        testForDesugaredLibrary(
+                parameters, libraryDesugaringSpecification, compilationSpecification)
+            .addProgramClasses(GuineaPig.class);
+    try {
+      testBuilder.compile();
+    } catch (Throwable t) {
+      // Expected since we are compiling with an invalid set-up.
+    }
+    testBuilder.applyOnBuilder(
+        b -> {
+          TestDiagnosticMessages diagnosticsMessages = b.getState().getDiagnosticsMessages();
+          assertFalse(diagnosticsMessages.getWarnings().isEmpty());
+          assertTrue(
+              diagnosticsMessages
+                  .getWarnings()
+                  .get(0)
+                  .getDiagnosticMessage()
+                  .contains(
+                      "Desugared library requires to be compiled with a library file of API greater"
+                          + " or equal to"));
+        });
+  }
+
+  static class GuineaPig {
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index 6024ccb..b833d04 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -4,19 +4,11 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
-import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
 import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertTrue;
 
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.L8Command;
 import com.android.tools.r8.L8TestBuilder;
-import com.android.tools.r8.LibraryDesugaringTestConfiguration;
-import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.StringConsumer;
-import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -27,28 +19,11 @@
 import com.android.tools.r8.desugar.desugaredlibrary.test.DesugaredLibraryTestBuilder;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
-import com.android.tools.r8.tracereferences.TraceReferences;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.base.Charsets;
-import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-import java.util.function.Consumer;
 import org.junit.BeforeClass;
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.MethodVisitor;
 
 public class DesugaredLibraryTestBase extends TestBase {
 
@@ -70,11 +45,6 @@
     return property.contains("jdk11");
   }
 
-  public void setDesugaredLibrarySpecificationForTesting(
-      InternalOptions options, DesugaredLibrarySpecification specification) {
-    options.setDesugaredLibrarySpecification(specification);
-  }
-
   // For conversions tests, we need DexRuntimes where classes to convert are present (DexRuntimes
   // above N and O depending if Stream or Time APIs are used), but we need to compile the program
   // with a minAPI below to force the use of conversions.
@@ -122,7 +92,7 @@
 
   protected boolean requiresAnyCoreLibDesugaring(AndroidApiLevel apiLevel, boolean isJDK11) {
     return apiLevel.getLevel()
-        <= (isJDK11 ? AndroidApiLevel.Sv2.getLevel() : AndroidApiLevel.N_MR1.getLevel());
+        <= (isJDK11 ? AndroidApiLevel.R.getLevel() : AndroidApiLevel.N_MR1.getLevel());
   }
 
   protected boolean requiresAnyCoreLibDesugaring(AndroidApiLevel apiLevel) {
@@ -145,79 +115,6 @@
     return L8TestBuilder.create(apiLevel, backend, new TestState(temp));
   }
 
-  protected Path buildDesugaredLibrary(AndroidApiLevel apiLevel) {
-    return buildDesugaredLibrary(apiLevel, null, false);
-  }
-
-  protected Path buildDesugaredLibrary(
-      AndroidApiLevel apiLevel, Consumer<InternalOptions> optionsModifier) {
-    return buildDesugaredLibrary(apiLevel, null, false, ImmutableList.of(), optionsModifier);
-  }
-
-  protected Path buildDesugaredLibrary(AndroidApiLevel apiLevel, String keepRules) {
-    return buildDesugaredLibrary(apiLevel, keepRules, true);
-  }
-
-  protected Path buildDesugaredLibrary(AndroidApiLevel apiLevel, String keepRules, boolean shrink) {
-    return buildDesugaredLibrary(apiLevel, keepRules, shrink, ImmutableList.of(), options -> {});
-  }
-
-  protected Path buildDesugaredLibrary(
-      AndroidApiLevel apiLevel,
-      String keepRules,
-      boolean shrink,
-      List<Path> additionalProgramFiles) {
-    return buildDesugaredLibrary(
-        apiLevel, keepRules, shrink, additionalProgramFiles, options -> {});
-  }
-
-  protected Path buildDesugaredLibrary(
-      AndroidApiLevel apiLevel,
-      String generatedKeepRules,
-      boolean release,
-      List<Path> additionalProgramFiles,
-      Consumer<InternalOptions> optionsModifier) {
-    try {
-      return testForL8(apiLevel)
-          .addProgramFiles(additionalProgramFiles)
-          .addLibraryFiles(getLibraryFile())
-          .applyIf(
-              release,
-              builder -> {
-                if (generatedKeepRules != null && !generatedKeepRules.trim().isEmpty()) {
-                  builder.addGeneratedKeepRules(generatedKeepRules);
-                }
-              },
-              L8TestBuilder::setDebug)
-          .addOptionsModifier(optionsModifier)
-          .setDesugarJDKLibsCustomConversions(ToolHelper.DESUGAR_LIB_CONVERSIONS)
-          // If we compile extended library here, it means we use TestNG. TestNG requires
-          // annotations, hence we disable annotation removal. This implies that extra warnings are
-          // generated.
-          .setDisableL8AnnotationRemoval(!additionalProgramFiles.isEmpty())
-          .compile()
-          .applyIf(
-              additionalProgramFiles.isEmpty(),
-              builder ->
-                  builder.inspectDiagnosticMessages(
-                      diagnostics ->
-                          assertTrue(
-                              // TODO(b/193597730): Fix JDK11 desugared library message,
-                              isJDK11DesugaredLibrary()
-                                  || diagnostics.getInfos().stream()
-                                      .noneMatch(
-                                          string ->
-                                              string
-                                                  .getDiagnosticMessage()
-                                                  .startsWith(
-                                                      "Invalid parameter counts in MethodParameter"
-                                                          + " attributes.")))))
-          .writeToZip();
-    } catch (CompilationFailedException | ExecutionException | IOException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
   protected void assertLines2By2Correct(String stdOut) {
     String[] lines = stdOut.split("\n");
     assert lines.length % 2 == 0;
@@ -236,128 +133,6 @@
         .toArray(Path[]::new);
   }
 
-  protected KeepRuleConsumer createKeepRuleConsumer(TestParameters parameters) {
-    return LibraryDesugaringTestConfiguration.createKeepRuleConsumer(parameters);
-  }
-
-  public Path getDesugaredLibraryInCF(
-      TestParameters parameters, Consumer<InternalOptions> configurationForLibraryCompilation)
-      throws IOException, CompilationFailedException {
-    Path desugaredLib = temp.newFolder().toPath().resolve("desugar_jdk_libs.jar");
-    L8Command.Builder l8Builder =
-        L8Command.builder()
-            .addLibraryFiles(getLibraryFile())
-            .addProgramFiles(ToolHelper.getDesugarJDKLibs())
-            .addProgramFiles(ToolHelper.DESUGAR_LIB_CONVERSIONS)
-            .setMode(CompilationMode.DEBUG)
-            .addDesugaredLibraryConfiguration(
-                StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()))
-            .setMinApiLevel(parameters.getApiLevel().getLevel())
-            .setOutput(desugaredLib, OutputMode.ClassFile);
-
-    ToolHelper.runL8(l8Builder.build(), configurationForLibraryCompilation);
-    return desugaredLib;
-  }
-
-  protected DesugaredLibrarySpecification configurationWithSupportAllCallbacksFromLibrary(
-      InternalOptions options,
-      boolean libraryCompilation,
-      TestParameters parameters,
-      boolean supportAllCallbacksFromLibrary) {
-    return DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecificationforTesting(
-        StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()),
-        options.dexItemFactory(),
-        options.reporter,
-        libraryCompilation,
-        parameters.getApiLevel().getLevel(),
-        builder -> builder.setSupportAllCallbacksFromLibrary(supportAllCallbacksFromLibrary));
-  }
-
-  private Map<AndroidApiLevel, Path> desugaredLibraryClassFileCache = new HashMap<>();
-
-  // Build the desugared library in class file format.
-  public Path buildDesugaredLibraryClassFile(AndroidApiLevel apiLevel) throws Exception {
-    Path desugaredLib = desugaredLibraryClassFileCache.get(apiLevel);
-    if (desugaredLib != null) {
-      return desugaredLib;
-    }
-    desugaredLib = temp.newFolder().toPath().resolve("desugar_jdk_libs.jar");
-    L8Command.Builder l8Builder =
-        L8Command.builder()
-            .addLibraryFiles(getLibraryFile())
-            .addProgramFiles(ToolHelper.getDesugarJDKLibs())
-            .addProgramFiles(ToolHelper.DESUGAR_LIB_CONVERSIONS)
-            .setMode(CompilationMode.DEBUG)
-            .addDesugaredLibraryConfiguration(
-                StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()))
-            .setMinApiLevel(apiLevel.getLevel())
-            .setOutput(desugaredLib, OutputMode.ClassFile);
-    ToolHelper.runL8(l8Builder.build());
-    desugaredLibraryClassFileCache.put(apiLevel, desugaredLib);
-    return desugaredLib;
-  }
-
-  public String collectKeepRulesWithTraceReferences(
-      Path desugaredProgramClassFile, Path desugaredLibraryClassFile) throws Exception {
-    Path generatedKeepRules = temp.newFile().toPath();
-    TraceReferences.run(
-        "--keep-rules",
-        "--lib",
-        getLibraryFile().toString(),
-        "--target",
-        desugaredLibraryClassFile.toString(),
-        "--source",
-        desugaredProgramClassFile.toString(),
-        "--output",
-        generatedKeepRules.toString(),
-        "--map-diagnostics",
-        "error",
-        "info");
-    return FileUtils.readTextFile(generatedKeepRules, Charsets.UTF_8);
-  }
-
-  protected static ClassFileInfo extractClassFileInfo(byte[] classFileBytes) {
-    class ClassFileInfoExtractor extends ClassVisitor {
-      private String classBinaryName;
-      private List<String> interfaces = new ArrayList<>();
-      private final List<String> methodNames = new ArrayList<>();
-
-      private ClassFileInfoExtractor() {
-        super(ASM_VERSION);
-      }
-
-      @Override
-      public void visit(
-          int version,
-          int access,
-          String name,
-          String signature,
-          String superName,
-          String[] interfaces) {
-        classBinaryName = name;
-        this.interfaces.addAll(Arrays.asList(interfaces));
-        super.visit(version, access, name, signature, superName, interfaces);
-      }
-
-      @Override
-      public MethodVisitor visitMethod(
-          int access, String name, String desc, String signature, String[] exceptions) {
-        methodNames.add(name);
-        return super.visitMethod(access, name, desc, signature, exceptions);
-      }
-
-      ClassFileInfo getClassFileInfo() {
-        return new ClassFileInfo(classBinaryName, interfaces, methodNames);
-      }
-    }
-
-    ClassReader reader = new ClassReader(classFileBytes);
-    ClassFileInfoExtractor extractor = new ClassFileInfoExtractor();
-    reader.accept(
-        extractor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
-    return extractor.getClassFileInfo();
-  }
-
   public interface KeepRuleConsumer extends StringConsumer {
 
     String get();
@@ -377,28 +152,4 @@
       };
     }
   }
-
-  protected static class ClassFileInfo {
-    private final String classBinaryName;
-    private List<String> interfaces;
-    private final List<String> methodNames;
-
-    ClassFileInfo(String classBinaryNamename, List<String> interfaces, List<String> methodNames) {
-      this.classBinaryName = classBinaryNamename;
-      this.interfaces = interfaces;
-      this.methodNames = methodNames;
-    }
-
-    public String getClassBinaryName() {
-      return classBinaryName;
-    }
-
-    public List<String> getInterfaces() {
-      return interfaces;
-    }
-
-    public List<String> getMethodNames() {
-      return methodNames;
-    }
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmptyDesugaredLibrary.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmptyDesugaredLibrary.java
index 2e09671..f744809 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmptyDesugaredLibrary.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmptyDesugaredLibrary.java
@@ -75,10 +75,7 @@
   }
 
   private boolean expectsEmptyDesugaredLibrary(AndroidApiLevel apiLevel) {
-    if (libraryDesugaringSpecification != JDK8) {
-      return apiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.S);
-    }
-    return !requiresAnyCoreLibDesugaring(apiLevel);
+    return !requiresAnyCoreLibDesugaring(apiLevel, libraryDesugaringSpecification != JDK8);
   }
 
   @Test
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 f0be12b..b6be183 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
@@ -98,7 +98,7 @@
 
   private final LibraryDesugaringSpecification libraryDesugaringSpecification;
 
-  @Parameters(name = "{0}, spec: {1}, {2}")
+  @Parameters(name = "{0}, spec: {1}")
   public static List<Object[]> data() {
     return buildParameters(getTestParameters().withNoneRuntime().build(), getJdk8Jdk11());
   }
@@ -147,7 +147,9 @@
             minApi.getLevel());
     MachineDesugaredLibrarySpecification specification =
         spec.toMachineSpecification(
-            new InternalOptions(factory, new Reporter()), getLibraryFile(), Timing.empty());
+            new InternalOptions(factory, new Reporter()),
+            libraryDesugaringSpecification.getLibraryFiles(),
+            Timing.empty());
     Set<String> wrappersInSpec =
         specification.getWrappers().keySet().stream()
             .map(DexType::toString)
@@ -288,10 +290,10 @@
   private CodeInspector getDesugaredApiJar() throws Exception {
     Path out = temp.newFolder().toPath();
     GenerateLintFiles desugaredApi =
-        new GenerateLintFiles(
-            ToolHelper.getDesugarLibJsonForTesting().toString(),
-            ToolHelper.getDesugarJDKLibs().toString(),
-            out.toString());
+        GenerateLintFiles.createForTesting(
+            libraryDesugaringSpecification.getSpecification(),
+            libraryDesugaringSpecification.getDesugarJdkLibs(),
+            out);
     desugaredApi.run(targetApi.getLevel());
     return new CodeInspector(
         out.resolve("compile_api_level_" + targetApi.getLevel())
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InvalidLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InvalidLibraryTest.java
index 397a46f..d22793d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InvalidLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InvalidLibraryTest.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8SHRINK;
-import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static org.hamcrest.core.StringContains.containsString;
 
@@ -83,8 +82,8 @@
   @Test
   public void testClasspathSupertype() throws Exception {
     Assume.assumeTrue(
-        requiresAnyCoreLibDesugaring(
-            parameters.getApiLevel(), libraryDesugaringSpecification != JDK8));
+        "Date is present in the library above O",
+        parameters.getApiLevel().isLessThan(AndroidApiLevel.O));
     testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
         .addProgramClasses(Executor.class, LocalClass.class, LocalClassOverride.class)
         .addClasspathClasses(SuperLibraryClass.class)
@@ -104,8 +103,8 @@
   @Test
   public void testNullSupertype() throws Exception {
     Assume.assumeTrue(
-        requiresAnyCoreLibDesugaring(
-            parameters.getApiLevel(), libraryDesugaringSpecification != JDK8));
+        "Date is present in the library above O",
+        parameters.getApiLevel().isLessThan(AndroidApiLevel.O));
     testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
         .addProgramClasses(Executor.class, LocalClass.class, LocalClassOverride.class)
         .setCustomLibrarySpecification(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
index eddeb8e..4b7a485 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
@@ -4,23 +4,23 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.SPECIFICATIONS_WITH_CF2CF;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.ThrowingSupplier;
 import com.android.tools.r8.utils.codeinspector.CheckCastInstructionSubject;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -31,7 +31,6 @@
 import com.android.tools.r8.utils.codeinspector.TryCatchSubject;
 import com.android.tools.r8.utils.codeinspector.TypeSubject;
 import com.google.common.collect.ImmutableSet;
-import java.nio.file.Path;
 import java.time.DateTimeException;
 import java.time.ZoneId;
 import java.time.temporal.TemporalAccessor;
@@ -41,7 +40,6 @@
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
-import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -50,10 +48,7 @@
 @RunWith(Parameterized.class)
 public class JavaTimeTest extends DesugaredLibraryTestBase {
 
-  private final TestParameters parameters;
-  private final boolean shrinkDesugaredLibrary;
-  private final boolean traceReferencesKeepRules;
-  private static final String expectedOutput =
+  private static final String EXPECTED_OUTPUT =
       StringUtils.lines(
           "Caught java.time.format.DateTimeParseException",
           "true",
@@ -64,31 +59,38 @@
           "true",
           "Hello, world");
 
-  @Parameters(name = "{2}, shrinkDesugaredLibrary: {0}, traceReferencesKeepRules {1}")
+  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(
-        BooleanUtils.values(),
-        BooleanUtils.values(),
-        getTestParameters()
-            .withAllRuntimes()
-            .withAllApiLevelsAlsoForCf()
-            .withApiLevel(AndroidApiLevel.N)
-            .build());
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+        getJdk8Jdk11(),
+        SPECIFICATIONS_WITH_CF2CF);
   }
 
   public JavaTimeTest(
-      boolean shrinkDesugaredLibrary, boolean traceReferencesKeepRules, TestParameters parameters) {
-    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
-    this.traceReferencesKeepRules = traceReferencesKeepRules;
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
     this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
   }
 
-  private void checkRewrittenInvokesForD8(CodeInspector inspector) {
-    checkRewrittenInvokes(inspector, false);
-  }
-
-  private void checkRewrittenInvokesForR8(CodeInspector inspector) {
-    checkRewrittenInvokes(inspector, true);
+  @Test
+  public void testTime() throws Throwable {
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .enableNoVerticalClassMergingAnnotations()
+        .enableInliningAnnotations()
+        .compile()
+        .inspect(i -> checkRewrittenInvokes(i, compilationSpecification.isProgramShrink()))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   private void checkRewrittenInvokes(CodeInspector inspector, boolean isR8) {
@@ -96,7 +98,7 @@
     Set<String> expectedCatchGuards;
     Set<String> expectedCheckCastType;
     String expectedInstanceOfTypes;
-    if (!requiresTimeDesugaring(parameters)) {
+    if (!requiresTimeDesugaring(parameters, libraryDesugaringSpecification != JDK8)) {
       expectedInvokeHolders =
           SetUtils.newHashSet("java.time.Clock", "java.time.LocalDate", "java.time.ZoneId");
       if (!isR8) {
@@ -149,7 +151,7 @@
             .isGreaterThanOrEqualTo(TestBase.apiLevelWithDefaultInterfaceMethodsSupport())
         && isR8) {
       String holder =
-          requiresTimeDesugaring(parameters)
+          requiresTimeDesugaring(parameters, libraryDesugaringSpecification != JDK8)
               ? "j$.time.temporal.TemporalAccessor"
               : "java.time.temporal.TemporalAccessor";
       assertThat(
@@ -186,117 +188,6 @@
     }
   }
 
-  private String desugaredLibraryKeepRules(
-      KeepRuleConsumer keepRuleConsumer, ThrowingSupplier<Path, Exception> programSupplier)
-      throws Exception {
-    String desugaredLibraryKeepRules = null;
-    if (shrinkDesugaredLibrary) {
-      desugaredLibraryKeepRules = keepRuleConsumer.get();
-      if (desugaredLibraryKeepRules != null) {
-        if (traceReferencesKeepRules) {
-          desugaredLibraryKeepRules =
-              collectKeepRulesWithTraceReferences(
-                  programSupplier.get(), buildDesugaredLibraryClassFile(parameters.getApiLevel()));
-        }
-      }
-    }
-    return desugaredLibraryKeepRules;
-  }
-
-  @Test
-  public void testTimeD8Cf() throws Exception {
-    Assume.assumeTrue(shrinkDesugaredLibrary || !traceReferencesKeepRules);
-
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-    // Use D8 to desugar with Java classfile output.
-    Path jar =
-        testForD8(Backend.CF)
-            .addInnerClasses(JavaTimeTest.class)
-            .setMinApi(parameters.getApiLevel())
-            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-            .compile()
-            .inspect(this::checkRewrittenInvokesForD8)
-            .writeToZip();
-
-    String desugaredLibraryKeepRules;
-    if (shrinkDesugaredLibrary && !traceReferencesKeepRules && keepRuleConsumer.get() != null) {
-      // Collection keep rules is only implemented in the DEX writer.
-      assertEquals(0, keepRuleConsumer.get().length());
-      desugaredLibraryKeepRules = "-keep class * { *; }";
-    } else {
-      desugaredLibraryKeepRules = desugaredLibraryKeepRules(keepRuleConsumer, () -> jar);
-    }
-
-    // Determine desugared library keep rules.
-    if (parameters.getRuntime().isDex()) {
-      // Convert to DEX without desugaring and run.
-      testForD8()
-          .addProgramFiles(jar)
-          .setMinApi(parameters.getApiLevel())
-          .disableDesugaring()
-          .compile()
-          .addDesugaredCoreLibraryRunClassPath(
-              this::buildDesugaredLibrary,
-              parameters.getApiLevel(),
-              desugaredLibraryKeepRules,
-              shrinkDesugaredLibrary)
-          .run(parameters.getRuntime(), TestClass.class)
-          .assertSuccessWithOutput(expectedOutput);
-    } else {
-      // Run on the JVM with desugared library on classpath.
-      testForJvm()
-          .addProgramFiles(jar)
-          .addRunClasspathFiles(buildDesugaredLibraryClassFile(parameters.getApiLevel()))
-          .run(parameters.getRuntime(), TestClass.class)
-          .assertSuccessWithOutput(expectedOutput);
-    }
-  }
-
-  @Test
-  public void testTimeD8() throws Exception {
-    Assume.assumeTrue(parameters.getRuntime().isDex());
-    Assume.assumeTrue(shrinkDesugaredLibrary || !traceReferencesKeepRules);
-
-    testForD8()
-        .addInnerClasses(JavaTimeTest.class)
-        .setMinApi(parameters.getApiLevel())
-        .addLibraryFiles(getLibraryFile())
-        .enableLibraryDesugaring(
-            LibraryDesugaringTestConfiguration.builder()
-                .setMinApi(parameters.getApiLevel())
-                .withKeepRuleConsumer()
-                .setMode(shrinkDesugaredLibrary ? CompilationMode.RELEASE : CompilationMode.DEBUG)
-                .build())
-        .compile()
-        .inspect(this::checkRewrittenInvokesForD8)
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(expectedOutput);
-  }
-
-  @Test
-  public void testTimeR8() throws Exception {
-    Assume.assumeTrue(parameters.getRuntime().isDex());
-    Assume.assumeTrue(shrinkDesugaredLibrary || !traceReferencesKeepRules);
-
-    testForR8(parameters.getBackend())
-        .addInnerClasses(JavaTimeTest.class)
-        .addKeepMainRule(TestClass.class)
-        .enableNoVerticalClassMergingAnnotations()
-        .setMinApi(parameters.getApiLevel())
-        .addLibraryFiles(getLibraryFile())
-        .enableLibraryDesugaring(
-            LibraryDesugaringTestConfiguration.builder()
-                .setMinApi(parameters.getApiLevel())
-                .withKeepRuleConsumer()
-                .setMode(shrinkDesugaredLibrary ? CompilationMode.RELEASE : CompilationMode.DEBUG)
-                .build())
-        .enableInliningAnnotations()
-        .compile()
-        .inspect(this::checkRewrittenInvokesForR8)
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(expectedOutput);
-  }
-
   @NoVerticalClassMerging
   static class TemporalAccessorImpl implements TemporalAccessor {
     @Override
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilFunctionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilFunctionTest.java
index b8fd76f..d14b845 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilFunctionTest.java
@@ -4,6 +4,12 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8SHRINK;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8SHRINK_TR;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.R8_L8SHRINK;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.R8_L8SHRINK_TR;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -11,10 +17,12 @@
 import com.android.tools.r8.KeepConstantArguments;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.BooleanUtils;
+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.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableSet;
 import java.util.List;
 import java.util.function.Function;
 import org.junit.Test;
@@ -25,19 +33,27 @@
 @RunWith(Parameterized.class)
 public class JavaUtilFunctionTest extends DesugaredLibraryTestBase {
 
-  private final TestParameters parameters;
-  private final boolean shrinkDesugaredLibrary;
-  private static final String expectedOutput = StringUtils.lines("Hello, world", "Hello, world");
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world", "Hello, world");
 
-  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  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(
-        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+        getJdk8Jdk11(),
+        ImmutableSet.of(D8_L8DEBUG, D8_L8SHRINK, R8_L8SHRINK, D8_L8SHRINK_TR, R8_L8SHRINK_TR));
   }
 
-  public JavaUtilFunctionTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
-    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+  public JavaUtilFunctionTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
     this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
   }
 
   private void checkRewrittenArguments(CodeInspector inspector) {
@@ -59,46 +75,17 @@
   }
 
   @Test
-  public void testJavaUtilFunctionD8() throws Exception {
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-    testForD8()
-        .addLibraryFiles(getLibraryFile())
-        .addInnerClasses(JavaUtilFunctionTest.class)
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-        .setIncludeClassesChecksum(true)
-        .compile()
-        .inspect(this::checkRewrittenArguments)
-        .addDesugaredCoreLibraryRunClassPath(
-            this::buildDesugaredLibrary,
-            parameters.getApiLevel(),
-            keepRuleConsumer.get(),
-            shrinkDesugaredLibrary)
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(expectedOutput);
-  }
-
-  @Test
-  public void testJavaUtilFunctionR8() throws Exception {
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-    testForR8(parameters.getBackend())
-        .addLibraryFiles(getLibraryFile())
+  public void testJavaUtilFunction() throws Throwable {
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
         .noMinification()
-        .addKeepMainRule(TestClass.class)
-        .addInnerClasses(JavaUtilFunctionTest.class)
-        .setMinApi(parameters.getApiLevel())
         .enableConstantArgumentAnnotations()
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
         .inspect(this::checkRewrittenArguments)
-        .addDesugaredCoreLibraryRunClassPath(
-            this::buildDesugaredLibrary,
-            parameters.getApiLevel(),
-            keepRuleConsumer.get(),
-            shrinkDesugaredLibrary)
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(expectedOutput);
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilOptionalTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilOptionalTest.java
index e2271ae..7c98967 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilOptionalTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilOptionalTest.java
@@ -4,22 +4,26 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
+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.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.InvokeInstructionSubject;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Paths;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Optional;
 import java.util.OptionalDouble;
 import java.util.OptionalInt;
@@ -27,19 +31,30 @@
 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 JavaUtilOptionalTest extends DesugaredLibraryTestBase {
 
   private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        getJdk8Jdk11(),
+        ImmutableList.of(D8_L8DEBUG));
   }
 
-  public JavaUtilOptionalTest(TestParameters parameters) {
+  public JavaUtilOptionalTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
     this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
   }
 
   private void checkRewrittenInvokes(CodeInspector inspector) {
@@ -63,7 +78,7 @@
   }
 
   @Test
-  public void testJavaUtilOptional() throws Exception {
+  public void testJavaUtilOptional() throws Throwable {
     String expectedOutput =
         StringUtils.lines(
             "false",
@@ -78,12 +93,8 @@
             "false",
             "true",
             "42.42");
-    testForD8()
-        .addInnerClasses(JavaUtilOptionalTest.class)
-        .addLibraryFiles(getLibraryFile())
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(
-            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
         .compile()
         .inspect(this::checkRewrittenInvokes)
         .run(parameters.getRuntime(), TestClass.class)
@@ -92,14 +103,9 @@
 
   @Test
   public void testJavaOptionalJava9() throws Exception {
-    testForD8()
-        .addLibraryFiles(getLibraryFile())
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
         .addProgramFiles(
             Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR).resolve("backport" + JAR_EXTENSION))
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(
-            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
-        .compile()
         .run(parameters.getRuntime(), "backport.OptionalBackportJava9Main")
         .assertSuccess();
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LegacyDesugaredLibraryConfigurationParsingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LegacyDesugaredLibraryConfigurationParsingTest.java
index ffc072c..944036e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LegacyDesugaredLibraryConfigurationParsingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LegacyDesugaredLibraryConfigurationParsingTest.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecification;
@@ -117,7 +117,9 @@
   public void testReference() throws Exception {
     // Just test that the reference file parses without issues.
     LegacyDesugaredLibrarySpecification spec =
-        runPassing(StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()));
+        runPassing(
+            StringResource.fromFile(
+                LibraryDesugaringSpecification.RELEASED_1_1_5.getSpecification()));
     assertEquals(libraryCompilation, spec.isLibraryCompilation());
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java
index 18989a3..0b048b4 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java
@@ -4,11 +4,14 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import org.junit.Test;
@@ -20,66 +23,48 @@
 public class LibraryEmptySubclassInterfaceTest extends DesugaredLibraryTestBase {
 
   private final TestParameters parameters;
-  private final boolean shrinkDesugaredLibrary;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
 
-  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  @Parameters(name = "{0}, spec: {1}, {2}")
   public static List<Object[]> data() {
     return buildParameters(
-        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        getJdk8Jdk11(),
+        DEFAULT_SPECIFICATIONS);
   }
 
   public LibraryEmptySubclassInterfaceTest(
-      boolean shrinkDesugaredLibrary, TestParameters parameters) {
-    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
     this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
   }
 
   @Test
-  public void testD8() throws Exception {
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-    testForD8()
-        .addLibraryFiles(getLibraryFile())
-        .addInnerClasses(LibraryEmptySubclassInterfaceTest.class)
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+  public void testEmptySubclassInterface() throws Throwable {
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Executor.class)
+        .noMinification()
         .compile()
-        .addDesugaredCoreLibraryRunClassPath(
-            this::buildDesugaredLibrary,
-            parameters.getApiLevel(),
-            keepRuleConsumer.get(),
-            shrinkDesugaredLibrary)
+        .inspectKeepRules(this::assertExpectedKeepRules)
         .run(parameters.getRuntime(), Executor.class)
         .assertSuccessWithOutputLines(getResult());
-    assertExpectedKeepRules(keepRuleConsumer);
   }
 
-  private void assertExpectedKeepRules(KeepRuleConsumer keepRuleConsumer) {
+  private void assertExpectedKeepRules(List<String> keepRuleList) {
     if (!requiresEmulatedInterfaceCoreLibDesugaring(parameters)) {
       return;
     }
-    String keepRules = keepRuleConsumer.get();
-    assertThat(keepRules, containsString("-keep class j$.util.concurrent.ConcurrentHashMap"));
-  }
-
-  @Test
-  public void testR8() throws Exception {
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-    testForR8(Backend.DEX)
-        .addLibraryFiles(getLibraryFile())
-        .addInnerClasses(LibraryEmptySubclassInterfaceTest.class)
-        .addKeepMainRule(Executor.class)
-        .noMinification()
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-        .compile()
-        .addDesugaredCoreLibraryRunClassPath(
-            this::buildDesugaredLibrary,
-            parameters.getApiLevel(),
-            keepRuleConsumer.get(),
-            shrinkDesugaredLibrary)
-        .run(parameters.getRuntime(), Executor.class)
-        .assertSuccessWithOutputLines(getResult());
-    assertExpectedKeepRules(keepRuleConsumer);
+    StringBuilder keepRules = new StringBuilder();
+    for (String kr : keepRuleList) {
+      keepRules.append("\n").append(kr);
+    }
+    assertThat(
+        keepRules.toString(), containsString("-keep class j$.util.concurrent.ConcurrentHashMap"));
   }
 
   private String getResult() {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java
index de87214..ebd2e9f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java
@@ -4,10 +4,15 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
-import com.android.tools.r8.LibraryDesugaringTestConfiguration;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
+
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.google.common.collect.ImmutableList;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.Spliterator;
 import java.util.Spliterators;
@@ -15,31 +20,38 @@
 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 LinkedHashSetTest extends DesugaredLibraryTestBase {
 
   private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        getJdk8Jdk11(),
+        ImmutableList.of(D8_L8DEBUG));
   }
 
-  public LinkedHashSetTest(TestParameters parameters) {
+  public LinkedHashSetTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
     this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
   }
 
   @Test
-  public void testLinkedHashSetOverrides() throws Exception {
+  public void testLinkedHashSet() throws Throwable {
     String stdOut =
-        testForD8()
-            .addLibraryFiles(getLibraryFile())
-            .addInnerClasses(LinkedHashSetTest.class)
-            .setMinApi(parameters.getApiLevel())
-            .enableCoreLibraryDesugaring(
-                LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
-            .compile()
+        testForDesugaredLibrary(
+                parameters, libraryDesugaringSpecification, compilationSpecification)
+            .addInnerClasses(getClass())
             .run(parameters.getRuntime(), Executor.class)
             .assertSuccess()
             .getStdOut();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
index cd54e5d..ddf8c9d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -11,8 +13,8 @@
 import com.android.tools.r8.GenerateLintFiles;
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
@@ -33,13 +35,17 @@
 @RunWith(Parameterized.class)
 public class LintFilesTest extends DesugaredLibraryTestBase {
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withNoneRuntime().build();
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+
+  @Parameters(name = "{0}, spec: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withNoneRuntime().build(), getJdk8Jdk11());
   }
 
-  public LintFilesTest(TestParameters parameters) {
-    parameters.assertNoneRuntime();
+  public LintFilesTest(
+      TestParameters parameters, LibraryDesugaringSpecification libraryDesugaringSpecification) {
+    assert parameters.isNoneRuntime();
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
   }
 
   private void checkFileContent(AndroidApiLevel minApiLevel, Path lintFile) throws Exception {
@@ -52,7 +58,8 @@
 
     // ConcurrentHashMap is fully supported on JDK 11.
     assertEquals(
-        isJDK11DesugaredLibrary(), methods.contains("java/util/concurrent/ConcurrentHashMap"));
+        libraryDesugaringSpecification != JDK8,
+        methods.contains("java/util/concurrent/ConcurrentHashMap"));
 
     // No parallel* methods pre L, and all stream methods supported from L.
     assertEquals(
@@ -79,7 +86,7 @@
         methods.contains(
             "java/util/stream/IntStream#allMatch(Ljava/util/function/IntPredicate;)Z"));
 
-    if (isJDK11DesugaredLibrary()) {
+    if (libraryDesugaringSpecification != JDK8) {
       // TODO(b/203382252): Investigate why the following assertions are not working on JDK 11.
       return;
     }
@@ -118,16 +125,20 @@
   @Test
   public void testFileContent() throws Exception {
     Path directory = temp.newFolder().toPath();
+    Path jdkLibJar =
+        libraryDesugaringSpecification == JDK8
+            ? ToolHelper.DESUGARED_JDK_8_LIB_JAR
+            : ToolHelper.UNDESUGARED_JDK_11_LIB_JAR;
     GenerateLintFiles.main(
         new String[] {
-          ToolHelper.getDesugarLibJsonForTesting().toString(),
-          ToolHelper.getDesugarJDKLibs().toString(),
+          libraryDesugaringSpecification.getSpecification().toString(),
+          jdkLibJar.toString(),
           directory.toString()
         });
     InternalOptions options = new InternalOptions(new DexItemFactory(), new Reporter());
     DesugaredLibrarySpecification desugaredLibrarySpecification =
         DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecification(
-            StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()),
+            StringResource.fromFile(libraryDesugaringSpecification.getSpecification()),
             options.itemFactory,
             options.reporter,
             false,
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingJ$Test.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingJ$Test.java
index ccef7e8..51fc891 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingJ$Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingJ$Test.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11TestLibraryDesugaringSpecification.EXTENSION_PATH;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static junit.framework.TestCase.assertNotNull;
 import static junit.framework.TestCase.assertTrue;
 import static org.hamcrest.CoreMatchers.containsString;
@@ -21,9 +22,9 @@
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11TestLibraryDesugaringSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.errors.DuplicateTypesDiagnostic;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -40,13 +41,17 @@
 @RunWith(Parameterized.class)
 public class MergingJ$Test extends DesugaredLibraryTestBase {
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withNoneRuntime().build();
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+
+  @Parameters(name = "{0}, spec: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withNoneRuntime().build(), getJdk8Jdk11());
   }
 
-  public MergingJ$Test(TestParameters parameters) {
-    parameters.assertNoneRuntime();
+  public MergingJ$Test(
+      TestParameters parameters, LibraryDesugaringSpecification libraryDesugaringSpecification) {
+    assert parameters.isNoneRuntime();
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
   }
 
   @Test
@@ -58,7 +63,7 @@
         () ->
             testForD8()
                 .addProgramFiles(mergerInputPart1, mergerInputPart2)
-                .addLibraryFiles(getLibraryFile())
+                .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
                 .compileWithExpectedDiagnostics(
                     diagnostics -> {
                       diagnostics
@@ -75,7 +80,7 @@
     D8Command command =
         D8Command.builder()
             .addProgramFiles(mergerInputPart1, mergerInputPart2)
-            .addLibraryFiles(getLibraryFile())
+            .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
             .setOutput(merged, OutputMode.DexIndexed)
             .build();
     try {
@@ -109,10 +114,10 @@
     Path outputDex = temp.newFolder().toPath().resolve("merger-input-dex.zip");
     L8.run(
         L8Command.builder()
-            .addLibraryFiles(getLibraryFile())
-            .addProgramFiles(ToolHelper.getDesugarJDKLibs())
+            .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
+            .addProgramFiles(libraryDesugaringSpecification.getDesugarJdkLibs())
             .addDesugaredLibraryConfiguration(
-                StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()))
+                StringResource.fromFile(libraryDesugaringSpecification.getSpecification()))
             .setMinApiLevel(AndroidApiLevel.B.getLevel())
             .setOutput(outputDex, OutputMode.DexIndexed)
             .build());
@@ -127,11 +132,11 @@
     Jdk11TestLibraryDesugaringSpecification.setUp();
     L8.run(
         L8Command.builder()
-            .addLibraryFiles(getLibraryFile())
-            .addLibraryFiles(ToolHelper.getDesugarJDKLibs())
+            .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
+            .addLibraryFiles(libraryDesugaringSpecification.getDesugarJdkLibs())
             .addProgramFiles(EXTENSION_PATH)
             .addDesugaredLibraryConfiguration(
-                StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()))
+                StringResource.fromFile(libraryDesugaringSpecification.getSpecification()))
             .setMinApiLevel(AndroidApiLevel.B.getLevel())
             .setOutput(outputDex, OutputMode.DexIndexed)
             .build());
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MinimalInterfaceSuperTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MinimalInterfaceSuperTest.java
index 6ab6b2f..20e61c2 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MinimalInterfaceSuperTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MinimalInterfaceSuperTest.java
@@ -4,38 +4,53 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
-import com.android.tools.r8.LibraryDesugaringTestConfiguration;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
+
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+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 java.util.AbstractCollection;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
+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;
 
 @SuppressWarnings("ALL")
 @RunWith(Parameterized.class)
 public class MinimalInterfaceSuperTest extends DesugaredLibraryTestBase {
 
-  private final TestParameters parameters;
-
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
-  }
-
-  public MinimalInterfaceSuperTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
   private static final String EXPECTED_OUTPUT = StringUtils.lines("removeIf from Col1Itf");
 
+  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(
+        getTestParameters().withAllRuntimes().withAllApiLevels().build(),
+        getJdk8Jdk11(),
+        DEFAULT_SPECIFICATIONS);
+  }
+
+  public MinimalInterfaceSuperTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
+    this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
+  }
+
   @Test
-  public void testCustomCollectionR8() throws Exception {
+  public void testMinimalInterfaceSuper() throws Exception {
     if (parameters.isCfRuntime()) {
       testForJvm()
           .addInnerClasses(MinimalInterfaceSuperTest.class)
@@ -43,15 +58,9 @@
           .assertSuccessWithOutput(EXPECTED_OUTPUT);
       return;
     }
-    testForR8(parameters.getBackend())
-        .addLibraryFiles(getLibraryFile())
-        .addInnerClasses(MinimalInterfaceSuperTest.class)
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(
-            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
-        .compile()
-        .assertNoMessages()
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MonthTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MonthTest.java
index fbc2ad7..943c7a9 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MonthTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MonthTest.java
@@ -4,33 +4,49 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
-import com.android.tools.r8.LibraryDesugaringTestConfiguration;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
+
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime;
+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 java.time.Month;
 import java.time.format.TextStyle;
+import java.util.List;
 import java.util.Locale;
-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 MonthTest extends DesugaredLibraryTestBase {
-  private final TestParameters parameters;
 
   private static final String EXPECTED_JAVA_8_OUTPUT = StringUtils.lines("4");
   private static final String EXPECTED_JAVA_9_OUTPUT = StringUtils.lines("April");
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  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(
+        getTestParameters().withAllRuntimes().withAllApiLevels().build(),
+        getJdk8Jdk11(),
+        DEFAULT_SPECIFICATIONS);
   }
 
-  public MonthTest(TestParameters parameters) {
+  public MonthTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
     this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
   }
 
   private String getExpectedResult(TestParameters parameters) {
@@ -41,14 +57,14 @@
       return EXPECTED_JAVA_8_OUTPUT;
     }
     assert parameters.isDexRuntime();
-    if (requiresTimeDesugaring(parameters)) {
+    if (requiresTimeDesugaring(parameters, libraryDesugaringSpecification != JDK8)) {
       return EXPECTED_JAVA_8_OUTPUT;
     }
     return EXPECTED_JAVA_9_OUTPUT;
   }
 
   @Test
-  public void testMonthD8() throws Exception {
+  public void testMonth() throws Exception {
     if (parameters.isCfRuntime()) {
       testForJvm()
           .addInnerClasses(MonthTest.class)
@@ -56,28 +72,9 @@
           .assertSuccessWithOutput(getExpectedResult(parameters));
       return;
     }
-    testForD8(parameters.getBackend())
-        .addLibraryFiles(getLibraryFile())
-        .addInnerClasses(MonthTest.class)
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(
-            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
-        .compile()
-        .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutput(getExpectedResult(parameters));
-  }
-
-  @Test
-  public void testMonthR8() throws Exception {
-    Assume.assumeTrue(parameters.isDexRuntime());
-    testForR8(parameters.getBackend())
-        .addLibraryFiles(getLibraryFile())
-        .addInnerClasses(MonthTest.class)
-        .addKeepMainRule(MonthTest.Main.class)
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(
-            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
-        .compile()
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutput(getExpectedResult(parameters));
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java
index 607ff9b..82ca473 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java
@@ -4,34 +4,50 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class NeverMergeCoreLibDesugarClasses extends DesugaredLibraryTestBase {
 
   private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection
-  data() {
-    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        getJdk8Jdk11(),
+        ImmutableList.of(D8_L8DEBUG));
   }
 
-  public NeverMergeCoreLibDesugarClasses(TestParameters parameters) {
+  public NeverMergeCoreLibDesugarClasses(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
     this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
   }
 
   @Test
@@ -44,7 +60,7 @@
       testForD8()
           .addInnerClasses(NeverMergeCoreLibDesugarClasses.class)
           .addProgramDexFileData(builder.compile())
-          .setMinApi(parameters.getRuntime())
+          .setMinApi(parameters.getApiLevel())
           .compileWithExpectedDiagnostics(
               diagnostics -> {
                 diagnostics.assertErrorsCount(1);
@@ -65,13 +81,20 @@
 
   @Test
   public void testDesugaredCoreLibrary() throws Exception {
-    Assume.assumeTrue(parameters.getApiLevel().getLevel() < 26);
+    Assume.assumeTrue(parameters.getApiLevel().getLevel() < AndroidApiLevel.O.getLevel());
     try {
+      Path input =
+          testForL8(parameters.getApiLevel())
+              .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
+              .setDebug()
+              .setDesugarJDKLibsCustomConversions(ToolHelper.DESUGAR_LIB_CONVERSIONS)
+              .compile()
+              .writeToZip();
       testForD8()
           .addInnerClasses(NeverMergeCoreLibDesugarClasses.class)
-          .addLibraryFiles(getLibraryFile())
-          .setMinApi(parameters.getRuntime())
-          .addProgramFiles(buildDesugaredLibrary(parameters.getApiLevel()))
+          .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
+          .setMinApi(parameters.getApiLevel())
+          .addProgramFiles(input)
           .compileWithExpectedDiagnostics(
               diagnostics -> {
                 diagnostics.assertErrorsCount(1);
@@ -93,14 +116,9 @@
   @Test
   public void testTestCodeRuns() throws Exception {
     // j$.util.Function is not present in recent APIs.
-    Assume.assumeTrue(parameters.getApiLevel().getLevel() < 24);
-    testForD8()
-        .addInnerClasses(NeverMergeCoreLibDesugarClasses.class)
-        .addLibraryFiles(getLibraryFile())
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(
-            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
-        .compile()
+    Assume.assumeTrue(parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel());
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines("Hello, world!");
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDefaultMethodOverrideInLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDefaultMethodOverrideInLibraryTest.java
index 87d0bbd..696d737 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDefaultMethodOverrideInLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDefaultMethodOverrideInLibraryTest.java
@@ -3,16 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.TestRuntime.CfVm;
+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.lang.reflect.Method;
 import java.util.Collection;
 import java.util.LinkedList;
@@ -21,6 +24,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 /**
  * This test checks that if a default interface method in a library is not overridden by a class
@@ -37,14 +41,24 @@
   static final String EXPECTED = StringUtils.lines("MyIntegerList::removeIf", "false", "false");
 
   private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevels().build(),
+        getJdk8Jdk11(),
+        ImmutableList.of(D8_L8DEBUG));
   }
 
-  public NoDefaultMethodOverrideInLibraryTest(TestParameters parameters) {
+  public NoDefaultMethodOverrideInLibraryTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
     this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
   }
 
   @Test
@@ -69,13 +83,8 @@
           .run(parameters.getRuntime(), Main.class)
           .assertSuccessWithOutput(EXPECTED);
     } else {
-      testForD8()
-          .addLibraryFiles(getLibraryFile())
-          .setMinApi(parameters.getApiLevel())
-          .addInnerClasses(NoDefaultMethodOverrideInLibraryTest.class)
-          .enableCoreLibraryDesugaring(
-              LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
-          .compile()
+      testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+          .addInnerClasses(getClass())
           .run(parameters.getRuntime(), Main.class)
           .assertSuccessWithOutput(EXPECTED);
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDesugaredLibraryDexFileTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDesugaredLibraryDexFileTest.java
index 3c83b83..5ddc567 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDesugaredLibraryDexFileTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDesugaredLibraryDexFileTest.java
@@ -4,10 +4,13 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -16,6 +19,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.SortedSet;
+import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -26,50 +30,37 @@
 public class NoDesugaredLibraryDexFileTest extends DesugaredLibraryTestBase {
 
   private final TestParameters parameters;
-  private final boolean shrinkDesugaredLibrary;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
 
-  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  @Parameters(name = "{0}, spec: {1}, {2}")
   public static List<Object[]> data() {
     return buildParameters(
-        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        getJdk8Jdk11(),
+        DEFAULT_SPECIFICATIONS);
   }
 
-  public NoDesugaredLibraryDexFileTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
-    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+  public NoDesugaredLibraryDexFileTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
     this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
   }
 
   @Test
-  public void testCustomCollectionD8() throws Exception {
+  public void testNoDesugaredLibraryDexFile() throws Throwable {
     Assume.assumeTrue(requiresEmulatedInterfaceCoreLibDesugaring(parameters));
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-    testForD8()
-        .addLibraryFiles(getLibraryFile())
-        .addInnerClasses(NoDesugaredLibraryDexFileTest.class)
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-        .compile()
-        .inspect(this::assertNoForwardingStreamMethod)
-        .run(parameters.getRuntime(), Executor.class)
-        .assertSuccessWithOutputLines("1", "0");
-    assertTrue(keepRuleConsumer.get().isEmpty());
-  }
-
-  @Test
-  public void testCustomCollectionR8() throws Exception {
-    Assume.assumeTrue(requiresEmulatedInterfaceCoreLibDesugaring(parameters));
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-    testForR8(parameters.getBackend())
-        .addLibraryFiles(getLibraryFile())
-        .addInnerClasses(NoDesugaredLibraryDexFileTest.class)
-        .setMinApi(parameters.getApiLevel())
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
         .addKeepClassAndMembersRules(Executor.class)
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
         .inspect(this::assertNoForwardingStreamMethod)
+        .inspectKeepRules(Assert::assertNull)
         .run(parameters.getRuntime(), Executor.class)
         .assertSuccessWithOutputLines("1", "0");
-    assertTrue(keepRuleConsumer.get().isEmpty());
   }
 
   private void assertNoForwardingStreamMethod(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoForwardingMethodsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoForwardingMethodsTest.java
index 298b653..c799716 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoForwardingMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoForwardingMethodsTest.java
@@ -4,10 +4,13 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -26,54 +29,33 @@
 public class NoForwardingMethodsTest extends DesugaredLibraryTestBase {
 
   private final TestParameters parameters;
-  private final boolean shrinkDesugaredLibrary;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
 
-  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  @Parameters(name = "{0}, spec: {1}, {2}")
   public static List<Object[]> data() {
     return buildParameters(
-        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        getJdk8Jdk11(),
+        DEFAULT_SPECIFICATIONS);
   }
 
-  public NoForwardingMethodsTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
-    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+  public NoForwardingMethodsTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
     this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
   }
 
   @Test
-  public void testCustomCollectionD8() throws Exception {
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-    testForD8()
-        .addLibraryFiles(getLibraryFile())
-        .addInnerClasses(NoForwardingMethodsTest.class)
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-        .compile()
-        .inspect(this::assertNoForwardingStreamMethod)
-        .addDesugaredCoreLibraryRunClassPath(
-            this::buildDesugaredLibrary,
-            parameters.getApiLevel(),
-            keepRuleConsumer.get(),
-            shrinkDesugaredLibrary)
-        .run(parameters.getRuntime(), Executor.class)
-        .assertSuccessWithOutputLines("str:1", "0");
-  }
-
-  @Test
-  public void testCustomCollectionR8() throws Exception {
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-    testForR8(parameters.getBackend())
-        .addLibraryFiles(getLibraryFile())
-        .addInnerClasses(NoForwardingMethodsTest.class)
-        .setMinApi(parameters.getApiLevel())
+  public void testNoForwardingMethods() throws Throwable {
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
         .addKeepClassAndMembersRules(Executor.class)
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
         .inspect(this::assertNoForwardingStreamMethod)
-        .addDesugaredCoreLibraryRunClassPath(
-            this::buildDesugaredLibrary,
-            parameters.getApiLevel(),
-            keepRuleConsumer.get(),
-            shrinkDesugaredLibrary)
         .run(parameters.getRuntime(), Executor.class)
         .assertSuccessWithOutputLines("str:1", "0");
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
index e05e09a..f503773 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
@@ -4,28 +4,27 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static com.android.tools.r8.ToolHelper.DESUGARED_JDK_8_LIB_JAR;
+import static com.android.tools.r8.ToolHelper.UNDESUGARED_JDK_11_LIB_JAR;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.SPECIFICATIONS_WITH_CF2CF;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.android.tools.r8.utils.structural.Ordered;
 import com.google.common.collect.ImmutableList;
-import java.nio.file.Path;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
@@ -63,43 +62,38 @@
           "3",
           "4");
 
-  private final TestParameters parameters;
   private final boolean libraryDesugarJavaUtilObjects;
-  private final boolean shrinkDesugaredLibrary = false;
-  private final Path androidJar;
 
-  @Parameters(name = "{0}")
+  private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
   public static List<Object[]> data() {
+    LibraryDesugaringSpecification jdk8MaxCompileSdk =
+        new LibraryDesugaringSpecification(
+            "JDK8_MAX", DESUGARED_JDK_8_LIB_JAR, "desugar_jdk_libs.json", AndroidApiLevel.LATEST);
+    LibraryDesugaringSpecification jdk11MaxCompileSdk =
+        new LibraryDesugaringSpecification(
+            "JDK11_MAX",
+            UNDESUGARED_JDK_11_LIB_JAR,
+            "jdk11/desugar_jdk_libs.json",
+            AndroidApiLevel.LATEST);
     return buildParameters(
-        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+        ImmutableList.of(JDK8, JDK11, jdk8MaxCompileSdk, jdk11MaxCompileSdk),
+        SPECIFICATIONS_WITH_CF2CF);
   }
 
-  public ObjectsTest(TestParameters parameters) {
+  public ObjectsTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
     this.parameters = parameters;
-    this.libraryDesugarJavaUtilObjects = isJDK11DesugaredLibrary();
-    this.androidJar =
-        ToolHelper.getAndroidJar(
-            Ordered.max(parameters.getApiLevel(), getRequiredCompilationAPILevel()));
-  }
-
-  DesugaredLibrarySpecification desugaredLibrarySpecification(
-      InternalOptions options, boolean libraryCompilation, TestParameters parameters) {
-    return DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecification(
-        StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()),
-        options.dexItemFactory(),
-        options.reporter,
-        libraryCompilation,
-        parameters.getApiLevel().getLevel());
-  }
-
-  private void configurationForProgramCompilation(InternalOptions options) {
-    setDesugaredLibrarySpecificationForTesting(
-        options, desugaredLibrarySpecification(options, false, parameters));
-  }
-
-  private void configurationForLibraryCompilation(InternalOptions options) {
-    setDesugaredLibrarySpecificationForTesting(
-        options, desugaredLibrarySpecification(options, true, parameters));
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
+    this.libraryDesugarJavaUtilObjects =
+        !libraryDesugaringSpecification.toString().contains("JDK8");
   }
 
   private Matcher<MethodSubject> invokesObjectsCompare(String holder) {
@@ -291,129 +285,20 @@
   }
 
   @Test
-  public void testD8Cf() throws Exception {
-    // Adjust API level if running on JDK 8. The java.util.Objects methods added in
-    // Android R where added in JDK 9, so setting the the API level to Android P will backport
-    // these methods for JDK 8.
-    AndroidApiLevel apiLevel = parameters.getApiLevel();
-    if (parameters.getRuntime().isCf()
-        && parameters.getRuntime().asCf().getVm() == CfVm.JDK8
-        && apiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.R)) {
-      apiLevel = AndroidApiLevel.P;
-    }
-
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-    // Use D8 to desugar with Java classfile output.
-    Path jar =
-        testForD8(Backend.CF)
-            .addLibraryFiles(androidJar)
-            .addOptionsModification(this::configurationForProgramCompilation)
-            .addInnerClasses(ObjectsTest.class)
-            .addProgramClassFileData(dumpAndroidRUtilsObjectsMethods())
-            .setMinApi(apiLevel)
-            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-            .compile()
-            .inspect(this::inspect)
-            .writeToZip();
-
-    if (parameters.getRuntime().isDex()) {
-      // Collection keep rules is only implemented in the DEX writer.
-      String desugaredLibraryKeepRules = keepRuleConsumer.get();
-      if (desugaredLibraryKeepRules != null) {
-        assertEquals(0, desugaredLibraryKeepRules.length());
-        desugaredLibraryKeepRules = "-keep class * { *; }";
-      }
-
-      // Convert to DEX without desugaring and run.
-      testForD8()
-          .addLibraryFiles(androidJar)
-          .addProgramFiles(jar)
-          .setMinApi(apiLevel)
-          .disableDesugaring()
-          .compile()
-          .addDesugaredCoreLibraryRunClassPath(
-              (apiLevel_, keepRules, shrink) ->
-                  buildDesugaredLibrary(
-                      apiLevel_,
-                      keepRules,
-                      shrink,
-                      ImmutableList.of(),
-                      this::configurationForLibraryCompilation),
-              parameters.getApiLevel(),
-              desugaredLibraryKeepRules,
-              shrinkDesugaredLibrary)
-          .run(parameters.getRuntime(), TestClass.class)
-          .assertSuccessWithOutput(EXPECTED_OUTPUT);
-    } else {
-      // Build the desugared library in class file format.
-      Path desugaredLib =
-          getDesugaredLibraryInCF(parameters, this::configurationForLibraryCompilation);
-
-      // Run on the JVM with desugared library on classpath.
-      testForJvm()
-          .addProgramFiles(jar)
-          .addRunClasspathFiles(desugaredLib)
-          .run(parameters.getRuntime(), TestClass.class)
-          .assertSuccessWithOutput(EXPECTED_OUTPUT);
-    }
-  }
-
-  @Test
-  public void testD8() throws Exception {
-    Assume.assumeTrue(parameters.getRuntime().isDex());
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-    testForD8()
-        .addLibraryFiles(androidJar)
-        .addOptionsModification(this::configurationForProgramCompilation)
-        .addInnerClasses(ObjectsTest.class)
-        .addProgramClassFileData(dumpAndroidRUtilsObjectsMethods())
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-        .compile()
-        .addDesugaredCoreLibraryRunClassPath(
-            (apiLevel, keepRules, shrink) ->
-                buildDesugaredLibrary(
-                    apiLevel,
-                    keepRules,
-                    shrink,
-                    ImmutableList.of(),
-                    this::configurationForLibraryCompilation),
-            parameters.getApiLevel(),
-            keepRuleConsumer.get(),
-            shrinkDesugaredLibrary)
-        .inspect(this::inspect)
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(EXPECTED_OUTPUT);
-  }
-
-  @Test
-  public void testR8() throws Exception {
-    Assume.assumeTrue(parameters.getRuntime().isDex());
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-    testForR8(parameters.getBackend())
-        .addLibraryFiles(androidJar)
-        .addOptionsModification(this::configurationForProgramCompilation)
-        .addInnerClasses(ObjectsTest.class)
+  public void testObjects() throws Throwable {
+    Assume.assumeFalse(
+        "Method is absent on JDK8",
+        parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.R)
+            && parameters.isCfRuntime(CfVm.JDK8));
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
+        .addProgramClassFileData(ImmutableList.of(dumpAndroidRUtilsObjectsMethods()))
         .addKeepMainRule(TestClass.class)
-        .addProgramClassFileData(dumpAndroidRUtilsObjectsMethods())
         .enableInliningAnnotations()
         .noMinification()
         .addKeepRules("-keep class AndroidRUtilsObjectsMethods { *; }")
         .addKeepRules("-neverinline class AndroidRUtilsObjectsMethods { *; }")
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
-        .addDesugaredCoreLibraryRunClassPath(
-            (apiLevel, keepRules, shrink) ->
-                buildDesugaredLibrary(
-                    apiLevel,
-                    keepRules,
-                    shrink,
-                    ImmutableList.of(),
-                    this::configurationForLibraryCompilation),
-            parameters.getApiLevel(),
-            keepRuleConsumer.get(),
-            shrinkDesugaredLibrary)
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PriorityQueueSubclassTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PriorityQueueSubclassTest.java
index 72c8729..2b687bd 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PriorityQueueSubclassTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PriorityQueueSubclassTest.java
@@ -4,8 +4,12 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
+
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import java.util.List;
 import java.util.PriorityQueue;
 import java.util.stream.Stream;
@@ -18,52 +22,31 @@
 public class PriorityQueueSubclassTest extends DesugaredLibraryTestBase {
 
   private final TestParameters parameters;
-  private final boolean shrinkDesugaredLibrary;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
 
-  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  @Parameters(name = "{0}, spec: {1}, {2}")
   public static List<Object[]> data() {
     return buildParameters(
-        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        getJdk8Jdk11(),
+        DEFAULT_SPECIFICATIONS);
   }
 
-  public PriorityQueueSubclassTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
-    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+  public PriorityQueueSubclassTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
     this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
   }
 
   @Test
-  public void testPriorityQueueD8() throws Exception {
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-    testForD8()
-        .addLibraryFiles(getLibraryFile())
-        .addInnerClasses(PriorityQueueSubclassTest.class)
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-        .compile()
-        .addDesugaredCoreLibraryRunClassPath(
-            this::buildDesugaredLibrary,
-            parameters.getApiLevel(),
-            keepRuleConsumer.get(),
-            shrinkDesugaredLibrary)
-        .run(parameters.getRuntime(), Executor.class)
-        .assertSuccessWithOutputLines("1");
-  }
-
-  @Test
-  public void testPriorityQueueR8() throws Exception {
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-    testForR8(Backend.DEX)
-        .addLibraryFiles(getLibraryFile())
-        .addInnerClasses(PriorityQueueSubclassTest.class)
-        .setMinApi(parameters.getApiLevel())
+  public void testPriorityQueue() throws Throwable {
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
         .addKeepClassAndMembersRules(Executor.class)
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-        .compile()
-        .addDesugaredCoreLibraryRunClassPath(
-            this::buildDesugaredLibrary,
-            parameters.getApiLevel(),
-            keepRuleConsumer.get(),
-            shrinkDesugaredLibrary)
         .run(parameters.getRuntime(), Executor.class)
         .assertSuccessWithOutputLines("1");
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramInterfaceWithLibraryMethod.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramInterfaceWithLibraryMethod.java
index daf4da7..18ccc4e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramInterfaceWithLibraryMethod.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramInterfaceWithLibraryMethod.java
@@ -3,134 +3,68 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar.desugaredlibrary;
 
-import static org.junit.Assume.assumeTrue;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.SPECIFICATIONS_WITH_CF2CF;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 
-import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CustomLibrarySpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
-import java.nio.file.Files;
-import java.nio.file.Path;
+import java.util.List;
 import java.util.function.Consumer;
-import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
 // See b/204518518.
 @RunWith(Parameterized.class)
 public class ProgramInterfaceWithLibraryMethod extends DesugaredLibraryTestBase {
 
-  @Parameter(0)
-  public TestParameters parameters;
-
   private static final String EXPECTED_RESULT = StringUtils.lines("Hello, world!");
-  private static Path CUSTOM_LIB_DEX;
-  private static Path CUSTOM_LIB_CF;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  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(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        getJdk8Jdk11(),
+        SPECIFICATIONS_WITH_CF2CF);
   }
 
-  @BeforeClass
-  public static void compileCustomLib() throws Exception {
-    CUSTOM_LIB_DEX = getStaticTemp().newFolder().toPath().resolve("customLibDex.jar");
-    testForD8(getStaticTemp())
-        .addProgramClasses(LibraryClass.class)
-        .setMinApi(AndroidApiLevel.B)
-        .compile()
-        .writeToZip(CUSTOM_LIB_DEX);
-    CUSTOM_LIB_CF = getStaticTemp().newFolder().toPath().resolve("customLibCf.jar");
-    ZipBuilder.builder(CUSTOM_LIB_CF)
-        .addBytes(
-            DescriptorUtils.getPathFromJavaType(LibraryClass.class),
-            Files.readAllBytes(ToolHelper.getClassFileForTestClass(LibraryClass.class)))
-        .build();
+  public ProgramInterfaceWithLibraryMethod(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
+    this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
   }
 
   @Test
-  public void testD8() throws Exception {
-    assumeTrue(parameters.isDexRuntime());
-    testForD8()
-        .addLibraryFiles(getLibraryFile())
-        .addLibraryClasses(LibraryClass.class)
-        .setMinApi(parameters.getApiLevel())
+  public void testProgramInterfaceWithLibraryMethod() throws Throwable {
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
         .addProgramClasses(Executor.class, ProgramInterface.class, ProgramClass.class)
-        .enableCoreLibraryDesugaring(
-            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
-        .compile()
-        .addRunClasspathFiles(CUSTOM_LIB_DEX)
-        .run(parameters.getRuntime(), Executor.class)
-        .applyIf(
-            parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N),
-            r -> r.assertSuccessWithOutput(EXPECTED_RESULT),
-            r -> r.assertFailureWithErrorThatThrows(AbstractMethodError.class));
-  }
-
-  @Test
-  public void testD8CfToCf() throws Exception {
-    Path jar =
-        testForD8(Backend.CF)
-            .addLibraryFiles(getLibraryFile())
-            .addLibraryClasses(LibraryClass.class)
-            .addProgramClasses(Executor.class, ProgramInterface.class, ProgramClass.class)
-            .setMinApi(parameters.getApiLevel())
-            .enableCoreLibraryDesugaring(
-                LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
-            .compile()
-            .writeToZip();
-    if (parameters.getRuntime().isDex()) {
-      testForD8()
-          .addProgramFiles(jar)
-          .setMinApi(parameters.getApiLevel())
-          .disableDesugaring()
-          .compile()
-          .addDesugaredCoreLibraryRunClassPath(
-              this::buildDesugaredLibrary, parameters.getApiLevel())
-          .addRunClasspathFiles(CUSTOM_LIB_DEX)
-          .run(parameters.getRuntime(), Executor.class)
-          .applyIf(
-              parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N),
-              r -> r.assertSuccessWithOutput(EXPECTED_RESULT),
-              r -> r.assertFailureWithErrorThatThrows(AbstractMethodError.class));
-    } else {
-      testForJvm()
-          .addProgramFiles(jar)
-          .addRunClasspathFiles(getDesugaredLibraryInCF(parameters, options -> {}))
-          .addRunClasspathFiles(CUSTOM_LIB_CF)
-          .run(parameters.getRuntime(), Executor.class)
-          .applyIf(
-              parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N),
-              r -> r.assertSuccessWithOutput(EXPECTED_RESULT),
-              r -> r.assertFailureWithErrorThatThrows(AbstractMethodError.class));
-    }
-  }
-
-  @Test
-  public void testR8() throws Exception {
-    testForR8(parameters.getBackend())
-        .addLibraryFiles(getLibraryFile())
-        .addLibraryClasses(LibraryClass.class)
-        .setMinApi(parameters.getApiLevel())
-        .addProgramClasses(Executor.class, ProgramInterface.class, ProgramClass.class)
+        .setCustomLibrarySpecification(
+            new CustomLibrarySpecification(LibraryClass.class, AndroidApiLevel.B))
         .addKeepMainRule(Executor.class)
-        .enableCoreLibraryDesugaring(
-            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
-        .compile()
-        .addRunClasspathFiles(parameters.isDexRuntime() ? CUSTOM_LIB_DEX : CUSTOM_LIB_CF)
         .run(parameters.getRuntime(), Executor.class)
         .applyIf(
             parameters.isDexRuntime()
                 && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N),
             r -> r.assertSuccessWithOutput(EXPECTED_RESULT),
-            r -> r.assertFailureWithErrorThatThrows(NoSuchMethodError.class));
+            r -> {
+              if (compilationSpecification.isProgramShrink()) {
+                r.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+              } else {
+                r.assertFailureWithErrorThatThrows(AbstractMethodError.class);
+              }
+            });
   }
 
   static class Executor {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
index 20e5408..8ed7907 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
@@ -4,30 +4,34 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static com.android.tools.r8.ToolHelper.DESUGARED_JDK_8_LIB_JAR;
+import static com.android.tools.r8.ToolHelper.UNDESUGARED_JDK_11_LIB_JAR;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertTrue;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertFalse;
 
-import com.android.tools.r8.D8TestCompileResult;
-import com.android.tools.r8.D8TestRunResult;
-import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import java.nio.file.Path;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import java.nio.file.Paths;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
-import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -39,66 +43,85 @@
   private static final String TEST_CLASS = "stream.ProgramRewritingTestClass";
 
   private final TestParameters parameters;
-  private final boolean shrinkDesugaredLibrary;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
 
-  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  @Parameters(name = "{0}, spec: {1}, {2}")
   public static List<Object[]> data() {
+    LibraryDesugaringSpecification jdk8CoreLambdaStubs =
+        new LibraryDesugaringSpecification(
+            "JDK8_CL",
+            ImmutableSet.of(
+                DESUGARED_JDK_8_LIB_JAR,
+                ToolHelper.DESUGAR_LIB_CONVERSIONS,
+                ToolHelper.getCoreLambdaStubs()),
+            JDK8.getSpecification(),
+            ImmutableSet.of(ToolHelper.getAndroidJar(AndroidApiLevel.O)),
+            "");
+    LibraryDesugaringSpecification jdk11CoreLambdaStubs =
+        new LibraryDesugaringSpecification(
+            "JDK11_CL",
+            ImmutableSet.of(
+                UNDESUGARED_JDK_11_LIB_JAR,
+                ToolHelper.DESUGAR_LIB_CONVERSIONS,
+                ToolHelper.getCoreLambdaStubs()),
+            JDK11.getSpecification(),
+            ImmutableSet.of(ToolHelper.getAndroidJar(AndroidApiLevel.R)),
+            "");
     return buildParameters(
-        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        ImmutableList.of(JDK8, JDK11, jdk8CoreLambdaStubs, jdk11CoreLambdaStubs),
+        DEFAULT_SPECIFICATIONS);
   }
 
-  public ProgramRewritingTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
-    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+  public ProgramRewritingTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
     this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
   }
 
   @Test
-  public void testProgramD8() throws Exception {
-    ArrayList<Path> coreLambdaStubs = new ArrayList<>();
-    coreLambdaStubs.add(ToolHelper.getCoreLambdaStubs());
-    for (Boolean coreLambdaStubsActive : BooleanUtils.values()) {
-      KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-      D8TestCompileResult compileResult =
-          testForD8()
-              .addLibraryFiles(getLibraryFile())
-              .addProgramFiles(Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR + "stream.jar"))
-              .setMinApi(parameters.getApiLevel())
-              .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-              .compile()
-              .inspect(this::checkRewrittenInvokes);
-      if (parameters.getApiLevel().getLevel() < AndroidApiLevel.O.getLevel()) {
-        compileResult.addRunClasspathFiles(
-            buildCoreLambdaDesugaredLibrary(
-                parameters.getApiLevel(),
-                keepRuleConsumer.get(),
-                shrinkDesugaredLibrary,
-                coreLambdaStubsActive,
-                coreLambdaStubs));
-      }
-      D8TestRunResult runResult =
-          compileResult.run(parameters.getRuntime(), TEST_CLASS).assertSuccess();
-      assertResultIsCorrect(runResult.getStdOut(), runResult.getStdErr(), keepRuleConsumer.get());
-    }
-  }
-
-  private Path buildCoreLambdaDesugaredLibrary(
-      AndroidApiLevel apiLevel,
-      String keepRules,
-      boolean shrink,
-      boolean coreLambdaStubsActive,
-      ArrayList<Path> coreLambdaStubs) {
-    return coreLambdaStubsActive
-        ? buildDesugaredLibrary(apiLevel, keepRules, shrink, coreLambdaStubs)
-        : buildDesugaredLibrary(apiLevel, keepRules, shrink);
+  public void testRewriting() throws Throwable {
+    Box<String> keepRules = new Box<>();
+    SingleTestRunResult<?> run =
+        testForDesugaredLibrary(
+                parameters, libraryDesugaringSpecification, compilationSpecification)
+            .addProgramFiles(Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR + "stream.jar"))
+            .addKeepMainRule(TEST_CLASS)
+            .applyIf(
+                compilationSpecification.isProgramShrink(),
+                b ->
+                    b.addOptionsModification(
+                        options -> {
+                          // TODO(b/140233505): Allow devirtualization once fixed.
+                          options.enableDevirtualization = false;
+                        }))
+            .compile()
+            .inspect(this::checkRewrittenInvokes)
+            .inspectKeepRules(
+                kr -> {
+                  if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
+                    keepRules.set(String.join("\n", kr));
+                  } else {
+                    assert kr == null;
+                    keepRules.set("");
+                  }
+                })
+            .run(parameters.getRuntime(), TEST_CLASS);
+    assertResultIsCorrect(run.getStdOut(), run.getStdErr(), keepRules.get());
   }
 
   private void assertResultIsCorrect(String stdOut, String stdErr, String keepRules) {
     if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
-      if (!shrinkDesugaredLibrary) {
+      if (compilationSpecification.isL8Shrink()) {
+        assertGeneratedKeepRulesAreCorrect(keepRules);
+      } else {
         // When shrinking the class names are not printed correctly anymore due to minification.
         assertLines2By2Correct(stdOut);
       }
-      assertGeneratedKeepRulesAreCorrect(keepRules);
     }
     if (parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
       // Flaky: There might be a missing method on lambda deserialization.
@@ -110,39 +133,6 @@
     }
   }
 
-  @Test
-  public void testProgramR8() throws Exception {
-    Assume.assumeTrue(
-        "TODO(b/139451198): Make the test run with new SDK.",
-        parameters.getApiLevel().getLevel() < AndroidApiLevel.O.getLevel());
-    for (Boolean minifying : BooleanUtils.values()) {
-      KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-      R8TestRunResult runResult =
-          testForR8(parameters.getBackend())
-              .addLibraryFiles(getLibraryFile())
-              .minification(minifying)
-              .addKeepMainRule(TEST_CLASS)
-              .addProgramFiles(Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR + "stream.jar"))
-              .setMinApi(parameters.getApiLevel())
-              .addOptionsModification(
-                  options -> {
-                    // TODO(b/140233505): Allow devirtualization once fixed.
-                    options.enableDevirtualization = false;
-                  })
-              .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-              .compile()
-              .inspect(this::checkRewrittenInvokes)
-              .addDesugaredCoreLibraryRunClassPath(
-                  this::buildDesugaredLibrary,
-                  parameters.getApiLevel(),
-                  keepRuleConsumer.get(),
-                  shrinkDesugaredLibrary)
-              .run(parameters.getRuntime(), TEST_CLASS)
-              .assertSuccess();
-      assertResultIsCorrect(runResult.getStdOut(), runResult.getStdErr(), keepRuleConsumer.get());
-    }
-  }
-
   private void checkRewrittenInvokes(CodeInspector inspector) {
     if (parameters.getApiLevel().getLevel() >= AndroidApiLevel.N.getLevel()) {
       return;
@@ -213,6 +203,6 @@
             "}",
             "-keep class j$.util.stream.IntStream",
             "-keep class j$.util.stream.Stream");
-    assertEquals(expectedResult, keepRules);
+    assertEquals(expectedResult.trim(), keepRules.trim());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PseudoPlatformApiTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PseudoPlatformApiTest.java
index 3c1716a..0901073 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PseudoPlatformApiTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PseudoPlatformApiTest.java
@@ -97,13 +97,14 @@
 
   @Test
   public void testD8WithLibraryDesugaringOemClassNotPresent() throws Exception {
+    // Enable library desugaring with an effective min API of 1.
     testForD8(parameters.getBackend())
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
         .addLibraryFiles(androidJarAdditions())
         .addProgramClasses(ProgramClass.class)
         .setMinApi(AndroidApiLevel.H_MR2)
-        // Enable library desugaring with an effective min API of 1.
-        .enableLibraryDesugaring(LibraryDesugaringTestConfiguration.forApiLevel(AndroidApiLevel.B))
+        .enableCoreLibraryDesugaring(
+            LibraryDesugaringTestConfiguration.forApiLevel(AndroidApiLevel.B))
         .addRunClasspathFiles(androidJarAdditionsDex())
         .run(parameters.getRuntime(), ProgramClass.class)
         .assertSuccessWithOutputLines("DEFAULT-X", "Y-DEFAULT");
@@ -111,13 +112,14 @@
 
   @Test
   public void testD8WithLibraryDesugaringOemClassPresent() throws Exception {
+    // Enable library desugaring with an effective min API of 1.
     testForD8(parameters.getBackend())
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
         .addLibraryFiles(androidJarAdditions())
         .addProgramClasses(ProgramClass.class)
         .setMinApi(AndroidApiLevel.H_MR2)
-        // Enable library desugaring with an effective min API of 1.
-        .enableLibraryDesugaring(LibraryDesugaringTestConfiguration.forApiLevel(AndroidApiLevel.B))
+        .enableCoreLibraryDesugaring(
+            LibraryDesugaringTestConfiguration.forApiLevel(AndroidApiLevel.B))
         .addRunClasspathFiles(androidJarAdditionsDex())
         .addRunClasspathFiles(oemDex())
         .run(parameters.getRuntime(), ProgramClass.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java
index 29a21ad..53edf4c 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java
@@ -6,8 +6,11 @@
 
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
 
 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.CustomLibrarySpecification;
@@ -29,7 +32,11 @@
   private final CompilationSpecification compilationSpecification;
 
   private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.O;
-  private static final String EXPECTED_RESULT = StringUtils.lines("Z", "Z", "true", "Z", "Z");
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines("Z", "Z", "true", "Z", "Z", "true", "true", "true", "true", "true", "true");
+  private static final String DESUGARED_LIBRARY_EXPECTED_RESULT =
+      StringUtils.lines(
+          "Z", "Z", "true", "Z", "Z", "true", "true", "false", "false", "true", "true");
 
   @Parameters(name = "{0}, spec: {1}, {2}")
   public static List<Object[]> data() {
@@ -56,6 +63,41 @@
             new CustomLibrarySpecification(CustomLibClass.class, MIN_SUPPORTED))
         .addKeepMainRule(Executor.class)
         .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(DESUGARED_LIBRARY_EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testD8() throws Throwable {
+    // Run a D8 test without desugared library on all runtimes which natively supports java.time to
+    // ensure the expectations. The API level check is just to not run the same test repeatedly.
+    assertEquals(AndroidApiLevel.O, MIN_SUPPORTED);
+    assumeTrue(
+        parameters.getApiLevel().isEqualTo(AndroidApiLevel.N_MR1)
+            && parameters.isDexRuntime()
+            && parameters.asDexRuntime().getVersion().isNewerThanOrEqual(Version.V8_1_0)
+            && compilationSpecification == CompilationSpecification.D8_L8DEBUG);
+    testForD8(parameters.getBackend())
+        .addProgramClasses(Executor.class, CustomLibClass.class)
+        .setMinApi(MIN_SUPPORTED)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8() throws Throwable {
+    // Run a R8 test without desugared library on all runtimes which natively supports java.time to
+    // ensure the expectations. The API level check is just to not run the same test repeatedly.
+    assertEquals(AndroidApiLevel.O, MIN_SUPPORTED);
+    assumeTrue(
+        parameters.getApiLevel().isEqualTo(AndroidApiLevel.N_MR1)
+            && parameters.isDexRuntime()
+            && parameters.asDexRuntime().getVersion().isNewerThanOrEqual(Version.V8_1_0)
+            && compilationSpecification == CompilationSpecification.D8_L8DEBUG);
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Executor.class, CustomLibClass.class)
+        .addKeepMainRule(Executor.class)
+        .setMinApi(MIN_SUPPORTED)
+        .run(parameters.getRuntime(), Executor.class)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
@@ -71,6 +113,12 @@
       System.out.println(localClock == clock2);
       System.out.println(CustomLibClass.getClocks()[0].getZone());
       System.out.println(CustomLibClass.getClockss()[0][0].getZone());
+      System.out.println(clock1.equals(CustomLibClass.getClock()));
+      System.out.println(localClock.equals(Clock.systemUTC()));
+      System.out.println(localClock.equals(clock1)); // Prints false with desugared library.
+      System.out.println(clock1.equals(localClock)); // Prints false with desugared library.
+      System.out.println(clock1.equals(CustomLibClass.getClocks()[0]));
+      System.out.println(clock1.equals(CustomLibClass.getClockss()[0][0]));
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java
index 1846a68..ed4b818 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java
@@ -63,7 +63,7 @@
 
   private Path buildClass(Class<?> cls) throws Exception {
     return testForD8()
-        .addLibraryFiles(getLibraryFile())
+        .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
         .setMinApi(parameters.getApiLevel())
         .addProgramClasses(cls)
         .enableCoreLibraryDesugaring(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperEqualityTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperEqualityTest.java
new file mode 100644
index 0000000..1353145
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperEqualityTest.java
@@ -0,0 +1,241 @@
+// 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.conversiontests;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+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.CustomLibrarySpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+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 WrapperEqualityTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
+
+  private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "true", "true", "true", "true", "true", "true", "true", "true", "false", "false", "1",
+          "1", "2", "2", "1", "1", "1", "0", "true", "true", "true", "true");
+  private static final String DESUGARED_LIBRARY_EXPECTED_RESULT =
+      StringUtils.lines(
+          "true", "true", "true", "true", "false", "true", "false", "true", "false", "false", "1",
+          "1", "2", "2", "1", "1", "1", "0", "false", "true", "false", "true");
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getConversionParametersUpToExcluding(MIN_SUPPORTED),
+        getJdk8Jdk11(),
+        DEFAULT_SPECIFICATIONS);
+  }
+
+  public WrapperEqualityTest(
+      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)
+        .addProgramClasses(Executor.class)
+        .setCustomLibrarySpecification(
+            new CustomLibrarySpecification(CustomLibClass.class, MIN_SUPPORTED))
+        .addKeepMainRule(Executor.class)
+        .compile()
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(DESUGARED_LIBRARY_EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testD8() throws Throwable {
+    // Run a D8 test without desugared library on all runtimes which natively supports
+    // java.util.function to ensure the expectations. The API level check is just to not run the
+    // same test repeatedly.
+    assertEquals(AndroidApiLevel.N, MIN_SUPPORTED);
+    assumeTrue(
+        parameters.getApiLevel().isEqualTo(AndroidApiLevel.M)
+            && parameters.isDexRuntime()
+            && parameters.asDexRuntime().getVersion().isNewerThanOrEqual(Version.V8_1_0)
+            && compilationSpecification == CompilationSpecification.D8_L8DEBUG);
+    testForD8(parameters.getBackend())
+        .addProgramClasses(Executor.class, CustomLibClass.class)
+        .compile()
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  static class Executor {
+
+    public static void main(String[] args) {
+      Consumer<Boolean> consumer = b -> {};
+      Supplier<Boolean> supplier = () -> Boolean.TRUE;
+      // Prints true for desugared library as the same wrapper is used for both arguments.
+      System.out.println(CustomLibClass.same(consumer, consumer));
+      System.out.println(CustomLibClass.equals(consumer, consumer));
+      // Prints true for desugared library as the same wrapper is used for both arguments.
+      System.out.println(CustomLibClass.same(supplier, supplier));
+      System.out.println(CustomLibClass.equals(supplier, supplier));
+
+      CustomLibClass.setConsumer(consumer);
+      CustomLibClass.setSupplier(supplier);
+      System.out.println(CustomLibClass.same(consumer));
+      System.out.println(CustomLibClass.equals(consumer));
+      System.out.println(CustomLibClass.same(supplier));
+      System.out.println(CustomLibClass.equals(supplier));
+      System.out.println(CustomLibClass.equalsWithObject(consumer, new HashMap<>()));
+      System.out.println(CustomLibClass.equalsWithObject(supplier, new ArrayList<>()));
+
+      CustomLibClass.register(consumer, new Object());
+      System.out.println(CustomLibClass.registrations());
+      CustomLibClass.register(consumer, new Object());
+      System.out.println(CustomLibClass.registrations());
+      CustomLibClass.register(supplier, new Object());
+      System.out.println(CustomLibClass.registrations());
+      CustomLibClass.register(supplier, new Object());
+      System.out.println(CustomLibClass.registrations());
+      System.out.println(CustomLibClass.suppliers());
+      System.out.println(CustomLibClass.consumers());
+      CustomLibClass.unregister(consumer);
+      System.out.println(CustomLibClass.registrations());
+      CustomLibClass.unregister(supplier);
+      System.out.println(CustomLibClass.registrations());
+
+      // Prints false for desugared library as wrappers does not keep identity.
+      System.out.println(
+          CustomLibClass.getConsumerFromPlatform() == CustomLibClass.getConsumerFromPlatform());
+      System.out.println(
+          CustomLibClass.getConsumerFromPlatform()
+              .equals(CustomLibClass.getConsumerFromPlatform()));
+      // Prints false for desugared library as wrappers does not keep identity.
+      System.out.println(
+          CustomLibClass.getSupplierFromPlatform() == CustomLibClass.getSupplierFromPlatform());
+      System.out.println(
+          CustomLibClass.getSupplierFromPlatform()
+              .equals(CustomLibClass.getSupplierFromPlatform()));
+    }
+  }
+
+  // This class will be put at compilation time as library and on the runtime class path.
+  // This class is convenient for easy testing. Each method plays the role of methods in the
+  // platform APIs for which argument/return values need conversion.
+  static class CustomLibClass {
+    private static final Map<Object, Object> map = new HashMap<>();
+    private static final Consumer<Boolean> consumer = b -> {};
+    private static final Supplier<Boolean> supplier = () -> Boolean.TRUE;
+
+    private static Consumer<Boolean> appConsumer;
+    private static Supplier<Boolean> appSupplier;
+
+    public static boolean equals(Consumer<Boolean> consumer1, Consumer<Boolean> consumer2) {
+      return consumer1.equals(consumer2) && consumer2.equals(consumer1);
+    }
+
+    public static boolean equals(Supplier<Boolean> supplier1, Supplier<Boolean> supplier2) {
+      return supplier1.equals(supplier2) && supplier2.equals(supplier1);
+    }
+
+    public static boolean same(Consumer<Boolean> consumer1, Consumer<Boolean> consumer2) {
+      return consumer1.equals(consumer2) && consumer2.equals(consumer1);
+    }
+
+    public static boolean same(Supplier<Boolean> supplier1, Supplier<Boolean> supplier2) {
+      return supplier1.equals(supplier2) && supplier2.equals(supplier1);
+    }
+
+    public static void setConsumer(Consumer<Boolean> consumer) {
+      appConsumer = consumer;
+    }
+
+    public static void setSupplier(Supplier supplier) {
+      appSupplier = supplier;
+    }
+
+    public static boolean equals(Consumer<Boolean> consumer) {
+      return consumer.equals(appConsumer) && appConsumer.equals(consumer);
+    }
+
+    public static boolean equals(Supplier<Boolean> supplier) {
+      return supplier.equals(appSupplier) && appSupplier.equals(supplier);
+    }
+
+    public static boolean same(Consumer<Boolean> consumer) {
+      return appConsumer == consumer;
+    }
+
+    public static boolean same(Supplier supplier) {
+      return appSupplier == supplier;
+    }
+
+    public static boolean equalsWithObject(Consumer<Boolean> consumer, Object object) {
+      return consumer.equals(object);
+    }
+
+    public static boolean equalsWithObject(Supplier<Boolean> supplier, Object object) {
+      return supplier.equals(object);
+    }
+
+    public static void register(Consumer<Boolean> listener, Object context) {
+      map.put(listener, context);
+    }
+
+    public static void unregister(Consumer<Boolean> listener) {
+      map.remove(listener);
+    }
+
+    public static void register(Supplier<Boolean> listener, Object context) {
+      map.put(listener, context);
+    }
+
+    public static void unregister(Supplier<Boolean> listener) {
+      map.remove(listener);
+    }
+
+    public static int registrations() {
+      return map.size();
+    }
+
+    public static long consumers() {
+      return map.keySet().stream().filter(k -> k instanceof Consumer).count();
+    }
+
+    public static long suppliers() {
+      return map.keySet().stream().filter(k -> k instanceof Supplier).count();
+    }
+
+    public static Supplier<Boolean> getSupplierFromPlatform() {
+      return supplier;
+    }
+
+    public static Consumer<Boolean> getConsumerFromPlatform() {
+      return consumer;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeAbstractTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeAbstractTests.java
index bc89176..6cf5f77 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeAbstractTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeAbstractTests.java
@@ -176,7 +176,7 @@
       };
   static final String[] RAW_TEMPORAL_SUCCESSES_IF_BRIDGE =
       new String[] {"tck.java.time.TestIsoChronology"};
-  static final String[] RAW_TEMPORAL_SUCCESSES_BUT_12 =
+  static final String[] RAW_TEMPORAL_SUCCESSES_UP_TO_11 =
       new String[] {"test.java.time.temporal.TestIsoWeekFields"};
   static final String[] FORMAT_CHRONO_SUCCESSES =
       new String[] {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeRawTemporalTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeRawTemporalTests.java
index 2b9b95c..aa1f537 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeRawTemporalTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeRawTemporalTests.java
@@ -28,9 +28,9 @@
   @Test
   public void testTime() throws Exception {
     testTime(RAW_TEMPORAL_SUCCESSES);
-    if (!parameters.getDexRuntimeVersion().isEqualTo(Version.V12_0_0)) {
+    if (parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0)) {
       // In 12 some ISO is supported that other versions do not support.
-      testTime(RAW_TEMPORAL_SUCCESSES_BUT_12);
+      testTime(RAW_TEMPORAL_SUCCESSES_UP_TO_11);
     }
     // The bridge is always present with JDK11 due to partial desugaring between 26 and 33.
     // On JDK8 the bridge is absent in between 26 and 33.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
index b58b4e1..3458390 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
@@ -6,35 +6,29 @@
 
 import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
 import static com.android.tools.r8.KotlinTestBase.getCompileMemoizer;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.D8TestRunResult;
-import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
 import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.KotlinTestBase.KotlinCompileMemoizer;
 import com.android.tools.r8.KotlinTestParameters;
-import com.android.tools.r8.R8FullTestBuilder;
-import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.R8TestRunResult;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.kotlin.KotlinMetadataWriter;
-import com.android.tools.r8.kotlin.metadata.KotlinMetadataTestBase;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
-import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import java.io.File;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Paths;
 import java.util.List;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -49,27 +43,72 @@
   private static final String PKG = KotlinMetadataTest.class.getPackage().getName();
   private static final String EXPECTED_OUTPUT = "Wuhuu, my special day is: 1997-8-29-2-14";
 
-  private final TestParameters parameters;
   private final KotlinTestParameters kotlinParameters;
   private final KotlinCompiler kotlinc;
-  private final boolean shrinkDesugaredLibrary;
 
-  @Parameters(name = "{0}, {1}, shrinkDesugaredLibrary: {2}")
+  private final TestParameters parameters;
+  private final CompilationSpecification compilationSpecification;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+
+  @Parameters(name = "{0}, kotlin: {1}, spec: {2}, {3}")
   public static List<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimesAndApiLevels().build(),
         getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
-        BooleanUtils.values());
+        ImmutableList.of(LibraryDesugaringSpecification.JDK11),
+        DEFAULT_SPECIFICATIONS);
   }
 
   public KotlinMetadataTest(
       TestParameters parameters,
       KotlinTestParameters kotlinParameters,
-      boolean shrinkDesugaredLibrary) {
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
     this.parameters = parameters;
     this.kotlinParameters = kotlinParameters;
     this.kotlinc = kotlinParameters.getCompiler();
-    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.compilationSpecification = compilationSpecification;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+  }
+
+  @Test
+  public void test() throws Throwable {
+    if (parameters.getRuntime().isCf()) {
+      testForRuntime(parameters)
+          .addProgramFiles(compiledJars.getForConfiguration(kotlinParameters))
+          .addProgramFiles(kotlinc.getKotlinStdlibJar())
+          .addProgramFiles(kotlinc.getKotlinReflectJar())
+          .run(parameters.getRuntime(), PKG + ".MainKt")
+          .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+      return;
+    }
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addProgramFiles(compiledJars.getForConfiguration(kotlinParameters))
+        .addProgramFiles(kotlinc.getKotlinStdlibJar())
+        .addProgramFiles(kotlinc.getKotlinReflectJar())
+        .applyIf(
+            compilationSpecification.isProgramShrink(),
+            builder -> builder.addProgramFiles(kotlinc.getKotlinAnnotationJar()))
+        .addOptionsModification(
+            options -> {
+              options.testing.enableD8ResourcesPassThrough = true;
+              options.dataResourceConsumer = options.programConsumer.getDataResourceConsumer();
+            })
+        .addKeepMainRule(PKG + ".MainKt")
+        .addKeepAllClassesRule()
+        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .allowDiagnosticMessages()
+        .allowUnusedDontWarnKotlinReflectJvmInternal(
+            kotlinParameters.getCompiler().isNot(KOTLINC_1_3_72))
+        .compile()
+        .inspect(
+            i -> {
+              if (requiresTimeDesugaring(parameters, libraryDesugaringSpecification != JDK8)) {
+                inspectRewrittenMetadata(i);
+              }
+            })
+        .run(parameters.getRuntime(), PKG + ".MainKt")
+        .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
   }
 
   private static KotlinCompileMemoizer compiledJars =
@@ -80,98 +119,6 @@
               DescriptorUtils.getBinaryNameFromJavaType(PKG),
               "Main" + FileUtils.KT_EXTENSION));
 
-  @Test
-  public void testCf() throws Exception {
-    assumeTrue(parameters.getRuntime().isCf());
-    testForRuntime(parameters)
-        .addProgramFiles(compiledJars.getForConfiguration(kotlinParameters))
-        .addProgramFiles(kotlinc.getKotlinStdlibJar())
-        .addProgramFiles(kotlinc.getKotlinReflectJar())
-        .run(parameters.getRuntime(), PKG + ".MainKt")
-        .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
-  }
-
-  @Test
-  public void testTimeD8() throws Exception {
-    assumeTrue(parameters.getRuntime().isDex());
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-    final File output = temp.newFile("output.zip");
-    final D8TestRunResult d8TestRunResult =
-        testForD8()
-            .addLibraryFiles(getLibraryFile())
-            .addProgramFiles(compiledJars.getForConfiguration(kotlinParameters))
-            .addProgramFiles(kotlinc.getKotlinStdlibJar())
-            .addProgramFiles(kotlinc.getKotlinReflectJar())
-            .setProgramConsumer(new ArchiveConsumer(output.toPath(), true))
-            .setMinApi(parameters.getApiLevel())
-            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-            .addOptionsModification(
-                options -> {
-                  options.testing.enableD8ResourcesPassThrough = true;
-                  options.dataResourceConsumer = options.programConsumer.getDataResourceConsumer();
-                })
-            .compile()
-            .addDesugaredCoreLibraryRunClassPath(
-                this::buildDesugaredLibrary,
-                parameters.getApiLevel(),
-                keepRuleConsumer.get(),
-                false)
-            .run(parameters.getRuntime(), PKG + ".MainKt")
-            .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
-    if (requiresTimeDesugaring(parameters)) {
-      d8TestRunResult.inspect(this::inspectRewrittenMetadata);
-    }
-  }
-
-  @Test
-  public void testTimeR8() throws Exception {
-    boolean desugarLibrary = parameters.isDexRuntime() && requiresTimeDesugaring(parameters);
-    final R8FullTestBuilder testBuilder =
-        testForR8(parameters.getBackend())
-            .addLibraryFiles(getLibraryFile())
-            .addProgramFiles(compiledJars.getForConfiguration(kotlinParameters))
-            .addProgramFiles(kotlinc.getKotlinStdlibJar())
-            .addProgramFiles(kotlinc.getKotlinReflectJar())
-            .addProgramFiles(kotlinc.getKotlinAnnotationJar())
-            .addKeepMainRule(PKG + ".MainKt")
-            .addKeepAllClassesRule()
-            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-            .setMinApi(parameters.getApiLevel())
-            .allowDiagnosticMessages()
-            .allowUnusedDontWarnKotlinReflectJvmInternal(
-                kotlinParameters.getCompiler().isNot(KOTLINC_1_3_72));
-    KeepRuleConsumer keepRuleConsumer = null;
-    if (desugarLibrary) {
-      keepRuleConsumer = createKeepRuleConsumer(parameters);
-      testBuilder.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer);
-    }
-    R8TestCompileResult compileResult =
-        testBuilder
-            .compile()
-            .assertNoErrorMessages()
-            // -keepattributes Signature is added in kotlin-reflect from version 1.4.20.
-            .applyIf(
-                kotlinParameters.getCompiler().isNot(KOTLINC_1_3_72),
-                TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation,
-                TestCompileResult::assertNoInfoMessages)
-            .apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib);
-    if (desugarLibrary) {
-      assertNotNull(keepRuleConsumer);
-      compileResult.addDesugaredCoreLibraryRunClassPath(
-          this::buildDesugaredLibrary,
-          parameters.getApiLevel(),
-          keepRuleConsumer.get(),
-          shrinkDesugaredLibrary);
-    }
-    final R8TestRunResult r8TestRunResult =
-        compileResult
-            .run(parameters.getRuntime(), PKG + ".MainKt")
-            .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
-    if (desugarLibrary) {
-      r8TestRunResult.inspect(this::inspectRewrittenMetadata);
-    }
-  }
-
   private void inspectRewrittenMetadata(CodeInspector inspector) {
     final ClassSubject clazz =
         inspector.clazz("com.android.tools.r8.desugar.desugaredlibrary.kotlin.Skynet");
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
index ff80b66..7c98dbe 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.ApiLevelRange;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanTopLevelFlags;
@@ -48,6 +49,8 @@
   public void testMultiLevel() throws IOException {
     Assume.assumeTrue(ToolHelper.isLocalDevelopment());
 
+    LibraryDesugaringSpecification legacySpec = LibraryDesugaringSpecification.JDK8;
+
     LegacyToHumanSpecificationConverter converter =
         new LegacyToHumanSpecificationConverter(Timing.empty());
 
@@ -56,15 +59,11 @@
     MultiAPILevelLegacyDesugaredLibrarySpecification spec =
         new MultiAPILevelLegacyDesugaredLibrarySpecificationParser(
                 options.dexItemFactory(), options.reporter)
-            .parseMultiLevelConfiguration(
-                StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()));
+            .parseMultiLevelConfiguration(StringResource.fromFile(legacySpec.getSpecification()));
 
     MultiAPILevelHumanDesugaredLibrarySpecification humanSpec1 =
         converter.convertAllAPILevels(
-            spec,
-            ToolHelper.getDesugarJDKLibs(),
-            ToolHelper.getAndroidJar(getRequiredCompilationAPILevel()),
-            options);
+            spec, legacySpec.getDesugarJdkLibs(), legacySpec.getLibraryFiles(), options);
 
     Box<String> json = new Box<>();
     MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.export(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/CompilationSpecification.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/CompilationSpecification.java
index 3aecfba..0a8d35b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/CompilationSpecification.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/CompilationSpecification.java
@@ -12,14 +12,17 @@
 import java.util.Set;
 
 public enum CompilationSpecification {
-  D8_L8DEBUG(false, false, false, DEBUG),
-  D8_L8SHRINK(false, true, false, RELEASE),
+  D8_L8DEBUG(false, false, false, false, DEBUG),
+  D8_L8SHRINK(false, true, false, false, RELEASE),
   // In theory no build system uses R8_L8DEBUG, for local debugging only.
-  R8_L8DEBUG(true, false, false, RELEASE),
-  R8_L8SHRINK(true, true, false, RELEASE),
+  R8_L8DEBUG(true, false, false, false, RELEASE),
+  R8_L8SHRINK(true, true, false, false, RELEASE),
   // The D8CFTOCF specifications can run either in CF or be dexed afterwards.
-  D8CF2CF_L8DEBUG(false, false, true, DEBUG),
-  D8CF2CF_L8SHRINK(false, true, true, RELEASE);
+  D8CF2CF_L8DEBUG(false, false, true, false, DEBUG),
+  D8CF2CF_L8SHRINK(false, true, true, true, RELEASE),
+  // Variants with trace reference in dex.
+  D8_L8SHRINK_TR(false, true, false, true, RELEASE),
+  R8_L8SHRINK_TR(true, true, false, true, RELEASE);
 
   public static Set<CompilationSpecification> DEFAULT_SPECIFICATIONS =
       ImmutableSet.of(D8_L8DEBUG, D8_L8SHRINK, R8_L8SHRINK);
@@ -29,13 +32,19 @@
   private final boolean programShrink;
   private final boolean l8Shrink;
   private final boolean cfToCf;
+  private final boolean traceReferences;
   private final CompilationMode programCompilationMode;
 
   CompilationSpecification(
-      boolean programShrink, boolean l8Shrink, boolean cfToCf, CompilationMode mode) {
+      boolean programShrink,
+      boolean l8Shrink,
+      boolean cfToCf,
+      boolean traceReferences,
+      CompilationMode mode) {
     this.programShrink = programShrink;
     this.l8Shrink = l8Shrink;
     this.cfToCf = cfToCf;
+    this.traceReferences = traceReferences;
     this.programCompilationMode = mode;
   }
 
@@ -54,4 +63,8 @@
   public boolean isCfToCf() {
     return cfToCf;
   }
+
+  public boolean isTraceReferences() {
+    return traceReferences;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
index 7e11cc5..e72e4e7 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.test;
 
-import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8CF2CF_L8SHRINK;
-
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.FeatureSplit;
@@ -29,6 +27,7 @@
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
 import com.google.common.base.Charsets;
 import java.io.IOException;
 import java.nio.file.Path;
@@ -188,6 +187,11 @@
     return this;
   }
 
+  public DesugaredLibraryTestBuilder<T> allowUnusedDontWarnKotlinReflectJvmInternal(boolean allow) {
+    withR8TestBuilder(b -> b.allowUnusedDontWarnKotlinReflectJvmInternal(allow));
+    return this;
+  }
+
   public DesugaredLibraryTestBuilder<T> allowDiagnosticInfoMessages() {
     withR8TestBuilder(R8TestBuilder::allowDiagnosticInfoMessages);
     return this;
@@ -198,6 +202,16 @@
     return this;
   }
 
+  public DesugaredLibraryTestBuilder<T> addKeepRules(String keepRules) {
+    withR8TestBuilder(b -> b.addKeepRules(keepRules));
+    return this;
+  }
+
+  public DesugaredLibraryTestBuilder<T> addKeepClassAndMembersRules(Class<?>... clazz) {
+    withR8TestBuilder(b -> b.addKeepClassAndMembersRules(clazz));
+    return this;
+  }
+
   public DesugaredLibraryTestBuilder<T> addKeepAttributes(String... attributes) {
     withR8TestBuilder(b -> b.addKeepAttributes(attributes));
     return this;
@@ -234,16 +248,43 @@
     return this;
   }
 
+  public DesugaredLibraryTestBuilder<T> enableNeverClassInliningAnnotations() {
+    withR8TestBuilder(R8TestBuilder::enableNeverClassInliningAnnotations);
+    return this;
+  }
+
   public DesugaredLibraryTestBuilder<T> enableInliningAnnotations() {
     withR8TestBuilder(R8TestBuilder::enableInliningAnnotations);
     return this;
   }
 
+  public DesugaredLibraryTestBuilder<T> enableNoVerticalClassMergingAnnotations() {
+    withR8TestBuilder(R8TestBuilder::enableNoVerticalClassMergingAnnotations);
+    return this;
+  }
+
+  public DesugaredLibraryTestBuilder<T> addVerticallyMergedClassesInspector(
+      Consumer<VerticallyMergedClassesInspector> inspector) {
+    withR8TestBuilder(b -> b.addVerticallyMergedClassesInspector(inspector));
+    return this;
+  }
+
   public DesugaredLibraryTestBuilder<T> noMinification() {
     withR8TestBuilder(R8TestBuilder::noMinification);
     return this;
   }
 
+  public DesugaredLibraryTestBuilder<T> enableConstantArgumentAnnotations() {
+    withR8TestBuilder(R8TestBuilder::enableConstantArgumentAnnotations);
+    return this;
+  }
+
+  public DesugaredLibraryTestBuilder<T> applyOnBuilder(
+      Consumer<TestCompilerBuilder<?, ?, ?, ?, ?>> consumer) {
+    consumer.accept(builder);
+    return this;
+  }
+
   public DesugaredLibraryTestBuilder<T> applyIf(
       boolean apply, Consumer<DesugaredLibraryTestBuilder<T>> consumer) {
     if (apply) {
@@ -301,13 +342,11 @@
     if (!compilationSpecification.isL8Shrink()) {
       return compileDesugaredLibrary(null);
     }
-    if (!compilationSpecification.isCfToCf()) {
+    if (!compilationSpecification.isTraceReferences()) {
       // When going to dex we can get the generated keep rule through the keep rule consumer.
       assert keepRuleConsumer != null;
       return compileDesugaredLibrary(keepRuleConsumer.get());
     }
-    // In D8CF2CF_L8SHRINK, we use trace reference to extract the keep rules.
-    assert compilationSpecification == D8CF2CF_L8SHRINK;
     L8TestCompileResult nonShrunk =
         test.testForL8(parameters.getApiLevel(), Backend.CF)
             .apply(libraryDesugaringSpecification::configureL8TestBuilder)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
index d4e8e32..65171c1 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
@@ -5,10 +5,10 @@
 
 import static com.android.tools.r8.ToolHelper.DESUGARED_JDK_8_LIB_JAR;
 import static com.android.tools.r8.ToolHelper.DESUGARED_LIB_RELEASES_DIR;
+import static com.android.tools.r8.ToolHelper.UNDESUGARED_JDK_11_LIB_JAR;
 
 import com.android.tools.r8.L8TestBuilder;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.desugar.desugaredlibrary.jdk11.DesugaredLibraryJDK11Undesugarer;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -19,11 +19,6 @@
 
 public class LibraryDesugaringSpecification {
 
-  private static final String RELEASES_DIR = "third_party/openjdk/desugar_jdk_libs_releases/";
-  private static final Path UNDESUGARED_JDK_11_LIB_JAR =
-      DesugaredLibraryJDK11Undesugarer.undesugaredJarJDK11(
-          Paths.get("third_party/openjdk/desugar_jdk_libs_11/desugar_jdk_libs.jar"));
-
   // Main head specifications.
   public static LibraryDesugaringSpecification JDK8 =
       new LibraryDesugaringSpecification(
@@ -74,25 +69,21 @@
       new LibraryDesugaringSpecification("1.1.1", AndroidApiLevel.P);
   public static final LibraryDesugaringSpecification RELEASED_1_1_5 =
       new LibraryDesugaringSpecification("1.1.5", AndroidApiLevel.P);
+
   private final String name;
   private final Set<Path> desugarJdkLibs;
   private final Path specification;
   private final Set<Path> libraryFiles;
   private final String extraKeepRules;
 
-  private LibraryDesugaringSpecification(
+  public LibraryDesugaringSpecification(
       String name, Path desugarJdkLibs, String specificationPath, AndroidApiLevel androidJarLevel) {
     this(
         name,
         ImmutableSet.of(desugarJdkLibs, ToolHelper.DESUGAR_LIB_CONVERSIONS),
         Paths.get("src/library_desugar/" + specificationPath),
-        ToolHelper.getAndroidJar(androidJarLevel));
-  }
-
-  // This can be used to build custom specifications for testing purposes.
-  public LibraryDesugaringSpecification(
-      String name, Set<Path> desugarJdkLibs, Path specification, Path androidJar) {
-    this(name, desugarJdkLibs, specification, ImmutableSet.of(androidJar), "");
+        ImmutableSet.of(ToolHelper.getAndroidJar(androidJarLevel)),
+        "");
   }
 
   // This can be used to build custom specifications for testing purposes.
@@ -116,7 +107,8 @@
             Paths.get(DESUGARED_LIB_RELEASES_DIR, version, "desugar_jdk_libs.jar"),
             Paths.get(DESUGARED_LIB_RELEASES_DIR, version, "desugar_jdk_libs_configuration.jar")),
         Paths.get(DESUGARED_LIB_RELEASES_DIR, version, "desugar.json"),
-        ToolHelper.getAndroidJar(androidJarLevel));
+        ImmutableSet.of(ToolHelper.getAndroidJar(androidJarLevel)),
+        "");
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDex.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDex.java
new file mode 100644
index 0000000..8c5cdd0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDex.java
@@ -0,0 +1,205 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.nestaccesscontrol;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.desugar.nestaccesscontrol.NestAttributesInDex.Host.Member1;
+import com.android.tools.r8.desugar.nestaccesscontrol.NestAttributesInDex.Host.Member2;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class NestAttributesInDex extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
+  }
+
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines(
+          "true", "true", "true", "true", "true", "true", "true", "true", "true", "false", "false",
+          "true", "true", "true", "false", "false", "true", "true", "true", "false", "false",
+          "true", "true", "true");
+
+  @Test
+  public void testRuntime() throws Exception {
+    assumeTrue(
+        parameters.isCfRuntime()
+            && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK11)
+            && parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+    testForJvm()
+        .addProgramClassFileData(getTransformedClasses())
+        .addProgramClasses(OtherHost.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject host = inspector.clazz(Host.class);
+    ClassSubject member1 = inspector.clazz(Member1.class);
+    ClassSubject member2 = inspector.clazz(Member2.class);
+    assertEquals(
+        ImmutableList.of(member1.asTypeSubject(), member2.asTypeSubject()),
+        host.getFinalNestMembersAttribute());
+    assertEquals(host.asTypeSubject(), member1.getFinalNestHostAttribute());
+    assertEquals(host.asTypeSubject(), member2.getFinalNestHostAttribute());
+    ClassSubject otherHost = inspector.clazz(OtherHost.class);
+    assertNull(otherHost.getFinalNestHostAttribute());
+    assertEquals(0, otherHost.getFinalNestMembersAttribute().size());
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedClasses())
+        .addProgramClasses(OtherHost.class)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(
+            options -> {
+              options.emitNestAnnotationsInDex = true;
+            })
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        // No Art versions have support for nest attributes yet.
+        .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+  }
+
+  public Collection<byte[]> getTransformedClasses() throws Exception {
+    ClassFileTransformer transformer =
+        transformer(TestClass.class)
+            .setMinVersion(CfVm.JDK11)
+            .transformMethodInsnInMethod(
+                "main",
+                ((opcode, owner, name, descriptor, isInterface, continuation) -> {
+                  if (owner.equals(DescriptorUtils.getClassBinaryName(AdditionalClassAPIs.class))) {
+                    if (name.equals("getNestMembers")) {
+                      continuation.visitMethodInsn(
+                          Opcodes.INVOKEVIRTUAL,
+                          "java/lang/Class",
+                          "getNestMembers",
+                          "()[Ljava/lang/Class;",
+                          false);
+                    } else if (name.equals("getNestHost")) {
+                      continuation.visitMethodInsn(
+                          Opcodes.INVOKEVIRTUAL,
+                          "java/lang/Class",
+                          "getNestHost",
+                          "()Ljava/lang/Class;",
+                          false);
+                    } else if (name.equals("isNestmateOf")) {
+                      continuation.visitMethodInsn(
+                          Opcodes.INVOKEVIRTUAL,
+                          "java/lang/Class",
+                          "isNestmateOf",
+                          "(Ljava/lang/Class;)Z",
+                          false);
+                    } else {
+                      fail("Unsupported rewriting of API " + owner + "." + name + descriptor);
+                    }
+                  } else {
+                    continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+                  }
+                }));
+
+    return ImmutableList.of(
+        transformer.transform(),
+        withNest(Host.class).transform(),
+        withNest(Member1.class).transform(),
+        withNest(Member2.class).transform());
+  }
+
+  private ClassFileTransformer withNest(Class<?> clazz) throws Exception {
+    return transformer(clazz).setNest(Host.class, Member1.class, Member2.class);
+  }
+
+  static class AdditionalClassAPIs {
+    public static Class<?>[] getNestMembers(Class<?> clazz) {
+      throw new RuntimeException();
+    }
+
+    public static Class<?> getNestHost(Class<?> clazz) {
+      throw new RuntimeException();
+    }
+
+    public static boolean isNestmateOf(Class<?> class1, Class<?> class2) {
+      throw new RuntimeException();
+    }
+  }
+
+  static class TestClass {
+
+    public static boolean sameArrayContent(Class<?>[] array1, Class<?>[] array2) {
+      Set<Class<?>> expected = new HashSet<>(Arrays.asList(array1));
+      for (Class<?> clazz : array2) {
+        if (!expected.remove(clazz)) {
+          return false;
+        }
+      }
+      return expected.isEmpty();
+    }
+
+    public static void main(String[] args) {
+      Class<?>[] nestMembers = new Class<?>[] {Host.class, Member1.class, Member2.class};
+      System.out.println(
+          sameArrayContent(nestMembers, AdditionalClassAPIs.getNestMembers(Host.class)));
+      System.out.println(
+          sameArrayContent(nestMembers, AdditionalClassAPIs.getNestMembers(Member1.class)));
+      System.out.println(
+          sameArrayContent(nestMembers, AdditionalClassAPIs.getNestMembers(Member2.class)));
+      System.out.println(AdditionalClassAPIs.getNestHost(Host.class).equals(Host.class));
+      System.out.println(AdditionalClassAPIs.getNestHost(Member1.class).equals(Host.class));
+      System.out.println(AdditionalClassAPIs.getNestHost(Member2.class).equals(Host.class));
+      for (Class<?> class1 : nestMembers) {
+        for (Class<?> class2 : nestMembers) {
+          System.out.println(AdditionalClassAPIs.isNestmateOf(class1, class2));
+        }
+        System.out.println(AdditionalClassAPIs.isNestmateOf(OtherHost.class, class1));
+        System.out.println(AdditionalClassAPIs.isNestmateOf(class1, OtherHost.class));
+      }
+      System.out.println(AdditionalClassAPIs.getNestHost(OtherHost.class).equals(OtherHost.class));
+      System.out.println(
+          sameArrayContent(
+              new Class<?>[] {OtherHost.class},
+              AdditionalClassAPIs.getNestMembers(OtherHost.class)));
+      System.out.println(AdditionalClassAPIs.isNestmateOf(OtherHost.class, OtherHost.class));
+    }
+  }
+
+  static class OtherHost {}
+
+  static class Host {
+    static class Member1 {}
+
+    static class Member2 {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultMethodInvokeSuperOnDefaultLibraryMethodTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultMethodInvokeSuperOnDefaultLibraryMethodTest.java
new file mode 100644
index 0000000..0cb64c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultMethodInvokeSuperOnDefaultLibraryMethodTest.java
@@ -0,0 +1,175 @@
+// 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.desugaring.interfacemethods;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Consumer;
+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 DefaultMethodInvokeSuperOnDefaultLibraryMethodTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().build();
+  }
+
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("1", "2");
+
+  private boolean runtimeHasConsumerInterface(TestParameters parameters) {
+    // java,util.function.Consumer was introduced at API level 24.
+    return parameters.asDexRuntime().getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
+  }
+
+  @Test
+  public void testD8WithDefaultInterfaceMethodDesugaringWithAPIInLibrary() throws Exception {
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addInnerClasses(getClass())
+        .setMinApi(AndroidApiLevel.I_MR1)
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics
+                    .assertOnlyWarnings()
+                    .assertWarningsMatch(
+                        allOf(
+                            diagnosticType(StringDiagnostic.class),
+                            diagnosticMessage(
+                                containsString(
+                                    "Interface method desugaring has inserted NoSuchMethodError"
+                                        + " replacing a super call in")),
+                            diagnosticMessage(containsString("forEachPrint")))))
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            // If the platform does not have java.util.function.Consumer the lambda instantiation
+            // will throw NoClassDefFoundError as it implements java.util.function.Consumer.
+            // Otherwise, the generated code will throw NoSuchMethodError.
+            runtimeHasConsumerInterface(parameters),
+            b -> b.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+            b -> b.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+  }
+
+  @Test
+  public void testD8WithDefaultInterfaceMethodDesugaringWithoutAPIInLibrary() throws Exception {
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.M))
+        .addInnerClasses(getClass())
+        .setMinApi(AndroidApiLevel.I_MR1)
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics
+                    .assertOnlyWarnings()
+                    .assertWarningsMatch(
+                        diagnosticType(InterfaceDesugarMissingTypeDiagnostic.class)))
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            // If the platform does not have java.util.function.Consumer the lambda instantiation
+            // will throw NoClassDefFoundError as it implements java.util.function.Consumer.
+            // Otherwise, the generated code will throw NoSuchMethodError.
+            runtimeHasConsumerInterface(parameters),
+            b -> b.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+            b -> b.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+  }
+
+  @Test
+  public void testD8WithDefaultInterfaceMethodSupport() throws Exception {
+    assumeTrue(
+        parameters.asDexRuntime().getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addInnerClasses(getClass())
+        .setMinApi(AndroidApiLevel.N)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testR8WithDefaultInterfaceMethodDesugaringWithAPIInLibrary() throws Exception {
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addInnerClasses(getClass())
+        .setMinApi(AndroidApiLevel.I_MR1)
+        .addKeepMainRule(TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+  }
+
+  @Test
+  public void testR8WithDefaultInterfaceMethodDesugaringWithoutAPIInLibrary() throws Exception {
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.M))
+        .addInnerClasses(getClass())
+        .setMinApi(AndroidApiLevel.I_MR1)
+        .addKeepMainRule(TestClass.class)
+        .addDontWarn(Consumer.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            // If the platform does not have java.util.function.Consumer the lambda instantiation
+            // will throw NoClassDefFoundError as it implements java.util.function.Consumer.
+            // Otherwise, the generated code will throw NoSuchMethodError.
+            runtimeHasConsumerInterface(parameters),
+            b -> b.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+            b -> b.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+  }
+
+  @Test
+  public void testR8WithDefaultInterfaceMethodSupport() throws Exception {
+    assumeTrue(
+        parameters.asDexRuntime().getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addInnerClasses(getClass())
+        .setMinApi(AndroidApiLevel.N)
+        .addKeepMainRule(TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  interface IntegerIterable extends Iterable<Integer> {
+    default void forEachPrint() {
+      Iterable.super.forEach(System.out::println);
+    }
+  }
+
+  static class IntegerIterable1And2 implements IntegerIterable {
+
+    @Override
+    public Iterator<Integer> iterator() {
+      List<Integer> result = new ArrayList<>();
+      result.add(1);
+      result.add(2);
+      return result.iterator();
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new IntegerIterable1And2().forEachPrint();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java
index fc4fba0..c412e9f 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java
@@ -16,6 +16,7 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.L8TestCompileResult;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.LibraryDesugaringTestConfiguration.PresentKeepRuleConsumer;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
@@ -119,9 +120,13 @@
         .apply(configuration)
         .setMinApi(getApiLevel())
         .enableCoreLibraryDesugaring(
-            getApiLevel(),
-            keepRuleConsumer,
-            StringResource.fromFile(getDesugaredLibraryConfiguration()))
+            LibraryDesugaringTestConfiguration.builder()
+                .setMinApi(getApiLevel())
+                .setKeepRuleConsumer(keepRuleConsumer)
+                .addDesugaredLibraryConfiguration(
+                    StringResource.fromFile(getDesugaredLibraryConfiguration()))
+                .dontAddRunClasspath()
+                .build())
         .compile()
         .assertAllInfoMessagesMatch(
             anyOf(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java
index 71b7da8..53fa602 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java
@@ -4,16 +4,19 @@
 
 package com.android.tools.r8.ir.optimize.inliner;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.R8_L8DEBUG;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import com.android.tools.r8.LibraryDesugaringTestConfiguration;
-import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 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.google.common.collect.ImmutableList;
 import java.util.Arrays;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -23,25 +26,31 @@
 public class InlineMethodWithRetargetedLibMemberTest extends DesugaredLibraryTestBase {
 
   private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return TestBase.getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        getJdk8Jdk11(),
+        ImmutableList.of(R8_L8DEBUG));
   }
 
-  public InlineMethodWithRetargetedLibMemberTest(TestParameters parameters) {
+  public InlineMethodWithRetargetedLibMemberTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
     this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
   }
 
   @Test
-  public void test() throws Exception {
-    testForR8(parameters.getBackend())
-        .addLibraryFiles(getLibraryFile())
+  public void test() throws Throwable {
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
         .addProgramClasses(TestClass.class)
         .addKeepMainRule(TestClass.class)
-        .enableCoreLibraryDesugaring(
-            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
-        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
             inspector ->
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java
index 57b70d2..198fcde 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java
@@ -121,13 +121,13 @@
               assertThat(inspector.clazz(LibrarySubclass.class), isPresent());
               List<FoundMethodSubject> methods =
                   inspector.clazz(LibrarySubclass.class).allMethods();
-              assertEquals(3, methods.size());
+              assertEquals(4, methods.size());
               assertEquals(
                   1, methods.stream().filter(FoundMethodSubject::isInstanceInitializer).count());
               assertEquals(
                   1, methods.stream().filter(m -> m.getFinalName().contains("main")).count());
               assertEquals(
-                  1, methods.stream().filter(m -> m.getOriginalName().contains("foo")).count());
+                  2, methods.stream().filter(m -> m.getOriginalName().contains("foo")).count());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 61c6fb5..55b84f4 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -215,6 +215,16 @@
   }
 
   @Override
+  public TypeSubject getFinalNestHostAttribute() {
+    throw new Unreachable("Cannot determine NestHost attribute of an absent class");
+  }
+
+  @Override
+  public List<TypeSubject> getFinalNestMembersAttribute() {
+    throw new Unreachable("Cannot determine NestMembers attribute of an absent class");
+  }
+
+  @Override
   public KmClassSubject getKmClass() {
     return null;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index d604156..36a856e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -242,6 +242,10 @@
 
   public abstract String getFinalSignatureAttribute();
 
+  public abstract TypeSubject getFinalNestHostAttribute();
+
+  public abstract List<TypeSubject> getFinalNestMembersAttribute();
+
   public abstract KmClassSubject getKmClass();
 
   public abstract KmPackageSubject getKmPackage();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index fbf91d6..5b0e184 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -22,6 +22,7 @@
 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.NestMemberClassAttribute;
 import com.android.tools.r8.kotlin.KotlinClassMetadataReader;
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
 import com.android.tools.r8.naming.MemberNaming;
@@ -486,6 +487,23 @@
   }
 
   @Override
+  public TypeSubject getFinalNestHostAttribute() {
+    if (dexClass.getNestHost() == null) {
+      return null;
+    }
+    return new TypeSubject(codeInspector, dexClass.getNestHost());
+  }
+
+  @Override
+  public List<TypeSubject> getFinalNestMembersAttribute() {
+    List<TypeSubject> result = new ArrayList<>();
+    for (NestMemberClassAttribute member : dexClass.getNestMembersClassAttributes()) {
+      result.add(new TypeSubject(codeInspector, member.getNestMember()));
+    }
+    return result;
+  }
+
+  @Override
   public int hashCode() {
     int result = codeInspector.hashCode();
     result = 31 * result + dexClass.hashCode();
diff --git a/tools/create_r8lib.py b/tools/create_r8lib.py
index 6f77ba6..c48eaca 100755
--- a/tools/create_r8lib.py
+++ b/tools/create_r8lib.py
@@ -23,30 +23,35 @@
 def parse_options():
   parser = argparse.ArgumentParser(description='Tag R8 Versions')
   parser.add_argument(
-      '--r8jar',
-      required=True,
-      help='The R8 jar to compile')
+    '--classpath',
+    action='append',
+    help='Dependencies to add to classpath')
   parser.add_argument(
-      '--output',
-      required=True,
-      help='The output path for the r8lib')
+    '--debug-agent',
+    action='store_true',
+    default=False,
+    help='Create a socket for debugging')
   parser.add_argument(
-      '--pg-conf',
-      action='append',
-      help='Keep configuration')
+    '--excldeps-variant',
+    action='store_true',
+    default=False,
+    help='Mark this artifact as an "excldeps" variant of the compiler')
   parser.add_argument(
-      '--lib',
-      action='append',
-      help='Additional libraries (JDK 1.8 rt.jar already included)')
+    '--lib',
+    action='append',
+    help='Additional libraries (JDK 1.8 rt.jar already included)')
   parser.add_argument(
-      '--classpath',
-      action='append',
-      help='Dependencies to add to classpath')
+    '--output',
+    required=True,
+    help='The output path for the r8lib')
   parser.add_argument(
-      '--excldeps-variant',
-      action='store_true',
-      default=False,
-      help='Mark this artifact as an "excldeps" variant of the compiler')
+    '--pg-conf',
+    action='append',
+    help='Keep configuration')
+  parser.add_argument(
+    '--r8jar',
+    required=True,
+    help='The R8 jar to compile')
   return parser.parse_args()
 
 def get_r8_version(r8jar):
@@ -82,6 +87,8 @@
   source_file_template = 'R8_%MAP_ID_%MAP_HASH'
   # TODO(b/139725780): See if we can remove or lower the heap size (-Xmx8g).
   cmd = [jdk.GetJavaExecutable(), '-Xmx8g', '-ea']
+  if args.debug_agent:
+    cmd.extend(['-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005'])
   cmd.extend(['-cp', 'build/libs/r8_with_deps.jar', 'com.android.tools.r8.R8'])
   cmd.append(args.r8jar)
   cmd.append('--classfile')