Merge commit '127105d72c0927207be3a56f436ab07a7fb26981' into dev-release
diff --git a/build.gradle b/build.gradle
index 8a6e218..532320f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -374,6 +374,7 @@
                 "openjdk/desugar_jdk_libs_releases/1.1.1",
                 "openjdk/desugar_jdk_libs_releases/1.1.5",
                 "openjdk/jdk-11-test",
+                "opensource-apps/tivi",
                 "proguard/proguard5.2.1",
                 "proguard/proguard6.0.1",
                 "proguard/proguard-7.0.0",
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs.json b/src/library_desugar/jdk11/desugar_jdk_libs.json
index 503352b..2c892cd 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs.json
@@ -13,16 +13,30 @@
       }
     },
     {
+      "api_level_below_or_equal": 32,
+      "rewrite_prefix": {
+        "java.io.DesugarInputStream": "j$.io.DesugarInputStream"
+      },
+      "retarget_method_with_emulated_dispatch": {
+        "long java.io.InputStream#transferTo(java.io.OutputStream)": "java.io.DesugarInputStream"
+      },
+      "amend_library_method": [
+        "public long java.io.InputStream#transferTo(java.io.OutputStream)"
+      ]
+    },
+    {
       "api_level_below_or_equal": 30,
       "rewrite_prefix": {
         "java.time.": "j$.time.",
         "java.util.Desugar": "j$.util.Desugar"
       },
       "retarget_method": {
+        "java.util.TimeZone java.util.TimeZone#getTimeZone(java.time.ZoneId)": "java.util.DesugarTimeZone"
+      },
+      "retarget_method_with_emulated_dispatch": {
         "java.time.Instant java.util.Date#toInstant()": "java.util.DesugarDate",
         "java.time.ZoneId java.util.TimeZone#toZoneId()": "java.util.DesugarTimeZone",
-        "java.time.ZonedDateTime java.util.GregorianCalendar#toZonedDateTime()": "java.util.DesugarGregorianCalendar",
-        "java.util.TimeZone java.util.TimeZone#getTimeZone(java.time.ZoneId)": "java.util.DesugarTimeZone"
+        "java.time.ZonedDateTime java.util.GregorianCalendar#toZonedDateTime()": "java.util.DesugarGregorianCalendar"
       },
       "wrapper_conversion": [
         "java.time.Clock"
@@ -70,7 +84,6 @@
       "retarget_method": {
         "java.util.Spliterator java.util.Arrays#spliterator(java.lang.Object[])": "java.util.DesugarArrays",
         "java.util.Spliterator java.util.Arrays#spliterator(java.lang.Object[], int, int)": "java.util.DesugarArrays",
-        "java.util.Spliterator java.util.LinkedHashSet#spliterator()": "java.util.DesugarLinkedHashSet",
         "java.util.Spliterator$OfDouble java.util.Arrays#spliterator(double[])": "java.util.DesugarArrays",
         "java.util.Spliterator$OfDouble java.util.Arrays#spliterator(double[], int, int)": "java.util.DesugarArrays",
         "java.util.Spliterator$OfInt java.util.Arrays#spliterator(int[])": "java.util.DesugarArrays",
@@ -86,6 +99,9 @@
         "java.util.stream.Stream java.util.Arrays#stream(java.lang.Object[])": "java.util.DesugarArrays",
         "java.util.stream.Stream java.util.Arrays#stream(java.lang.Object[], int, int)": "java.util.DesugarArrays"
       },
+      "retarget_method_with_emulated_dispatch": {
+        "java.util.Spliterator java.util.LinkedHashSet#spliterator()": "java.util.DesugarLinkedHashSet"
+      },
       "wrapper_conversion": [
         "java.util.function.IntUnaryOperator",
         "java.util.function.BiFunction",
@@ -111,7 +127,6 @@
         "java.util.stream.Stream",
         "java.util.function.ObjLongConsumer",
         "java.util.function.ToDoubleFunction",
-        "java.util.Spliterator",
         "java.util.stream.IntStream",
         "java.util.function.LongBinaryOperator",
         "java.util.Spliterator$OfDouble",
@@ -138,6 +153,12 @@
         "java.util.stream.BaseStream",
         "java.util.function.DoublePredicate"
       ],
+      "wrapper_conversion_excluding": {
+        "java.util.Spliterator": [
+          "boolean java.util.Spliterator#hasCharacteristics(int)",
+          "long java.util.Spliterator#getExactSizeIfKnown()"
+        ]
+      },
       "custom_conversion": {
         "java.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatisticsConversions",
         "java.util.IntSummaryStatistics": "java.util.IntSummaryStatisticsConversions",
@@ -255,7 +276,9 @@
         "sun.util.PreHashedMap": "j$.sun.util.PreHashedMap"
       },
       "retarget_method": {
-        "boolean java.util.Arrays#deepEquals0(java.lang.Object, java.lang.Object)": "java.util.DesugarArrays",
+        "boolean java.util.Arrays#deepEquals0(java.lang.Object, java.lang.Object)": "java.util.DesugarArrays"
+      },
+      "retarget_method_with_emulated_dispatch": {
         "java.nio.file.Path java.io.File#toPath()": "java.io.DesugarFile"
       },
       "backport": {
@@ -271,7 +294,6 @@
     {
       "api_level_below_or_equal": 23,
       "rewrite_prefix": {
-        "java.io.DesugarInputStream": "j$.io.DesugarInputStream",
         "java.lang.FunctionalInterface": "j$.lang.FunctionalInterface",
         "java.nio.channels.SeekableByteChannel": "j$.nio.channels.SeekableByteChannel",
         "java.util.AbstractList": "j$.util.AbstractList",
@@ -296,13 +318,7 @@
         "java.util.Optional": {
           "j$.util.Optional": "java.util.Optional"
         }
-      },
-      "retarget_method": {
-        "long java.io.InputStream#transferTo(java.io.OutputStream)": "java.io.DesugarInputStream"
-      },
-      "amend_library_method": [
-        "public long java.io.InputStream#transferTo(java.io.OutputStream)"
-      ]
+      }
     }
   ],
   "shrinker_config": [
@@ -318,6 +334,7 @@
     "-keepattributes Signature",
     "-keepattributes EnclosingMethod",
     "-keepattributes InnerClasses",
-    "-dontwarn sun.misc.Unsafe"
+    "-dontwarn sun.misc.Unsafe",
+    "-dontwarn wrapper.**"
   ]
 }
\ No newline at end of file
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json b/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json
index ab3c8a6..5bf4338 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json
@@ -13,16 +13,30 @@
       }
     },
     {
+      "api_level_below_or_equal": 32,
+      "rewrite_prefix": {
+        "java.io.DesugarInputStream": "j$.io.DesugarInputStream"
+      },
+      "retarget_method_with_emulated_dispatch": {
+        "long java.io.InputStream#transferTo(java.io.OutputStream)": "java.io.DesugarInputStream"
+      },
+      "amend_library_method": [
+        "public long java.io.InputStream#transferTo(java.io.OutputStream)"
+      ]
+    },
+    {
       "api_level_below_or_equal": 30,
       "rewrite_prefix": {
         "java.time.": "j$.time.",
         "java.util.Desugar": "j$.util.Desugar"
       },
       "retarget_method": {
+        "java.util.TimeZone java.util.TimeZone#getTimeZone(java.time.ZoneId)": "java.util.DesugarTimeZone"
+      },
+      "retarget_method_with_emulated_dispatch": {
         "java.time.Instant java.util.Date#toInstant()": "java.util.DesugarDate",
         "java.time.ZoneId java.util.TimeZone#toZoneId()": "java.util.DesugarTimeZone",
-        "java.time.ZonedDateTime java.util.GregorianCalendar#toZonedDateTime()": "java.util.DesugarGregorianCalendar",
-        "java.util.TimeZone java.util.TimeZone#getTimeZone(java.time.ZoneId)": "java.util.DesugarTimeZone"
+        "java.time.ZonedDateTime java.util.GregorianCalendar#toZonedDateTime()": "java.util.DesugarGregorianCalendar"
       },
       "wrapper_conversion": [
         "java.time.Clock"
@@ -73,7 +87,6 @@
       "retarget_method": {
         "java.util.Spliterator java.util.Arrays#spliterator(java.lang.Object[])": "java.util.DesugarArrays",
         "java.util.Spliterator java.util.Arrays#spliterator(java.lang.Object[], int, int)": "java.util.DesugarArrays",
-        "java.util.Spliterator java.util.LinkedHashSet#spliterator()": "java.util.DesugarLinkedHashSet",
         "java.util.Spliterator$OfDouble java.util.Arrays#spliterator(double[])": "java.util.DesugarArrays",
         "java.util.Spliterator$OfDouble java.util.Arrays#spliterator(double[], int, int)": "java.util.DesugarArrays",
         "java.util.Spliterator$OfInt java.util.Arrays#spliterator(int[])": "java.util.DesugarArrays",
@@ -86,10 +99,13 @@
         "java.util.stream.IntStream java.util.Arrays#stream(int[], int, int)": "java.util.DesugarArrays",
         "java.util.stream.LongStream java.util.Arrays#stream(long[])": "java.util.DesugarArrays",
         "java.util.stream.LongStream java.util.Arrays#stream(long[], int, int)": "java.util.DesugarArrays",
-        "java.util.stream.Stream java.io.BufferedReader#lines()": "java.io.DesugarBufferedReader",
         "java.util.stream.Stream java.util.Arrays#stream(java.lang.Object[])": "java.util.DesugarArrays",
         "java.util.stream.Stream java.util.Arrays#stream(java.lang.Object[], int, int)": "java.util.DesugarArrays"
       },
+      "retarget_method_with_emulated_dispatch": {
+        "java.util.stream.Stream java.io.BufferedReader#lines()": "java.io.DesugarBufferedReader",
+        "java.util.Spliterator java.util.LinkedHashSet#spliterator()": "java.util.DesugarLinkedHashSet"
+      },
       "wrapper_conversion": [
         "java.util.function.IntUnaryOperator",
         "java.util.function.BiFunction",
@@ -115,7 +131,6 @@
         "java.util.stream.Stream",
         "java.util.function.ObjLongConsumer",
         "java.util.function.ToDoubleFunction",
-        "java.util.Spliterator",
         "java.util.stream.IntStream",
         "java.util.function.LongBinaryOperator",
         "java.util.Spliterator$OfDouble",
@@ -142,6 +157,12 @@
         "java.util.stream.BaseStream",
         "java.util.function.DoublePredicate"
       ],
+      "wrapper_conversion_excluding": {
+        "java.util.Spliterator": [
+          "boolean java.util.Spliterator#hasCharacteristics(int)",
+          "long java.util.Spliterator#getExactSizeIfKnown()"
+        ]
+      },
       "custom_conversion": {
         "java.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatisticsConversions",
         "java.util.IntSummaryStatistics": "java.util.IntSummaryStatisticsConversions",
@@ -259,7 +280,9 @@
         "sun.util.PreHashedMap": "j$.sun.util.PreHashedMap"
       },
       "retarget_method": {
-        "boolean java.util.Arrays#deepEquals0(java.lang.Object, java.lang.Object)": "java.util.DesugarArrays",
+        "boolean java.util.Arrays#deepEquals0(java.lang.Object, java.lang.Object)": "java.util.DesugarArrays"
+      },
+      "retarget_method_with_emulated_dispatch": {
         "java.nio.file.Path java.io.File#toPath()": "java.io.DesugarFile"
       },
       "backport": {
@@ -275,7 +298,6 @@
     {
       "api_level_below_or_equal": 23,
       "rewrite_prefix": {
-        "java.io.DesugarInputStream": "j$.io.DesugarInputStream",
         "java.lang.FunctionalInterface": "j$.lang.FunctionalInterface",
         "java.nio.channels.SeekableByteChannel": "j$.nio.channels.SeekableByteChannel",
         "java.util.AbstractList": "j$.util.AbstractList",
@@ -300,13 +322,7 @@
         "java.util.Optional": {
           "j$.util.Optional": "java.util.Optional"
         }
-      },
-      "retarget_method": {
-        "long java.io.InputStream#transferTo(java.io.OutputStream)": "java.io.DesugarInputStream"
-      },
-      "amend_library_method": [
-        "public long java.io.InputStream#transferTo(java.io.OutputStream)"
-      ]
+      }
     }
   ],
   "shrinker_config": [
@@ -322,6 +338,7 @@
     "-keepattributes Signature",
     "-keepattributes EnclosingMethod",
     "-keepattributes InnerClasses",
-    "-dontwarn sun.misc.Unsafe"
+    "-dontwarn sun.misc.Unsafe",
+    "-dontwarn wrapper.**"
   ]
 }
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/BackportedMethodList.java b/src/main/java/com/android/tools/r8/BackportedMethodList.java
index befc767..bffd580 100644
--- a/src/main/java/com/android/tools/r8/BackportedMethodList.java
+++ b/src/main/java/com/android/tools/r8/BackportedMethodList.java
@@ -79,6 +79,7 @@
       return;
     }
     InternalOptions options = command.getInternalOptions();
+
     ExecutorService executorService = ThreadUtils.getExecutorService(options);
     try {
       ExceptionUtils.withD8CompilationHandler(
diff --git a/src/main/java/com/android/tools/r8/BackportedMethodListCommand.java b/src/main/java/com/android/tools/r8/BackportedMethodListCommand.java
index 9c11062..c79e8fe 100644
--- a/src/main/java/com/android/tools/r8/BackportedMethodListCommand.java
+++ b/src/main/java/com/android/tools/r8/BackportedMethodListCommand.java
@@ -106,7 +106,7 @@
   InternalOptions getInternalOptions() {
     InternalOptions options = new InternalOptions(factory, getReporter());
     options.setMinApiLevel(AndroidApiLevel.getAndroidApiLevel(minApiLevel));
-    options.setDesugaredLibrarySpecification(desugaredLibrarySpecification, getInputApp());
+    options.setDesugaredLibrarySpecification(desugaredLibrarySpecification);
     return options;
   }
 
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index a522278..b2e0f6d 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexItemFactory;
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 851b677..217a787 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.AssertionUtils.forTesting;
 import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
 
+import com.android.tools.r8.androidapi.ApiReferenceStubber;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.dex.Marker;
@@ -169,9 +170,10 @@
   private static AppView<AppInfo> readApp(
       AndroidApp inputApp, InternalOptions options, ExecutorService executor, Timing timing)
       throws IOException {
-    TypeRewriter typeRewriter = options.getTypeRewriter();
     ApplicationReader applicationReader = new ApplicationReader(inputApp, options, timing);
     LazyLoadedDexApplication app = applicationReader.read(executor);
+    options.loadMachineDesugaredLibrarySpecification(timing, app);
+    TypeRewriter typeRewriter = options.getTypeRewriter();
     AppInfo appInfo = AppInfo.createInitialAppInfo(app, applicationReader.readMainDexClasses(app));
     return AppView.createForD8(appInfo, typeRewriter);
   }
@@ -189,9 +191,6 @@
     }
     Timing timing = Timing.create("D8", options);
     try {
-      // Disable global optimizations.
-      options.disableGlobalOptimizations();
-
       // Synthetic assertion to check that testing assertions works and can be enabled.
       assert forTesting(options, () -> !options.testing.testEnableTestAssertions);
 
@@ -267,7 +266,7 @@
       namingLens = RecordRewritingNamingLens.createRecordRewritingNamingLens(appView, namingLens);
 
       if (options.isGeneratingClassFiles()) {
-        finalizeApplication(inputApp, appView, executor, namingLens);
+        finalizeApplication(appView, executor);
         new CfApplicationWriter(appView, marker, GraphLens.getIdentityLens(), namingLens)
             .write(options.getClassFileConsumer(), inputApp);
       } else {
@@ -310,7 +309,12 @@
                       executor, appView.appInfo().app(), appView.appInfo().getMainDexInfo());
           appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo));
         }
-        finalizeApplication(inputApp, appView, executor, namingLens);
+
+        finalizeApplication(appView, executor);
+
+        if (options.apiModelingOptions().enableStubbingOfClasses && !appView.options().debug) {
+          new ApiReferenceStubber(appView).run(executor);
+        }
 
         new ApplicationWriter(
                 appView,
@@ -332,11 +336,7 @@
     }
   }
 
-  private static void finalizeApplication(
-      AndroidApp inputApp,
-      AppView<AppInfo> appView,
-      ExecutorService executorService,
-      NamingLens namingLens)
+  private static void finalizeApplication(AppView<AppInfo> appView, ExecutorService executorService)
       throws ExecutionException {
     SyntheticFinalization.finalize(appView, executorService);
   }
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index c8923f8..583c6bb 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -7,6 +7,7 @@
 
 import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
@@ -487,18 +488,11 @@
     assert !internal.outline.enabled;
     assert !internal.enableTreeShakingOfLibraryMethodOverrides;
 
-    // TODO(b/187675788): Enable class merging for synthetics in D8.
-    HorizontalClassMergerOptions horizontalClassMergerOptions =
-        internal.horizontalClassMergerOptions();
-    horizontalClassMergerOptions.disable();
-    assert !horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.INITIAL);
-    assert !horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.FINAL);
-
     internal.desugarState = getDesugarState();
     internal.encodeChecksums = getIncludeClassesChecksum();
     internal.dexClassChecksumFilter = getDexClassChecksumFilter();
     internal.enableInheritanceClassInDexDistributor = isOptimizeMultidexForLinearAlloc();
-    internal.setDesugaredLibrarySpecification(desugaredLibrarySpecification, getInputApp());
+    internal.setDesugaredLibrarySpecification(desugaredLibrarySpecification);
     internal.synthesizedClassPrefix = synthesizedClassPrefix;
     internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
 
@@ -519,6 +513,16 @@
       internal.threadCount = getThreadCount();
     }
 
+    // Disable global optimizations.
+    internal.disableGlobalOptimizations();
+
+    // TODO(b/187675788): Enable class merging for synthetics in D8.
+    HorizontalClassMergerOptions horizontalClassMergerOptions =
+        internal.horizontalClassMergerOptions();
+    horizontalClassMergerOptions.disable();
+    assert !horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.INITIAL);
+    assert !horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.FINAL);
+
     internal.setDumpInputFlags(getDumpInputFlags(), skipDump);
     internal.dumpOptions = dumpOptions();
 
@@ -526,7 +530,7 @@
   }
 
   private DumpOptions dumpOptions() {
-    DumpOptions.Builder builder = DumpOptions.builder(Tool.D8);
+    DumpOptions.Builder builder = DumpOptions.builder(Tool.D8).readCurrentSystemProperties();
     dumpBaseCommandOptions(builder);
     return builder
         .setIntermediate(intermediate)
diff --git a/src/main/java/com/android/tools/r8/InputDependencyGraphConsumer.java b/src/main/java/com/android/tools/r8/InputDependencyGraphConsumer.java
new file mode 100644
index 0000000..20b0a30
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/InputDependencyGraphConsumer.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.origin.Origin;
+import java.nio.file.Path;
+
+/** Consumer for receiving file dependencies from inputs. */
+@KeepForSubclassing
+public interface InputDependencyGraphConsumer {
+
+  /**
+   * Callback indicating that file {@code dependency} is referenced from the compiler inputs.
+   *
+   * <p>Note: this callback may be called on multiple threads.
+   *
+   * <p>Note: this callback places no guarantees on order of calls or on duplicate calls.
+   *
+   * @param dependent Origin of input that is has the file {@code dependency}.
+   * @param dependency Path of the dependency.
+   */
+  void accept(Origin dependent, Path dependency);
+
+  /** Callback for proguard `-include` directive. Defaults to call general accept. */
+  default void acceptProguardInclude(Origin dependent, Path dependency) {
+    accept(dependent, dependency);
+  }
+
+  /** Callback for proguard `-injars` directive. Defaults to call general accept. */
+  default void acceptProguardInJars(Origin dependent, Path dependency) {
+    accept(dependent, dependency);
+  }
+
+  /** Callback for proguard `-libraryjars` directive. Defaults to call general accept. */
+  default void acceptProguardLibraryJars(Origin dependent, Path dependency) {
+    accept(dependent, dependency);
+  }
+
+  /** Callback for proguard `-applymapping` directive. Defaults to call general accept. */
+  default void acceptProguardApplyMapping(Origin dependent, Path dependency) {
+    accept(dependent, dependency);
+  }
+
+  /** Callback for proguard `-obfuscationdictionary` directive. Defaults to call general accept. */
+  default void acceptProguardObfuscationDictionary(Origin dependent, Path dependency) {
+    accept(dependent, dependency);
+  }
+
+  /**
+   * Callback for proguard `-classobfuscationdictionary` directive. Defaults to call general accept.
+   */
+  default void acceptProguardClassObfuscationDictionary(Origin dependent, Path dependency) {
+    accept(dependent, dependency);
+  }
+
+  /**
+   * Callback for proguard `-packageobfuscationdictionary` directive. Defaults to call general
+   * accept.
+   */
+  default void acceptProguardPackageObfuscationDictionary(Origin dependent, Path dependency) {
+    accept(dependent, dependency);
+  }
+
+  /**
+   * Callback indicating no more dependencies remain for the active compilation unit.
+   *
+   * <p>Note: this callback places no other guarantees on number of calls or on which threads.
+   */
+  void finished();
+}
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 0c8f9ea..430d811 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -128,8 +128,6 @@
     Timing timing = Timing.create("L8 desugaring", options);
     assert options.cfToCfDesugar;
     try {
-      // Disable global optimizations.
-      options.disableGlobalOptimizations();
       // Since L8 Cf representation is temporary, just disable long running back-end optimizations
       // on it.
       options.enableLoadStoreOptimization = false;
@@ -167,7 +165,7 @@
       throws IOException {
     LazyLoadedDexApplication lazyApp =
         new ApplicationReader(inputApp, options, timing).read(executor);
-
+    options.loadMachineDesugaredLibrarySpecification(timing, lazyApp);
     TypeRewriter typeRewriter = options.getTypeRewriter();
 
     DexApplication app = new L8TreePruner(options).prune(lazyApp, typeRewriter);
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 8678a9d..f4bb9c2 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
@@ -194,7 +195,7 @@
     internal.enableInheritanceClassInDexDistributor = false;
 
     assert desugaredLibrarySpecification != null;
-    internal.setDesugaredLibrarySpecification(desugaredLibrarySpecification, getInputApp());
+    internal.setDesugaredLibrarySpecification(desugaredLibrarySpecification);
     internal.synthesizedClassPrefix =
         desugaredLibrarySpecification.getSynthesizedLibraryClassesPackagePrefix();
 
@@ -213,6 +214,9 @@
       internal.threadCount = getThreadCount();
     }
 
+    // Disable global optimizations.
+    internal.disableGlobalOptimizations();
+
     internal.setDumpInputFlags(getDumpInputFlags(), false);
     internal.dumpOptions = dumpOptions();
 
@@ -442,7 +446,7 @@
   }
 
   private DumpOptions dumpOptions() {
-    DumpOptions.Builder builder = DumpOptions.builder(Tool.L8);
+    DumpOptions.Builder builder = DumpOptions.builder(Tool.L8).readCurrentSystemProperties();
     dumpBaseCommandOptions(builder);
     if (r8Command != null) {
       builder.setProguardConfiguration(r8Command.getInternalOptions().getProguardConfiguration());
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 732a73b..7d177cd 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -293,6 +293,7 @@
       {
         ApplicationReader applicationReader = new ApplicationReader(inputApp, options, timing);
         DirectMappedDexApplication application = applicationReader.read(executorService).toDirect();
+        options.loadMachineDesugaredLibrarySpecification(timing, application);
         MainDexInfo mainDexInfo = applicationReader.readMainDexClassesForR8(application);
 
         // Now that the dex-application is fully loaded, close any internal archive providers.
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 4e72575..05f03d2 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.experimental.startup.StartupConfiguration;
@@ -110,6 +111,7 @@
     private StringConsumer proguardConfigurationConsumer = null;
     private GraphConsumer keptGraphConsumer = null;
     private GraphConsumer mainDexKeptGraphConsumer = null;
+    private InputDependencyGraphConsumer inputDependencyGraphConsumer = null;
     private final List<FeatureSplit> featureSplits = new ArrayList<>();
     private String synthesizedClassPrefix = "";
     private boolean skipDump = false;
@@ -327,6 +329,19 @@
     }
 
     /**
+     * Set a consumer for receiving dependency edges for files referenced from inputs.
+     *
+     * <p>Note that these dependency edges are for files read when reading/parsing other input
+     * files, such as the proguard configuration files. For compilation dependencies for incremental
+     * desugaring see {@code setDesugarGraphConsumer}.
+     */
+    public Builder setInputDependencyGraphConsumer(
+        InputDependencyGraphConsumer inputDependencyGraphConsumer) {
+      this.inputDependencyGraphConsumer = inputDependencyGraphConsumer;
+      return self();
+    }
+
+    /**
      * Set the output path-and-mode.
      *
      * <p>Setting the output path-and-mode will override any previous set consumer or any previous
@@ -479,7 +494,8 @@
           getDesugaredLibraryConfiguration(factory, false);
 
       ProguardConfigurationParser parser =
-          new ProguardConfigurationParser(factory, reporter, allowTestProguardOptions);
+          new ProguardConfigurationParser(
+              factory, reporter, inputDependencyGraphConsumer, allowTestProguardOptions);
       if (!proguardConfigs.isEmpty()) {
         parser.parse(proguardConfigs);
       }
@@ -592,6 +608,10 @@
               getDumpInputFlags(),
               getMapIdProvider(),
               getSourceFileProvider());
+
+      if (inputDependencyGraphConsumer != null) {
+        inputDependencyGraphConsumer.finished();
+      }
       return command;
     }
 
@@ -943,7 +963,7 @@
 
     internal.enableInheritanceClassInDexDistributor = isOptimizeMultidexForLinearAlloc();
 
-    internal.setDesugaredLibrarySpecification(desugaredLibrarySpecification, getInputApp());
+    internal.setDesugaredLibrarySpecification(desugaredLibrarySpecification);
     internal.synthesizedClassPrefix = synthesizedClassPrefix;
     internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
 
@@ -991,7 +1011,7 @@
   }
 
   private DumpOptions dumpOptions() {
-    DumpOptions.Builder builder = DumpOptions.builder(Tool.R8);
+    DumpOptions.Builder builder = DumpOptions.builder(Tool.R8).readCurrentSystemProperties();
     dumpBaseCommandOptions(builder);
     return builder
         .setIncludeDataResources(includeDataResources)
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
index ef86f73..e0852c5 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.androidapi;
 
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DefaultInstanceInitializerCode;
 import com.android.tools.r8.graph.DexClass;
@@ -106,13 +106,13 @@
     }
   }
 
-  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final AppView<?> appView;
   private final Map<DexLibraryClass, Set<DexMethod>> libraryClassesToMock =
       new ConcurrentHashMap<>();
   private final Set<DexType> seenTypes = Sets.newConcurrentHashSet();
   private final AndroidApiLevelCompute apiLevelCompute;
 
-  public ApiReferenceStubber(AppView<? extends AppInfoWithClassHierarchy> appView) {
+  public ApiReferenceStubber(AppView<?> appView) {
     this.appView = appView;
     apiLevelCompute = appView.apiLevelCompute();
   }
@@ -138,10 +138,18 @@
       AppView<AppInfoWithLiveness> appInfoWithLivenessAppView = appView.withLiveness();
       appInfoWithLivenessAppView.setAppInfo(
           appInfoWithLivenessAppView.appInfo().rebuildWithLiveness(committedItems));
-    } else {
+    } else if (appView.hasClassHierarchy()) {
       appView
           .withClassHierarchy()
-          .setAppInfo(appView.appInfo().rebuildWithClassHierarchy(committedItems));
+          .setAppInfo(
+              appView.appInfo().withClassHierarchy().rebuildWithClassHierarchy(committedItems));
+    } else {
+      appView
+          .withoutClassHierarchy()
+          .setAppInfo(
+              new AppInfo(
+                  appView.appInfo().getSyntheticItems().commit(appView.app()),
+                  appView.appInfo().getMainDexInfo()));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
index b313b75..701d275 100644
--- a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
+++ b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
@@ -16,9 +16,11 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.MapIdTemplateProvider;
 import com.android.tools.r8.utils.SourceFileTemplateProvider;
 import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
 
@@ -46,6 +48,7 @@
     public final String mainDexList;
     public final MapIdProvider mapIdProvider;
     public final SourceFileProvider sourceFileProvider;
+    public final String depsFileOutput;
     public final List<String> proguardConfig;
     public boolean printHelpAndExit;
 
@@ -63,6 +66,7 @@
         String mainDexList,
         MapIdProvider mapIdProvider,
         SourceFileProvider sourceFileProvider,
+        String depsFileOutput,
         boolean printHelpAndExit,
         boolean disableVerticalClassMerging) {
       this.output = output;
@@ -75,6 +79,7 @@
       this.proguardConfig = proguardConfig;
       this.mapIdProvider = mapIdProvider;
       this.sourceFileProvider = sourceFileProvider;
+      this.depsFileOutput = depsFileOutput;
       this.printHelpAndExit = printHelpAndExit;
       this.disableVerticalClassMerging = disableVerticalClassMerging;
     }
@@ -91,6 +96,7 @@
       boolean printHelpAndExit = false;
       MapIdProvider mapIdProvider = null;
       SourceFileProvider sourceFileProvider = null;
+      String depsFileOutput = null;
       // Flags to disable experimental features.
       boolean disableVerticalClassMerging = false;
       // These flags are currently ignored.
@@ -134,6 +140,8 @@
               mapIdProvider = MapIdTemplateProvider.create(args[++i], handler);
             } else if (arg.equals("--source-file-template")) {
               sourceFileProvider = SourceFileTemplateProvider.create(args[++i], handler);
+            } else if (arg.equals("--deps-file")) {
+              depsFileOutput = args[++i];
             } else if (arg.equals("--no-vertical-class-merging")) {
               disableVerticalClassMerging = true;
             } else if (arg.equals("--minimal-main-dex")) {
@@ -173,6 +181,7 @@
           mainDexList,
           mapIdProvider,
           sourceFileProvider,
+          depsFileOutput,
           printHelpAndExit,
           disableVerticalClassMerging);
     }
@@ -228,7 +237,13 @@
     if (options.mainDexList != null) {
       builder.addMainDexListFiles(Paths.get(options.mainDexList));
     }
-
+    if (options.depsFileOutput != null) {
+      Path target = Paths.get(options.output);
+      if (!FileUtils.isArchive(target)) {
+        target = target.resolve("classes.dex");
+      }
+      builder.setInputDependencyGraphConsumer(new DepsFileWriter(target, options.depsFileOutput));
+    }
     R8.run(builder.build());
   }
 
diff --git a/src/main/java/com/android/tools/r8/compatproguard/DepsFileWriter.java b/src/main/java/com/android/tools/r8/compatproguard/DepsFileWriter.java
new file mode 100644
index 0000000..194bb42
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/compatproguard/DepsFileWriter.java
@@ -0,0 +1,62 @@
+// 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.compatproguard;
+
+import com.android.tools.r8.InputDependencyGraphConsumer;
+import com.android.tools.r8.origin.Origin;
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class DepsFileWriter implements InputDependencyGraphConsumer {
+
+  private final Path dependentFile;
+  private final String dependencyOutput;
+  private final Set<Path> dependencies = new HashSet<>();
+
+  public DepsFileWriter(Path dependentFile, String dependencyOutput) {
+    this.dependentFile = dependentFile;
+    this.dependencyOutput = dependencyOutput;
+  }
+
+  @Override
+  public void accept(Origin dependent, Path dependency) {
+    dependencies.add(dependency);
+  }
+
+  @Override
+  public void finished() {
+    List<Path> sorted = new ArrayList<>(dependencies);
+    sorted.sort(Path::compareTo);
+    Path output = Paths.get(dependencyOutput);
+    try (Writer writer =
+        Files.newBufferedWriter(
+            output,
+            StandardCharsets.UTF_8,
+            StandardOpenOption.CREATE,
+            StandardOpenOption.TRUNCATE_EXISTING)) {
+      writer.write(escape(dependentFile.toString()));
+      writer.write(":");
+      for (Path path : sorted) {
+        writer.write(" ");
+        writer.write(escape(path.toString()));
+      }
+      writer.write("\n");
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static String escape(String filepath) {
+    return filepath.replace(" ", "\\ ");
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/dump/CompilerDump.java b/src/main/java/com/android/tools/r8/dump/CompilerDump.java
new file mode 100644
index 0000000..a303c14
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/dump/CompilerDump.java
@@ -0,0 +1,56 @@
+// 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.dump;
+
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class CompilerDump {
+
+  private final Path directory;
+
+  public static CompilerDump fromArchive(Path dumpArchive, Path dumpExtractionDirectory)
+      throws IOException {
+    ZipUtils.unzip(dumpArchive, dumpExtractionDirectory);
+    return new CompilerDump(dumpExtractionDirectory);
+  }
+
+  public CompilerDump(Path directory) {
+    this.directory = directory;
+  }
+
+  public Path getProgramArchive() {
+    return directory.resolve("program.jar");
+  }
+
+  public Path getClasspathArchive() {
+    return directory.resolve("classpath.jar");
+  }
+
+  public Path getLibraryArchive() {
+    return directory.resolve("library.jar");
+  }
+
+  public Path getBuildPropertiesFile() {
+    return directory.resolve("build.properties");
+  }
+
+  public Path getProguardConfigFile() {
+    return directory.resolve("proguard.config");
+  }
+
+  public DumpOptions getBuildProperties() throws IOException {
+    if (Files.exists(getBuildPropertiesFile())) {
+      DumpOptions.Builder builder = new DumpOptions.Builder();
+      DumpOptions.parse(
+          FileUtils.readTextFile(getBuildPropertiesFile(), StandardCharsets.UTF_8), builder);
+      return builder.build();
+    }
+    return null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/DumpOptions.java b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
similarity index 72%
rename from src/main/java/com/android/tools/r8/DumpOptions.java
rename to src/main/java/com/android/tools/r8/dump/DumpOptions.java
index 2726086..224d24a 100644
--- a/src/main/java/com/android/tools/r8/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
@@ -1,9 +1,10 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// 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;
+package com.android.tools.r8.dump;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.experimental.startup.StartupConfiguration;
 import com.android.tools.r8.features.FeatureSplitConfiguration;
@@ -12,7 +13,10 @@
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.ThreadUtils;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 
 @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@@ -55,6 +59,8 @@
   private final ProguardConfiguration proguardConfiguration;
   private final List<ProguardConfigurationRule> mainDexKeepRules;
 
+  private final Map<String, String> systemProperties;
+
   // Reporting only.
   private final boolean dumpInputToFile;
 
@@ -74,6 +80,7 @@
       FeatureSplitConfiguration featureSplitConfiguration,
       ProguardConfiguration proguardConfiguration,
       List<ProguardConfigurationRule> mainDexKeepRules,
+      Map<String, String> systemProperties,
       boolean dumpInputToFile) {
     this.tool = tool;
     this.compilationMode = compilationMode;
@@ -90,6 +97,7 @@
     this.featureSplitConfiguration = featureSplitConfiguration;
     this.proguardConfiguration = proguardConfiguration;
     this.mainDexKeepRules = mainDexKeepRules;
+    this.systemProperties = systemProperties;
     this.dumpInputToFile = dumpInputToFile;
   }
 
@@ -112,18 +120,93 @@
     addOptionalDumpEntry(builder, TREE_SHAKING_KEY, treeShaking);
     addOptionalDumpEntry(builder, MINIFICATION_KEY, minification);
     addOptionalDumpEntry(builder, FORCE_PROGUARD_COMPATIBILITY_KEY, forceProguardCompatibility);
-    System.getProperties()
-        .stringPropertyNames()
-        .forEach(
-            name -> {
-              if (name.startsWith("com.android.tools.r8.")) {
-                String value = System.getProperty(name);
-                addDumpEntry(builder, SYSTEM_PROPERTY_PREFIX + name, value);
-              }
-            });
+    ArrayList<String> sortedKeys = new ArrayList<>(systemProperties.keySet());
+    sortedKeys.sort(String::compareTo);
+    sortedKeys.forEach(
+        key -> addDumpEntry(builder, SYSTEM_PROPERTY_PREFIX + key, systemProperties.get(key)));
     return builder.toString();
   }
 
+  public static void parse(String content, DumpOptions.Builder builder) {
+    String[] lines = content.split("\n");
+    for (String line : lines) {
+      String trimmed = line.trim();
+      int i = trimmed.indexOf('=');
+      if (i < 0) {
+        throw new RuntimeException("Invalid dump line. Expected = in line: '" + trimmed + "'");
+      }
+      String key = trimmed.substring(0, i).trim();
+      String value = trimmed.substring(i + 1).trim();
+      parseKeyValue(builder, key, value);
+    }
+  }
+
+  private static void parseKeyValue(Builder builder, String key, String value) {
+    switch (key) {
+      case TOOL_KEY:
+        builder.setTool(Tool.valueOf(value));
+        return;
+      case MODE_KEY:
+        if (value.equals(DEBUG_MODE_VALUE)) {
+          builder.setCompilationMode(CompilationMode.DEBUG);
+        } else if (value.equals(RELEASE_MODE_VALUE)) {
+          builder.setCompilationMode(CompilationMode.RELEASE);
+        } else {
+          parseKeyValueError(key, value);
+        }
+        return;
+      case MIN_API_KEY:
+        builder.setMinApi(Integer.parseInt(value));
+        return;
+      case OPTIMIZE_MULTIDEX_FOR_LINEAR_ALLOC_KEY:
+        builder.setOptimizeMultidexForLinearAlloc(Boolean.parseBoolean(value));
+        return;
+      case THREAD_COUNT_KEY:
+        builder.setThreadCount(Integer.parseInt(value));
+        return;
+      case DESUGAR_STATE_KEY:
+        builder.setDesugarState(DesugarState.valueOf(value));
+        return;
+      case INTERMEDIATE_KEY:
+        builder.setIntermediate(Boolean.parseBoolean(value));
+        return;
+      case INCLUDE_DATA_RESOURCES_KEY:
+        builder.setIncludeDataResources(Optional.of(Boolean.parseBoolean(value)));
+        return;
+      case TREE_SHAKING_KEY:
+        builder.setTreeShaking(Boolean.parseBoolean(value));
+        return;
+      case MINIFICATION_KEY:
+        builder.setMinification(Boolean.parseBoolean(value));
+        return;
+      case FORCE_PROGUARD_COMPATIBILITY_KEY:
+        builder.setForceProguardCompatibility(Boolean.parseBoolean(value));
+        return;
+      default:
+        if (key.startsWith(SYSTEM_PROPERTY_PREFIX)) {
+          builder.setSystemProperty(key.substring(SYSTEM_PROPERTY_PREFIX.length()), value);
+        } else {
+          parseKeyValueError(key, value);
+        }
+    }
+  }
+
+  private static void parseKeyValueError(String key, String value) {
+    throw new RuntimeException("Unknown key value pair: " + key + " = " + value);
+  }
+
+  public Tool getTool() {
+    return tool;
+  }
+
+  public CompilationMode getCompilationMode() {
+    return compilationMode;
+  }
+
+  public int getMinApi() {
+    return minApi;
+  }
+
   private void addOptionalDumpEntry(StringBuilder builder, String key, Optional<?> optionalValue) {
     optionalValue.ifPresent(bool -> addDumpEntry(builder, key, bool));
   }
@@ -169,11 +252,11 @@
   }
 
   public static Builder builder(Tool tool) {
-    return new Builder(tool);
+    return new Builder().setTool(tool);
   }
 
   public static class Builder {
-    private final Tool tool;
+    private Tool tool;
     private CompilationMode compilationMode;
     private int minApi;
     private boolean optimizeMultidexForLinearAlloc;
@@ -190,11 +273,16 @@
     private ProguardConfiguration proguardConfiguration;
     private List<ProguardConfigurationRule> mainDexKeepRules;
 
+    private Map<String, String> systemProperties = new HashMap<>();
+
     // Reporting only.
     private boolean dumpInputToFile;
 
-    public Builder(Tool tool) {
+    public Builder() {}
+
+    public Builder setTool(Tool tool) {
       this.tool = tool;
+      return this;
     }
 
     public Builder setCompilationMode(CompilationMode compilationMode) {
@@ -274,7 +362,26 @@
       return this;
     }
 
+    public Builder setSystemProperty(String key, String value) {
+      this.systemProperties.put(key, value);
+      return this;
+    }
+
+    public Builder readCurrentSystemProperties() {
+      System.getProperties()
+          .stringPropertyNames()
+          .forEach(
+              name -> {
+                if (name.startsWith("com.android.tools.r8.")) {
+                  String value = System.getProperty(name);
+                  setSystemProperty(name, value);
+                }
+              });
+      return this;
+    }
+
     public DumpOptions build() {
+      assert tool != null;
       return new DumpOptions(
           tool,
           compilationMode,
@@ -291,6 +398,7 @@
           featureSplitConfiguration,
           proguardConfiguration,
           mainDexKeepRules,
+          systemProperties,
           dumpInputToFile);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 6f1ab90..5b97ef1 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.ir.optimize.library.LibraryMethodSideEffectModelCollection;
 import com.android.tools.r8.naming.SeedMapper;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
+import com.android.tools.r8.optimize.interfaces.collection.OpenClosedInterfacesCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepClassInfo;
 import com.android.tools.r8.shaking.KeepFieldInfo;
@@ -105,6 +106,8 @@
   private HorizontallyMergedClasses horizontallyMergedClasses = HorizontallyMergedClasses.empty();
   private VerticallyMergedClasses verticallyMergedClasses;
   private EnumDataMap unboxedEnums = null;
+  private OpenClosedInterfacesCollection openClosedInterfacesCollection =
+      OpenClosedInterfacesCollection.getDefault();
   // TODO(b/169115389): Remove
   private Set<DexMethod> cfByteCodePassThrough = ImmutableSet.of();
   private Map<DexType, DexValueString> sourceDebugExtensions = new IdentityHashMap<>();
@@ -610,6 +613,15 @@
     testing().verticallyMergedClassesConsumer.accept(dexItemFactory(), verticallyMergedClasses);
   }
 
+  public OpenClosedInterfacesCollection getOpenClosedInterfacesCollection() {
+    return openClosedInterfacesCollection;
+  }
+
+  public void setOpenClosedInterfacesCollection(
+      OpenClosedInterfacesCollection openClosedInterfacesCollection) {
+    this.openClosedInterfacesCollection = openClosedInterfacesCollection;
+  }
+
   public boolean hasUnboxedEnums() {
     return unboxedEnums != null;
   }
@@ -713,6 +725,8 @@
     if (hasMainDexRootSet()) {
       setMainDexRootSet(mainDexRootSet.withoutPrunedItems(prunedItems));
     }
+    setOpenClosedInterfacesCollection(
+        openClosedInterfacesCollection.withoutPrunedItems(prunedItems));
   }
 
   @SuppressWarnings("unchecked")
@@ -805,6 +819,8 @@
           if (appView.hasMainDexRootSet()) {
             appView.setMainDexRootSet(appView.getMainDexRootSet().rewrittenWithLens(lens));
           }
+          appView.setOpenClosedInterfacesCollection(
+              appView.getOpenClosedInterfacesCollection().rewrittenWithLens(lens));
           if (appView.hasRootSet()) {
             appView.setRootSet(appView.rootSet().rewrittenWithLens(lens));
           }
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 8315b20..547fd3f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -150,21 +150,17 @@
   }
 
   public boolean isAlwaysNull(AppView<AppInfoWithLiveness> appView) {
-    return isAlwaysNull(appView.appInfo());
-  }
-
-  public boolean isAlwaysNull(AppInfoWithLiveness appInfo) {
     if (!isClassType()) {
       return false;
     }
-    DexProgramClass clazz = asProgramClassOrNull(appInfo.definitionFor(this));
+    DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(this));
     if (clazz == null) {
       return false;
     }
-    if (appInfo.options().enableUninstantiatedTypeOptimizationForInterfaces) {
-      return !appInfo.isInstantiatedDirectlyOrIndirectly(clazz);
+    if (clazz.isInterface() && appView.getOpenClosedInterfacesCollection().isMaybeOpen(clazz)) {
+      return false;
     }
-    return !clazz.isInterface() && !appInfo.isInstantiatedDirectlyOrIndirectly(clazz);
+    return !appView.appInfo().isInstantiatedDirectlyOrIndirectly(clazz);
   }
 
   public boolean isSamePackage(DexType other) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java b/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
index b6d9757..bff5528 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
@@ -4,86 +4,39 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
-import com.android.tools.r8.ir.analysis.type.InterfaceCollection;
-import com.android.tools.r8.utils.ObjectUtils;
-import java.util.Iterator;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.google.common.collect.Iterables;
 
 public class DexTypeUtils {
 
   public static DexType computeLeastUpperBound(
       AppView<? extends AppInfoWithClassHierarchy> appView, Iterable<DexType> types) {
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-
-    Iterator<DexType> iterator = types.iterator();
-    assert iterator.hasNext();
-
-    DexType result = iterator.next();
-    while (iterator.hasNext()) {
-      result = computeLeastUpperBound(appView, result, iterator.next());
-    }
-    return result;
+    TypeElement join =
+        TypeElement.join(Iterables.transform(types, type -> type.toTypeElement(appView)), appView);
+    return toDexType(appView, join);
   }
 
-  public static DexType computeLeastUpperBound(
-      AppView<? extends AppInfoWithClassHierarchy> appView, DexType type, DexType other) {
-    if (type == other) {
-      return type;
-    }
-
+  private static DexType toDexType(
+      AppView<? extends AppInfoWithClassHierarchy> appView, TypeElement type) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
-    if (type == dexItemFactory.objectType
-        || other == dexItemFactory.objectType
-        || type.isArrayType() != other.isArrayType()) {
-      return dexItemFactory.objectType;
+    if (type.isPrimitiveType()) {
+      return type.asPrimitiveType().toDexType(dexItemFactory);
     }
-
     if (type.isArrayType()) {
-      assert other.isArrayType();
-      int arrayDimension = type.getNumberOfLeadingSquareBrackets();
-      if (other.getNumberOfLeadingSquareBrackets() != arrayDimension) {
-        return dexItemFactory.objectType;
-      }
-
-      DexType baseType = type.toBaseType(dexItemFactory);
-      DexType otherBaseType = other.toBaseType(dexItemFactory);
-      if (baseType.isPrimitiveType() || otherBaseType.isPrimitiveType()) {
-        assert baseType != otherBaseType;
-        return dexItemFactory.objectType;
-      }
-
-      return dexItemFactory.createArrayType(
-          arrayDimension, computeLeastUpperBound(appView, baseType, otherBaseType));
+      ArrayTypeElement arrayType = type.asArrayType();
+      DexType baseType = toDexType(appView, arrayType.getBaseType());
+      return baseType.toArrayType(arrayType.getNesting(), dexItemFactory);
     }
-
-    assert !type.isArrayType();
-    assert !other.isArrayType();
-
-    boolean isInterface =
-        type.isClassType()
-            && ObjectUtils.getBooleanOrElse(
-                appView.definitionFor(type), DexClass::isInterface, false);
-    boolean otherIsInterface =
-        other.isClassType()
-            && ObjectUtils.getBooleanOrElse(
-                appView.definitionFor(other), DexClass::isInterface, false);
-    if (isInterface != otherIsInterface) {
-      return dexItemFactory.objectType;
+    assert type.isClassType();
+    ClassTypeElement classType = type.asClassType();
+    if (classType.getClassType() != dexItemFactory.objectType) {
+      return classType.getClassType();
     }
-
-    if (isInterface) {
-      assert otherIsInterface;
-      InterfaceCollection interfaceCollection =
-          ClassTypeElement.computeLeastUpperBoundOfInterfaces(
-              appView, InterfaceCollection.singleton(type), InterfaceCollection.singleton(other));
-      return interfaceCollection.hasSingleKnownInterface()
-          ? interfaceCollection.getSingleKnownInterface()
-          : dexItemFactory.objectType;
+    if (classType.getInterfaces().hasSingleKnownInterface()) {
+      return classType.getInterfaces().getSingleKnownInterface();
     }
-
-    assert !isInterface;
-    assert !otherIsInterface;
-
-    return ClassTypeElement.computeLeastUpperBoundOfClasses(appView.appInfo(), type, other);
+    return dexItemFactory.objectType;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index 4b79710..70d534e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -279,6 +279,7 @@
       classMergers.add(
           new ClassMerger.Builder(appView, codeProvider, group, mode).build(lensBuilder));
     }
+    appView.dexItemFactory().clearTypeElementsCache();
     return classMergers;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java
index f60f332..329d43a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.cf.code.CfPosition;
 import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfSafeCheckCast;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -115,7 +116,13 @@
 
     // Assign each field.
     addCfInstructionsForInstanceFieldAssignments(
-        instructionBuilder, instanceFieldAssignmentsPre, argumentToLocalIndex, maxStack, lens);
+        appView,
+        method,
+        instructionBuilder,
+        instanceFieldAssignmentsPre,
+        argumentToLocalIndex,
+        maxStack,
+        lens);
 
     // Load receiver for parent constructor call.
     int stackHeightForParentConstructorCall = 1;
@@ -155,7 +162,13 @@
 
     // Assign each field.
     addCfInstructionsForInstanceFieldAssignments(
-        instructionBuilder, instanceFieldAssignmentsPost, argumentToLocalIndex, maxStack, lens);
+        appView,
+        method,
+        instructionBuilder,
+        instanceFieldAssignmentsPost,
+        argumentToLocalIndex,
+        maxStack,
+        lens);
 
     // Return.
     instructionBuilder.add(new CfReturnVoid());
@@ -174,6 +187,8 @@
   }
 
   private static void addCfInstructionsForInstanceFieldAssignments(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      ProgramMethod method,
       ImmutableList.Builder<CfInstruction> instructionBuilder,
       Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignments,
       int[] argumentToLocalIndex,
@@ -186,8 +201,28 @@
           int stackSizeForInitializationInfo =
               addCfInstructionsForInitializationInfo(
                   instructionBuilder, initializationInfo, argumentToLocalIndex, field.getType());
-          instructionBuilder.add(
-              new CfInstanceFieldWrite(lens.getRenamedFieldSignature(field, lens.getPrevious())));
+          DexField rewrittenField = lens.getRenamedFieldSignature(field, lens.getPrevious());
+
+          // Insert a check to ensure the program continues to type check according to Java type
+          // checking. Otherwise, instance initializer merging may cause open interfaces. If
+          // <init>(A) and <init>(B) both have the behavior `this.i = arg; this.j = arg` where the
+          // type of `i` is I and the type of `j` is J, and both A and B implements I and J, then
+          // the constructors are merged into a single constructor <init>(java.lang.Object), which
+          // is no longer strictly type checking. Note that no choice of parameter type would solve
+          // this.
+          if (initializationInfo.isArgumentInitializationInfo()) {
+            int argumentIndex =
+                initializationInfo.asArgumentInitializationInfo().getArgumentIndex();
+            if (argumentIndex > 0) {
+              DexType argumentType = method.getArgumentType(argumentIndex);
+              if (argumentType.isClassType()
+                  && !appView.appInfo().isSubtype(argumentType, rewrittenField.getType())) {
+                instructionBuilder.add(new CfSafeCheckCast(rewrittenField.getType()));
+              }
+            }
+          }
+
+          instructionBuilder.add(new CfInstanceFieldWrite(rewrittenField));
           maxStack.setMax(stackSizeForInitializationInfo + 1);
         });
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
index 1c3443b..f669473 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
@@ -27,14 +27,15 @@
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.structural.Ordered;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 public class InstanceInitializerMerger {
 
@@ -101,10 +102,6 @@
     return classFileVersion;
   }
 
-  private DexMethod getNewMethodReference(ProgramMethod representative) {
-    return getNewMethodReference(representative, false);
-  }
-
   private DexMethod getNewMethodReference(ProgramMethod representative, boolean needsClassId) {
     DexType[] oldParameters = representative.getParameters().values;
     DexType[] newParameters =
@@ -112,12 +109,15 @@
     System.arraycopy(oldParameters, 0, newParameters, 0, oldParameters.length);
     for (int i = 0; i < oldParameters.length; i++) {
       final int parameterIndex = i;
-      newParameters[i] =
-          DexTypeUtils.computeLeastUpperBound(
-              appView,
-              Iterables.transform(
-                  instanceInitializers,
-                  instanceInitializer -> instanceInitializer.getParameter(parameterIndex)));
+      Set<DexType> parameterTypes =
+          SetUtils.newIdentityHashSet(
+              builder ->
+                  instanceInitializers.forEach(
+                      instanceInitializer ->
+                          builder.accept(instanceInitializer.getParameter(parameterIndex))));
+      if (parameterTypes.size() > 1) {
+        newParameters[i] = DexTypeUtils.computeLeastUpperBound(appView, parameterTypes);
+      }
     }
     if (needsClassId) {
       assert ArrayUtils.last(newParameters) == null;
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 67cc625..080df47 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -293,6 +293,13 @@
       virtuallyMergedMethodsKeepInfo.amendKeepInfo(appView.getKeepInfo(oldMethod));
     }
 
+    // The super method reference is not guaranteed to be rebound to a definition. To ensure correct
+    // lens code rewriting we need to disable proto normalization until lens code rewriting no
+    // longer relies on member rebinding (b/182129249).
+    if (superMethod != null) {
+      virtuallyMergedMethodsKeepInfo.getKeepInfo().disallowParameterReordering();
+    }
+
     // Add a mapping from a synthetic name to the synthetic merged method.
     lensBuilder.recordNewMethodSignature(bridgeMethodReference, newMethodReference);
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index 5d34c84..04b738c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -351,7 +351,7 @@
       }
 
       // Record access.
-      if (field.isProgramField() && appView.appInfo().mayPropagateValueFor(field)) {
+      if (field.isProgramField() && appView.appInfo().mayPropagateValueFor(appView, field)) {
         if (field.getAccessFlags().isStatic() == isStatic) {
           if (isWrite) {
             recordFieldAccessContext(definition, writtenFields, readFields);
@@ -396,7 +396,7 @@
     private void recordAccessThatCannotBeOptimized(
         DexClassAndField field, DexEncodedField definition) {
       constantFields.remove(definition);
-      if (field.isProgramField() && appView.appInfo().mayPropagateValueFor(field)) {
+      if (field.isProgramField() && appView.appInfo().mayPropagateValueFor(appView, field)) {
         destroyFieldAccessContexts(definition);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java b/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java
index 7b80c82..02c35fd 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java
@@ -16,6 +16,8 @@
 import java.util.List;
 import java.util.function.BiConsumer;
 import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 public class InterfaceCollection {
 
@@ -118,7 +120,25 @@
   }
 
   public void forEach(BiConsumer<DexType, Boolean> fn) {
-    interfaces.forEach(fn::accept);
+    interfaces.forEach(fn);
+  }
+
+  public void forEachKnownInterface(Consumer<DexType> consumer) {
+    forEach(
+        (type, isKnown) -> {
+          if (isKnown) {
+            consumer.accept(type);
+          }
+        });
+  }
+
+  public boolean allKnownInterfacesMatch(Predicate<DexType> fn) {
+    for (Entry<DexType> entry : interfaces.reference2BooleanEntrySet()) {
+      if (entry.getBooleanValue() && !fn.test(entry.getKey())) {
+        return false;
+      }
+    }
+    return true;
   }
 
   public boolean anyMatch(BiPredicate<DexType, Boolean> fn) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
index d06fd44..879e5a3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Lists;
 import java.util.ArrayDeque;
 import java.util.Comparator;
@@ -162,25 +163,26 @@
   }
 
   public static DexType getRefinedReceiverType(
-      AppView<? extends AppInfoWithClassHierarchy> appView, InvokeMethodWithReceiver invoke) {
-    return getRefinedReceiverType(appView, invoke.getInvokedMethod(), invoke.getReceiver());
-  }
-
-  public static DexType getRefinedReceiverType(
-      AppView<? extends AppInfoWithClassHierarchy> appView, DexMethod method, Value receiver) {
-    return toRefinedReceiverType(receiver.getDynamicUpperBoundType(appView), method, appView);
+      AppView<AppInfoWithLiveness> appView, InvokeMethodWithReceiver invoke) {
+    return toRefinedReceiverType(
+        invoke.getReceiver().getDynamicType(appView), invoke.getInvokedMethod(), appView);
   }
 
   public static DexType toRefinedReceiverType(
-      TypeElement receiverUpperBoundType,
+      DynamicType dynamicReceiverType,
       DexMethod method,
       AppView<? extends AppInfoWithClassHierarchy> appView) {
-    DexType staticReceiverType = method.holder;
-    if (receiverUpperBoundType.isClassType()) {
-      ClassTypeElement classType = receiverUpperBoundType.asClassType();
-      DexType refinedType = classType.getClassType();
+    DexType staticReceiverType = method.getHolderType();
+    TypeElement staticReceiverTypeElement = staticReceiverType.toTypeElement(appView);
+    TypeElement dynamicReceiverUpperBoundType =
+        dynamicReceiverType.getDynamicUpperBoundType(staticReceiverTypeElement);
+    if (dynamicReceiverUpperBoundType.isClassType()) {
+      ClassTypeElement dynamicReceiverUpperBoundClassType =
+          dynamicReceiverUpperBoundType.asClassType();
+      DexType refinedType = dynamicReceiverUpperBoundClassType.getClassType();
       if (refinedType == appView.dexItemFactory().objectType) {
-        DexType singleKnownInterface = classType.getInterfaces().getSingleKnownInterface();
+        DexType singleKnownInterface =
+            dynamicReceiverUpperBoundClassType.getInterfaces().getSingleKnownInterface();
         if (singleKnownInterface != null) {
           refinedType = singleKnownInterface;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index 864e37e..5e96f46 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -164,6 +164,11 @@
         return true;
       }
     }
+    if (!appView
+        .getOpenClosedInterfacesCollection()
+        .isDefinitelyInstanceOfStaticType(appViewWithLiveness, object())) {
+      return true;
+    }
     TypeElement castType = TypeElement.fromDexType(type, definitelyNotNull(), appView);
     if (object()
         .getDynamicUpperBoundType(appViewWithLiveness)
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index 062220b..f57b79f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -20,8 +20,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 import com.android.tools.r8.ir.analysis.modeling.LibraryMethodReadSetModeling;
-import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
@@ -133,10 +132,7 @@
 
   @Override
   public DexClassAndMethod lookupSingleTarget(
-      AppView<?> appView,
-      ProgramMethod context,
-      TypeElement receiverUpperBoundType,
-      ClassTypeElement receiverLowerBoundType) {
+      AppView<?> appView, ProgramMethod context, DynamicType dynamicReceiverType) {
     DexMethod invokedMethod = getInvokedMethod();
     DexEncodedMethod result;
     if (appView.appInfo().hasLiveness()) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
index 153ca1b..6749695 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.ir.code;
 
 import static com.android.tools.r8.graph.DexEncodedMethod.asDexClassAndMethodOrNull;
-import static com.android.tools.r8.ir.analysis.type.TypeAnalysis.toRefinedReceiverType;
 
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokeInterfaceRange;
@@ -17,8 +16,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
-import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -98,10 +96,7 @@
 
   @Override
   public DexClassAndMethod lookupSingleTarget(
-      AppView<?> appView,
-      ProgramMethod context,
-      TypeElement receiverUpperBoundType,
-      ClassTypeElement receiverLowerBoundType) {
+      AppView<?> appView, ProgramMethod context, DynamicType dynamicReceiverType) {
     if (!appView.appInfo().hasLiveness()) {
       return null;
     }
@@ -110,13 +105,12 @@
         appViewWithLiveness
             .appInfo()
             .lookupSingleVirtualTarget(
+                appViewWithLiveness,
                 getInvokedMethod(),
                 context,
                 true,
                 appView,
-                toRefinedReceiverType(
-                    receiverUpperBoundType, getInvokedMethod(), appViewWithLiveness),
-                receiverLowerBoundType);
+                dynamicReceiverType);
     return asDexClassAndMethodOrNull(result, appView);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 36ec641..d01bcaa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
@@ -37,18 +38,6 @@
     return Iterables.skip(arguments(), 1);
   }
 
-  public boolean hasRefinedReceiverLowerBoundType(AppView<AppInfoWithLiveness> appView) {
-    assert isInvokeMethodWithDynamicDispatch();
-    return getReceiver().getDynamicLowerBoundType(appView) != null;
-  }
-
-  public boolean hasRefinedReceiverUpperBoundType(AppView<AppInfoWithLiveness> appView) {
-    assert isInvokeMethodWithDynamicDispatch();
-    DexType staticReceiverType = getInvokedMethod().holder;
-    DexType refinedReceiverType = TypeAnalysis.getRefinedReceiverType(appView, this);
-    return refinedReceiverType != staticReceiverType;
-  }
-
   @Override
   public boolean isInvokeMethodWithReceiver() {
     return true;
@@ -77,29 +66,20 @@
 
   @Override
   public final DexClassAndMethod lookupSingleTarget(AppView<?> appView, ProgramMethod context) {
-    TypeElement receiverUpperBoundType = null;
-    ClassTypeElement receiverLowerBoundType = null;
-    if (appView.enableWholeProgramOptimizations()) {
-      AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-      receiverUpperBoundType = getReceiver().getDynamicUpperBoundType(appViewWithLiveness);
-      receiverLowerBoundType = getReceiver().getDynamicLowerBoundType(appViewWithLiveness);
-    }
-    return lookupSingleTarget(appView, context, receiverUpperBoundType, receiverLowerBoundType);
+    DynamicType dynamicReceiverType =
+        appView.hasLiveness()
+            ? getReceiver().getDynamicType(appView.withLiveness())
+            : DynamicType.unknown();
+    return lookupSingleTarget(appView, context, dynamicReceiverType);
   }
 
   public abstract DexClassAndMethod lookupSingleTarget(
-      AppView<?> appView,
-      ProgramMethod context,
-      TypeElement receiverUpperBoundType,
-      ClassTypeElement receiverLowerBoundType);
+      AppView<?> appView, ProgramMethod context, DynamicType dynamicReceiverType);
 
   public final ProgramMethod lookupSingleProgramTarget(
-      AppView<?> appView,
-      ProgramMethod context,
-      TypeElement receiverUpperBoundType,
-      ClassTypeElement receiverLowerBoundType) {
+      AppView<?> appView, ProgramMethod context, DynamicType dynamicReceiverType) {
     return DexClassAndMethod.asProgramMethodOrNull(
-        lookupSingleTarget(appView, context, receiverUpperBoundType, receiverLowerBoundType));
+        lookupSingleTarget(appView, context, dynamicReceiverType));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
index 630e177..d41559e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
@@ -14,8 +14,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
-import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -105,10 +104,7 @@
 
   @Override
   public DexClassAndMethod lookupSingleTarget(
-      AppView<?> appView,
-      ProgramMethod context,
-      TypeElement receiverUpperBoundType,
-      ClassTypeElement receiverLowerBoundType) {
+      AppView<?> appView, ProgramMethod context, DynamicType dynamicReceiverType) {
     if (appView.appInfo().hasLiveness() && context != null) {
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       AppInfoWithLiveness appInfo = appViewWithLiveness.appInfo();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index b5933de..d0bca6a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.ir.code;
 
 import static com.android.tools.r8.graph.DexEncodedMethod.asDexClassAndMethodOrNull;
-import static com.android.tools.r8.ir.analysis.type.TypeAnalysis.toRefinedReceiverType;
 
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokeVirtualRange;
@@ -18,8 +17,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
-import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -103,19 +101,14 @@
 
   @Override
   public DexClassAndMethod lookupSingleTarget(
-      AppView<?> appView,
-      ProgramMethod context,
-      TypeElement receiverUpperBoundType,
-      ClassTypeElement receiverLowerBoundType) {
-    return lookupSingleTarget(
-        appView, context, receiverUpperBoundType, receiverLowerBoundType, getInvokedMethod());
+      AppView<?> appView, ProgramMethod context, DynamicType dynamicReceiverType) {
+    return lookupSingleTarget(appView, context, dynamicReceiverType, getInvokedMethod());
   }
 
   public static DexClassAndMethod lookupSingleTarget(
       AppView<?> appView,
       ProgramMethod context,
-      TypeElement receiverUpperBoundType,
-      ClassTypeElement receiverLowerBoundType,
+      DynamicType dynamicReceiverType,
       DexMethod method) {
     DexEncodedMethod result = null;
     if (appView.appInfo().hasLiveness()) {
@@ -124,12 +117,7 @@
           appViewWithLiveness
               .appInfo()
               .lookupSingleVirtualTarget(
-                  method,
-                  context,
-                  false,
-                  appView,
-                  toRefinedReceiverType(receiverUpperBoundType, method, appViewWithLiveness),
-                  receiverLowerBoundType);
+                  appViewWithLiveness, method, context, false, appView, dynamicReceiverType);
     } else {
       // In D8, allow lookupSingleTarget() to be used for finding final library methods. This is
       // used for library modeling.
diff --git a/src/main/java/com/android/tools/r8/ir/code/Pop.java b/src/main/java/com/android/tools/r8/ir/code/Pop.java
index bf8ee2d..01bdc96 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Pop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -53,7 +53,19 @@
 
   @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
-    return other.isPop();
+    if (!other.isPop()) {
+      return false;
+    }
+    Pop pop = other.asPop();
+    if (getFirstOperand().isDefinedByInstructionSatisfying(Instruction::isInitClass)) {
+      InitClass initClass = getFirstOperand().getDefinition().asInitClass();
+      if (!pop.getFirstOperand().isDefinedByInstructionSatisfying(Instruction::isInitClass)) {
+        return false;
+      }
+      InitClass otherInitClass = pop.getFirstOperand().getDefinition().asInitClass();
+      return initClass.getClassValue() == otherInitClass.getClassValue();
+    }
+    return !pop.getFirstOperand().isDefinedByInstructionSatisfying(Instruction::isInitClass);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 0ac2896..9cf8f31 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -98,6 +98,8 @@
 import com.android.tools.r8.naming.IdentifierNameStringMarker;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorIROptimizer;
+import com.android.tools.r8.optimize.interfaces.analysis.OpenClosedInterfacesAnalysis;
+import com.android.tools.r8.optimize.interfaces.analysis.OpenClosedInterfacesAnalysisImpl;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepMethodInfo;
@@ -156,6 +158,7 @@
   private final ServiceLoaderRewriter serviceLoaderRewriter;
   private final EnumValueOptimizer enumValueOptimizer;
   private final EnumUnboxer enumUnboxer;
+  private final OpenClosedInterfacesAnalysis openClosedInterfacesAnalysis;
 
   public final AssumeInserter assumeInserter;
   private final DynamicTypeOptimization dynamicTypeOptimization;
@@ -241,6 +244,7 @@
       this.methodOptimizationInfoCollector = null;
       this.enumValueOptimizer = null;
       this.enumUnboxer = EnumUnboxer.empty();
+      this.openClosedInterfacesAnalysis = OpenClosedInterfacesAnalysis.empty();
       this.assumeInserter = null;
       return;
     }
@@ -274,6 +278,7 @@
       this.memberValuePropagation = new MemberValuePropagation(appViewWithLiveness);
       this.methodOptimizationInfoCollector =
           new MethodOptimizationInfoCollector(appViewWithLiveness, this);
+      this.openClosedInterfacesAnalysis = new OpenClosedInterfacesAnalysisImpl(appViewWithLiveness);
       if (options.isMinifying()) {
         this.identifierNameStringMarker = new IdentifierNameStringMarker(appViewWithLiveness);
       } else {
@@ -305,6 +310,7 @@
       this.methodOptimizationInfoCollector = null;
       this.enumValueOptimizer = null;
       this.enumUnboxer = EnumUnboxer.empty();
+      this.openClosedInterfacesAnalysis = OpenClosedInterfacesAnalysis.empty();
     }
     this.stringSwitchRemover =
         options.isStringSwitchConversionEnabled()
@@ -655,6 +661,7 @@
     appView.withArgumentPropagator(
         argumentPropagator -> argumentPropagator.initializeCodeScanner(executorService, timing));
     enumUnboxer.prepareForPrimaryOptimizationPass(graphLensForPrimaryOptimizationPass);
+    openClosedInterfacesAnalysis.prepareForPrimaryOptimizationPass();
     outliner.prepareForPrimaryOptimizationPass(graphLensForPrimaryOptimizationPass);
 
     if (fieldAccessAnalysis != null) {
@@ -841,6 +848,7 @@
     if (inliner != null) {
       inliner.onLastWaveDone(postMethodProcessorBuilder, executorService, timing);
     }
+    openClosedInterfacesAnalysis.onPrimaryOptimizationPassComplete();
   }
 
   public void addWaveDoneAction(com.android.tools.r8.utils.Action action) {
@@ -1158,6 +1166,8 @@
     assert code.verifyTypes(appView);
     assert code.isConsistentSSA();
 
+    openClosedInterfacesAnalysis.analyze(context, code);
+
     if (shouldPassThrough(context)) {
       // If the code is pass trough, do not finalize by overwriting the existing code.
       assert appView.enableWholeProgramOptimizations();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/IRProcessingCallGraphUseRegistry.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/IRProcessingCallGraphUseRegistry.java
index e20123e..00c9b70 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/IRProcessingCallGraphUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/IRProcessingCallGraphUseRegistry.java
@@ -102,6 +102,12 @@
 
   private void processInitClass(DexType type) {
     DexType rewrittenType = appView.graphLens().lookupType(type);
+    if (rewrittenType.isIntType()) {
+      // Type was unboxed; init-class instruction will be removed by enum unboxer.
+      assert appView.hasUnboxedEnums();
+      assert appView.unboxedEnums().isUnboxedEnum(type);
+      return;
+    }
     DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(rewrittenType));
     if (clazz == null) {
       assert false;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java
index a817cd6..4b15bbc 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java
@@ -85,7 +85,7 @@
       }
     } else {
       ProgramMethod singleTarget =
-          appView.appInfo().lookupSingleProgramTarget(type, method, context, appView);
+          appView.appInfo().lookupSingleProgramTarget(appView, type, method, context, appView);
       if (singleTarget != null) {
         processSingleTarget(singleTarget, context);
       }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 68d48dc..65f5e7d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -100,13 +100,14 @@
   public static List<DexMethod> generateListOfBackportedMethods(
       AndroidApp androidApp, InternalOptions options, ExecutorService executor) throws IOException {
     List<DexMethod> methods = new ArrayList<>();
-    TypeRewriter typeRewriter = options.getTypeRewriter();
     AppInfo appInfo = null;
     if (androidApp != null) {
       DexApplication app =
           new ApplicationReader(androidApp, options, Timing.empty()).read(executor);
+      options.loadMachineDesugaredLibrarySpecification(Timing.empty(), app);
       appInfo = AppInfo.createInitialAppInfo(app);
     }
+    TypeRewriter typeRewriter = options.getTypeRewriter();
     AppView<?> appView = AppView.createForD8(appInfo, typeRewriter);
     BackportedMethodRewriter.RewritableMethods rewritableMethods =
         new BackportedMethodRewriter.RewritableMethods(options, appView);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index a014283..3d91587 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -23,7 +23,6 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodResolutionResult;
@@ -34,14 +33,11 @@
 import com.android.tools.r8.ir.desugar.lambda.ForcefullyMovedLambdaMethodConsumer;
 import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring.DesugarInvoke;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
-import org.objectweb.asm.Opcodes;
 
 /**
  * Represents lambda class generated for a lambda descriptor in context of lambda instantiation
@@ -64,15 +60,11 @@
   public static final String JAVAC_EXPECTED_LAMBDA_METHOD_PREFIX = "lambda$";
   public static final String R8_LAMBDA_ACCESSOR_METHOD_PREFIX = "$r8$lambda$";
 
-  private static final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
-
   final AppView<?> appView;
   final LambdaInstructionDesugaring desugaring;
   public final DexType type;
   public LambdaDescriptor descriptor;
   public final DexMethod constructor;
-  final DexMethod classConstructor;
-  public final DexField lambdaField;
   public final Target target;
 
   // Considered final but is set after due to circularity in allocation.
@@ -99,14 +91,6 @@
 
     this.target = createTarget(accessedFrom);
 
-    boolean stateless = isStateless();
-    this.classConstructor =
-        !stateless
-            ? null
-            : factory.createMethod(type, constructorProto, factory.classConstructorMethodName);
-    this.lambdaField =
-        !stateless ? null : factory.createField(type, type, factory.lambdaInstanceFieldName);
-
     // Synthesize the program class once all fields are set.
     synthesizeLambdaClass(builder, desugarInvoke);
   }
@@ -130,13 +114,12 @@
   private void synthesizeLambdaClass(
       SyntheticProgramClassBuilder builder, DesugarInvoke desugarInvoke) {
     builder.setInterfaces(descriptor.interfaces);
-    synthesizeStaticFields(builder);
     synthesizeInstanceFields(builder);
     synthesizeDirectMethods(builder);
     synthesizeVirtualMethods(builder, desugarInvoke);
   }
 
-  final DexField getCaptureField(int index) {
+  DexField getCaptureField(int index) {
     return appView
         .dexItemFactory()
         .createField(
@@ -145,10 +128,6 @@
             appView.dexItemFactory().createString("f$" + index));
   }
 
-  public final boolean isStateless() {
-    return descriptor.isStateless();
-  }
-
   // Synthesize virtual methods.
   private void synthesizeVirtualMethods(
       SyntheticProgramClassBuilder builder, DesugarInvoke desugarInvoke) {
@@ -193,38 +172,19 @@
 
   // Synthesize direct methods.
   private void synthesizeDirectMethods(SyntheticProgramClassBuilder builder) {
-    boolean stateless = isStateless();
-    List<DexEncodedMethod> methods = new ArrayList<>(stateless ? 2 : 1);
-
     // Constructor.
     MethodAccessFlags accessFlags =
         MethodAccessFlags.fromSharedAccessFlags(
-            (stateless ? Constants.ACC_PRIVATE : Constants.ACC_PUBLIC) | Constants.ACC_SYNTHETIC,
-            true);
-    methods.add(
+            Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, true);
+    DexEncodedMethod method =
         DexEncodedMethod.syntheticBuilder()
             .setMethod(constructor)
             .setAccessFlags(accessFlags)
             .setCode(LambdaConstructorSourceCode.build(this))
             // The api level is computed when tracing.
             .disableAndroidApiLevelCheck()
-            .build());
-
-    // Class constructor for stateless lambda classes.
-    if (stateless) {
-      methods.add(
-          DexEncodedMethod.syntheticBuilder()
-              .setMethod(classConstructor)
-              .setAccessFlags(
-                  MethodAccessFlags.fromSharedAccessFlags(
-                      Constants.ACC_SYNTHETIC | Constants.ACC_STATIC, true))
-              .setCode(LambdaClassConstructorSourceCode.build(this))
-              // The api level is computed when tracing.
-              .disableAndroidApiLevelCheck()
-              .build());
-      feedback.classInitializerMayBePostponed(methods.get(1));
-    }
-    builder.setDirectMethods(methods);
+            .build();
+    builder.setDirectMethods(Collections.singletonList(method));
   }
 
   // Synthesize instance fields to represent captured values.
@@ -247,43 +207,6 @@
     builder.setInstanceFields(fields);
   }
 
-  // Synthesize static fields to represent singleton instance.
-  private void synthesizeStaticFields(SyntheticProgramClassBuilder builder) {
-    if (isStateless()) {
-      // Create instance field for stateless lambda.
-      assert this.lambdaField != null;
-      builder.setStaticFields(
-          Collections.singletonList(
-              DexEncodedField.syntheticBuilder()
-                  .setField(this.lambdaField)
-                  .setAccessFlags(
-                      FieldAccessFlags.fromSharedAccessFlags(
-                          Constants.ACC_PUBLIC
-                              | Constants.ACC_FINAL
-                              | Constants.ACC_SYNTHETIC
-                              | Constants.ACC_STATIC))
-                  .setStaticValue(DexValueNull.NULL)
-                  // The api level is computed when tracing.
-                  .disableAndroidApiLevelCheck()
-                  .build()));
-    }
-  }
-
-  public static int getAsmOpcodeForInvokeType(MethodHandleType type) {
-    switch (type) {
-      case INVOKE_INTERFACE:
-        return Opcodes.INVOKEINTERFACE;
-      case INVOKE_STATIC:
-        return Opcodes.INVOKESTATIC;
-      case INVOKE_DIRECT:
-        return Opcodes.INVOKESPECIAL;
-      case INVOKE_INSTANCE:
-        return Opcodes.INVOKEVIRTUAL;
-      default:
-        throw new Unreachable("Unexpected method handle type: " + type);
-    }
-  }
-
   // Creates a delegation target for this particular lambda class. Note that we
   // should always be able to create targets for the lambdas we support.
   private Target createTarget(ProgramMethod accessedFrom) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
deleted file mode 100644
index ddcb68d..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.desugar;
-
-import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.cf.code.CfNew;
-import com.android.tools.r8.cf.code.CfReturnVoid;
-import com.android.tools.r8.cf.code.CfStackInstruction;
-import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
-import com.android.tools.r8.cf.code.CfStaticFieldWrite;
-import com.android.tools.r8.graph.CfCode;
-import com.google.common.collect.ImmutableList;
-import org.objectweb.asm.Opcodes;
-
-// Source code representing synthesized lambda class constructor.
-// Used for stateless lambdas to instantiate singleton instance.
-final class LambdaClassConstructorSourceCode {
-
-  public static CfCode build(LambdaClass lambda) {
-    int maxStack = 2;
-    int maxLocals = 0;
-    return new CfCode(
-        lambda.type,
-        maxStack,
-        maxLocals,
-        ImmutableList.of(
-            new CfNew(lambda.type),
-            new CfStackInstruction(Opcode.Dup),
-            new CfInvoke(Opcodes.INVOKESPECIAL, lambda.constructor, false),
-            new CfStaticFieldWrite(lambda.lambdaField, lambda.lambdaField),
-            new CfReturnVoid()));
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index bc7a544..e72af99 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -190,15 +190,6 @@
     }
   }
 
-  public Iterable<DexType> getReferencedBaseTypes(DexItemFactory dexItemFactory) {
-    return enforcedProto.getBaseTypes(dexItemFactory);
-  }
-
-  /** Is a stateless lambda, i.e. lambda does not capture any values */
-  final boolean isStateless() {
-    return captures.isEmpty();
-  }
-
   /** Checks if call site needs a accessor when referenced from `accessedFrom`. */
   boolean needsAccessor(ProgramMethod accessedFrom) {
     if (implHandle.type.isInvokeInterface()) {
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 9d3bf12..08b98e6 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
@@ -4,9 +4,9 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary;
 
+import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
@@ -35,8 +35,8 @@
 
   AndroidApiLevel getRequiredCompilationApiLevel();
 
-  MachineDesugaredLibrarySpecification toMachineSpecification(
-      InternalOptions options, AndroidApp app, Timing timing) throws IOException;
+  MachineDesugaredLibrarySpecification toMachineSpecification(DexApplication app, Timing timing)
+      throws IOException;
 
   MachineDesugaredLibrarySpecification toMachineSpecification(
       InternalOptions options, Path library, Timing timing, Path desugaredJDKLib)
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 238ae7b..2a5f49f 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
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification;
 
+import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.HumanToMachineSpecificationConverter;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
@@ -91,8 +91,8 @@
 
   @Override
   public MachineDesugaredLibrarySpecification toMachineSpecification(
-      InternalOptions options, AndroidApp app, Timing timing) throws IOException {
-    return new HumanToMachineSpecificationConverter(timing).convert(this, app, options);
+      DexApplication app, Timing timing) {
+    return new HumanToMachineSpecificationConverter(timing).convert(this, app);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
index e52cc02..2eb550b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.Sets;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
@@ -26,6 +27,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
 import java.util.function.Consumer;
 
 public class HumanDesugaredLibrarySpecificationParser {
@@ -43,9 +45,12 @@
 
   static final String API_LEVEL_BELOW_OR_EQUAL_KEY = "api_level_below_or_equal";
   static final String WRAPPER_CONVERSION_KEY = "wrapper_conversion";
+  static final String WRAPPER_CONVERSION_EXCLUDING_KEY = "wrapper_conversion_excluding";
   static final String CUSTOM_CONVERSION_KEY = "custom_conversion";
   static final String REWRITE_PREFIX_KEY = "rewrite_prefix";
   static final String RETARGET_METHOD_KEY = "retarget_method";
+  static final String RETARGET_METHOD_EMULATED_DISPATCH_KEY =
+      "retarget_method_with_emulated_dispatch";
   static final String REWRITE_DERIVED_PREFIX_KEY = "rewrite_derived_prefix";
   static final String EMULATE_INTERFACE_KEY = "emulate_interface";
   static final String DONT_REWRITE_KEY = "dont_rewrite";
@@ -248,6 +253,14 @@
             stringDescriptorToDexType(retarget.getValue().getAsString()));
       }
     }
+    if (jsonFlagSet.has(RETARGET_METHOD_EMULATED_DISPATCH_KEY)) {
+      for (Map.Entry<String, JsonElement> retarget :
+          jsonFlagSet.get(RETARGET_METHOD_EMULATED_DISPATCH_KEY).getAsJsonObject().entrySet()) {
+        builder.retargetMethodEmulatedDispatch(
+            parseMethod(retarget.getKey()),
+            stringDescriptorToDexType(retarget.getValue().getAsString()));
+      }
+    }
     if (jsonFlagSet.has(BACKPORT_KEY)) {
       for (Map.Entry<String, JsonElement> backport :
           jsonFlagSet.get(BACKPORT_KEY).getAsJsonObject().entrySet()) {
@@ -277,6 +290,14 @@
         builder.addWrapperConversion(stringDescriptorToDexType(wrapper.getAsString()));
       }
     }
+    if (jsonFlagSet.has(WRAPPER_CONVERSION_EXCLUDING_KEY)) {
+      for (Map.Entry<String, JsonElement> wrapper :
+          jsonFlagSet.get(WRAPPER_CONVERSION_EXCLUDING_KEY).getAsJsonObject().entrySet()) {
+        builder.addWrapperConversion(
+            stringDescriptorToDexType(wrapper.getKey()),
+            parseMethods(wrapper.getValue().getAsJsonArray()));
+      }
+    }
     if (jsonFlagSet.has(DONT_REWRITE_KEY)) {
       JsonArray dontRewrite = jsonFlagSet.get(DONT_REWRITE_KEY).getAsJsonArray();
       for (JsonElement rewrite : dontRewrite) {
@@ -298,6 +319,14 @@
     }
   }
 
+  private Set<DexMethod> parseMethods(JsonArray array) {
+    Set<DexMethod> methods = Sets.newIdentityHashSet();
+    for (JsonElement method : array) {
+      methods.add(parseMethod(method.getAsString()));
+    }
+    return methods;
+  }
+
   private DexMethod parseMethod(String signature) {
     methodParser.parseMethod(signature);
     return methodParser.getMethod();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
index ece6fe0..9eb3ed7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
@@ -14,6 +14,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Sets.SetView;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.Map;
@@ -26,11 +27,12 @@
   private final Map<String, Map<String, String>> rewriteDerivedPrefix;
   private final Map<DexType, DexType> emulatedInterfaces;
   private final Map<DexMethod, DexType> retargetMethod;
+  private final Map<DexMethod, DexType> retargetMethodEmulatedDispatch;
   private final Map<DexType, DexType> legacyBackport;
   private final Map<DexType, DexType> customConversions;
   private final Set<DexMethod> dontRewriteInvocation;
   private final Set<DexType> dontRetarget;
-  private final Set<DexType> wrapperConversions;
+  private final Map<DexType, Set<DexMethod>> wrapperConversions;
   private final Map<DexMethod, MethodAccessFlags> amendLibraryMethod;
 
   HumanRewritingFlags(
@@ -38,16 +40,18 @@
       Map<String, Map<String, String>> rewriteDerivedPrefix,
       Map<DexType, DexType> emulateLibraryInterface,
       Map<DexMethod, DexType> retargetMethod,
+      Map<DexMethod, DexType> retargetMethodEmulatedDispatch,
       Map<DexType, DexType> legacyBackport,
       Map<DexType, DexType> customConversion,
       Set<DexMethod> dontRewriteInvocation,
       Set<DexType> dontRetarget,
-      Set<DexType> wrapperConversion,
+      Map<DexType, Set<DexMethod>> wrapperConversion,
       Map<DexMethod, MethodAccessFlags> amendLibraryMethod) {
     this.rewritePrefix = rewritePrefix;
     this.rewriteDerivedPrefix = rewriteDerivedPrefix;
     this.emulatedInterfaces = emulateLibraryInterface;
     this.retargetMethod = retargetMethod;
+    this.retargetMethodEmulatedDispatch = retargetMethodEmulatedDispatch;
     this.legacyBackport = legacyBackport;
     this.customConversions = customConversion;
     this.dontRewriteInvocation = dontRewriteInvocation;
@@ -64,9 +68,10 @@
         ImmutableMap.of(),
         ImmutableMap.of(),
         ImmutableMap.of(),
+        ImmutableMap.of(),
         ImmutableSet.of(),
         ImmutableSet.of(),
-        ImmutableSet.of(),
+        ImmutableMap.of(),
         ImmutableMap.of());
   }
 
@@ -82,6 +87,7 @@
         rewriteDerivedPrefix,
         emulatedInterfaces,
         retargetMethod,
+        retargetMethodEmulatedDispatch,
         legacyBackport,
         customConversions,
         dontRewriteInvocation,
@@ -106,6 +112,10 @@
     return retargetMethod;
   }
 
+  public Map<DexMethod, DexType> getRetargetMethodEmulatedDispatch() {
+    return retargetMethodEmulatedDispatch;
+  }
+
   public Map<DexType, DexType> getLegacyBackport() {
     return legacyBackport;
   }
@@ -122,7 +132,7 @@
     return dontRetarget;
   }
 
-  public Set<DexType> getWrapperConversions() {
+  public Map<DexType, Set<DexMethod>> getWrapperConversions() {
     return wrapperConversions;
   }
 
@@ -146,11 +156,12 @@
     private final Map<String, Map<String, String>> rewriteDerivedPrefix;
     private final Map<DexType, DexType> emulatedInterfaces;
     private final Map<DexMethod, DexType> retargetMethod;
+    private final Map<DexMethod, DexType> retargetMethodEmulatedDispatch;
     private final Map<DexType, DexType> legacyBackport;
     private final Map<DexType, DexType> customConversions;
     private final Set<DexMethod> dontRewriteInvocation;
     private final Set<DexType> dontRetarget;
-    private final Set<DexType> wrapperConversions;
+    private final Map<DexType, Set<DexMethod>> wrapperConversions;
     private final Map<DexMethod, MethodAccessFlags> amendLibraryMethod;
 
     Builder(Reporter reporter, Origin origin) {
@@ -163,9 +174,10 @@
           new IdentityHashMap<>(),
           new IdentityHashMap<>(),
           new IdentityHashMap<>(),
+          new IdentityHashMap<>(),
           Sets.newIdentityHashSet(),
           Sets.newIdentityHashSet(),
-          Sets.newIdentityHashSet(),
+          new IdentityHashMap<>(),
           new IdentityHashMap<>());
     }
 
@@ -175,27 +187,28 @@
         Map<String, String> rewritePrefix,
         Map<String, Map<String, String>> rewriteDerivedPrefix,
         Map<DexType, DexType> emulateLibraryInterface,
-        Map<DexMethod, DexType> retargetCoreLibMember,
+        Map<DexMethod, DexType> retargetMethod,
+        Map<DexMethod, DexType> retargetMethodEmulatedDispatch,
         Map<DexType, DexType> backportCoreLibraryMember,
         Map<DexType, DexType> customConversions,
         Set<DexMethod> dontRewriteInvocation,
         Set<DexType> dontRetargetLibMember,
-        Set<DexType> wrapperConversions,
+        Map<DexType, Set<DexMethod>> wrapperConversions,
         Map<DexMethod, MethodAccessFlags> amendLibrary) {
       this.reporter = reporter;
       this.origin = origin;
       this.rewritePrefix = new HashMap<>(rewritePrefix);
       this.rewriteDerivedPrefix = new HashMap<>(rewriteDerivedPrefix);
       this.emulatedInterfaces = new IdentityHashMap<>(emulateLibraryInterface);
-      this.retargetMethod = new IdentityHashMap<>(retargetCoreLibMember);
+      this.retargetMethod = new IdentityHashMap<>(retargetMethod);
+      this.retargetMethodEmulatedDispatch = new IdentityHashMap<>(retargetMethodEmulatedDispatch);
       this.legacyBackport = new IdentityHashMap<>(backportCoreLibraryMember);
       this.customConversions = new IdentityHashMap<>(customConversions);
       this.dontRewriteInvocation = Sets.newIdentityHashSet();
       this.dontRewriteInvocation.addAll(dontRewriteInvocation);
       this.dontRetarget = Sets.newIdentityHashSet();
       this.dontRetarget.addAll(dontRetargetLibMember);
-      this.wrapperConversions = Sets.newIdentityHashSet();
-      this.wrapperConversions.addAll(wrapperConversions);
+      this.wrapperConversions = new IdentityHashMap<>(wrapperConversions);
       this.amendLibraryMethod = new IdentityHashMap<>(amendLibrary);
     }
 
@@ -255,7 +268,11 @@
     }
 
     public Builder addWrapperConversion(DexType dexType) {
-      wrapperConversions.add(dexType);
+      return addWrapperConversion(dexType, Collections.emptySet());
+    }
+
+    public Builder addWrapperConversion(DexType dexType, Set<DexMethod> excludedMethods) {
+      wrapperConversions.put(dexType, excludedMethods);
       return this;
     }
 
@@ -268,6 +285,15 @@
       return this;
     }
 
+    public Builder retargetMethodEmulatedDispatch(DexMethod key, DexType rewrittenType) {
+      put(
+          retargetMethodEmulatedDispatch,
+          key,
+          rewrittenType,
+          HumanDesugaredLibrarySpecificationParser.RETARGET_METHOD_EMULATED_DISPATCH_KEY);
+      return this;
+    }
+
     public Builder putLegacyBackport(DexType backportType, DexType rewrittenBackportType) {
       put(
           legacyBackport,
@@ -299,16 +325,18 @@
           ImmutableMap.copyOf(rewriteDerivedPrefix),
           ImmutableMap.copyOf(emulatedInterfaces),
           ImmutableMap.copyOf(retargetMethod),
+          ImmutableMap.copyOf(retargetMethodEmulatedDispatch),
           ImmutableMap.copyOf(legacyBackport),
           ImmutableMap.copyOf(customConversions),
           ImmutableSet.copyOf(dontRewriteInvocation),
           ImmutableSet.copyOf(dontRetarget),
-          ImmutableSet.copyOf(wrapperConversions),
+          ImmutableMap.copyOf(wrapperConversions),
           ImmutableMap.copyOf(amendLibraryMethod));
     }
 
     private void validate() {
-      SetView<DexType> dups = Sets.intersection(customConversions.keySet(), wrapperConversions);
+      SetView<DexType> dups =
+          Sets.intersection(customConversions.keySet(), wrapperConversions.keySet());
       if (!dups.isEmpty()) {
         throw reporter.fatalError(
             new StringDiagnostic(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.java
index afc7dd6..c059c27 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.java
@@ -96,6 +96,11 @@
         commonBuilder::retargetMethod,
         builder::retargetMethod);
     deduplicateFlags(
+        flags.getRetargetMethodEmulatedDispatch(),
+        otherFlags.getRetargetMethodEmulatedDispatch(),
+        commonBuilder::retargetMethodEmulatedDispatch,
+        builder::retargetMethodEmulatedDispatch);
+    deduplicateFlags(
         flags.getLegacyBackport(),
         otherFlags.getLegacyBackport(),
         commonBuilder::putLegacyBackport,
@@ -116,15 +121,31 @@
         otherFlags.getDontRetarget(),
         commonBuilder::addDontRetargetLibMember,
         builder::addDontRetargetLibMember);
-    deduplicateFlags(
-        flags.getWrapperConversions(),
-        otherFlags.getWrapperConversions(),
-        commonBuilder::addWrapperConversion,
-        builder::addWrapperConversion);
+
+    deduplicateWrapperFlags(flags, otherFlags, commonBuilder, builder);
 
     deduplicateAmendLibraryMemberFlags(flags, otherFlags, commonBuilder, builder);
   }
 
+  private static void deduplicateWrapperFlags(
+      HumanRewritingFlags flags,
+      HumanRewritingFlags otherFlags,
+      HumanRewritingFlags.Builder commonBuilder,
+      HumanRewritingFlags.Builder builder) {
+    Map<DexType, Set<DexMethod>> other = otherFlags.getWrapperConversions();
+    flags
+        .getWrapperConversions()
+        .forEach(
+            (wrapperType, excludedMethods) -> {
+              if (other.containsKey(wrapperType)) {
+                assert excludedMethods.equals(other.get(wrapperType));
+                commonBuilder.addWrapperConversion(wrapperType, excludedMethods);
+              } else {
+                builder.addWrapperConversion(wrapperType, excludedMethods);
+              }
+            });
+  }
+
   private static void deduplicateAmendLibraryMemberFlags(
       HumanRewritingFlags flags,
       HumanRewritingFlags otherFlags,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java
index 97749d6..928eeab 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java
@@ -18,12 +18,14 @@
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.LIBRARY_FLAGS_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.PROGRAM_FLAGS_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.REQUIRED_COMPILATION_API_LEVEL_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.RETARGET_METHOD_EMULATED_DISPATCH_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.RETARGET_METHOD_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.REWRITE_DERIVED_PREFIX_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.REWRITE_PREFIX_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.SHRINKER_CONFIG_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.SYNTHESIZED_LIBRARY_CLASSES_PACKAGE_PREFIX_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.WRAPPER_CONVERSION_EXCLUDING_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.WRAPPER_CONVERSION_KEY;
 
 import com.android.tools.r8.DiagnosticsHandler;
@@ -33,7 +35,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
-import com.google.common.collect.Sets;
 import com.google.gson.Gson;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import java.util.ArrayList;
@@ -107,6 +108,11 @@
       if (!flags.getRetargetMethod().isEmpty()) {
         toJson.put(RETARGET_METHOD_KEY, mapToString(flags.getRetargetMethod()));
       }
+      if (!flags.getRetargetMethodEmulatedDispatch().isEmpty()) {
+        toJson.put(
+            RETARGET_METHOD_EMULATED_DISPATCH_KEY,
+            mapToString(flags.getRetargetMethodEmulatedDispatch()));
+      }
       if (!flags.getDontRetarget().isEmpty()) {
         toJson.put(DONT_RETARGET_KEY, setToString(flags.getDontRetarget()));
       }
@@ -114,7 +120,7 @@
         toJson.put(BACKPORT_KEY, mapToString(flags.getLegacyBackport()));
       }
       if (!flags.getWrapperConversions().isEmpty()) {
-        toJson.put(WRAPPER_CONVERSION_KEY, setToString(flags.getWrapperConversions()));
+        registerWrapperConversions(toJson, flags.getWrapperConversions());
       }
       if (!flags.getCustomConversions().isEmpty()) {
         toJson.put(CUSTOM_CONVERSION_KEY, mapToString(flags.getCustomConversions()));
@@ -127,15 +133,31 @@
     return list;
   }
 
-  private Set<String> amendLibraryToString(Map<DexMethod, MethodAccessFlags> amendLibraryMembers) {
-    Set<String> stringSet = Sets.newHashSet();
+  private void registerWrapperConversions(
+      Map<String, Object> toJson, Map<DexType, Set<DexMethod>> wrapperConversions) {
+    List<String> stringSet = new ArrayList<>();
+    Map<String, List<String>> stringMap = new TreeMap<>();
+    wrapperConversions.forEach(
+        (k, v) -> {
+          if (v.isEmpty()) {
+            stringSet.add(toString(k));
+          } else {
+            stringMap.put(toString(k), setToString(v));
+          }
+        });
+    toJson.put(WRAPPER_CONVERSION_KEY, stringSet);
+    toJson.put(WRAPPER_CONVERSION_EXCLUDING_KEY, stringMap);
+  }
+
+  private List<String> amendLibraryToString(Map<DexMethod, MethodAccessFlags> amendLibraryMembers) {
+    List<String> stringSet = new ArrayList<>();
     amendLibraryMembers.forEach(
         (member, flags) -> stringSet.add(flags.toString() + " " + toString(member)));
     return stringSet;
   }
 
-  private Set<String> setToString(Set<? extends DexItem> set) {
-    Set<String> stringSet = Sets.newHashSet();
+  private List<String> setToString(Set<? extends DexItem> set) {
+    List<String> stringSet = new ArrayList<>();
     set.forEach(e -> stringSet.add(toString(e)));
     return stringSet;
   }
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 3daf522..01fda4a 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
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
@@ -13,7 +14,6 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.LegacyToHumanSpecificationConverter;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
@@ -119,10 +119,10 @@
 
   @Override
   public MachineDesugaredLibrarySpecification toMachineSpecification(
-      InternalOptions options, AndroidApp app, Timing timing) throws IOException {
+      DexApplication app, Timing timing) throws IOException {
     return new LegacyToHumanSpecificationConverter(timing)
-        .convert(this, app, options)
-        .toMachineSpecification(options, app, timing);
+        .convert(this, app)
+        .toMachineSpecification(app, timing);
   }
 
   @Override
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 45f9dae..0df69b0 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
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion;
 
-import com.android.tools.r8.ClassFileResourceProvider;
-import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.utils.AndroidApp;
@@ -17,27 +15,8 @@
 import java.util.concurrent.ExecutorService;
 
 public class AppForSpecConversion {
-  static DexApplication readApp(
-      AndroidApp inputApp, InternalOptions options, boolean libraryCompilation, Timing timing)
-      throws IOException {
-    timing.begin("Read App");
-    AndroidApp.Builder builder = AndroidApp.builder();
-    for (ClassFileResourceProvider classFileResourceProvider :
-        inputApp.getLibraryResourceProviders()) {
-      builder.addLibraryResourceProvider(classFileResourceProvider);
-    }
-    if (libraryCompilation) {
-      for (ProgramResourceProvider programResourceProvider :
-          inputApp.getProgramResourceProviders()) {
-        builder.addProgramResourceProvider(programResourceProvider);
-      }
-    }
-    DexApplication app = internalReadApp(builder.build(), options, timing);
-    timing.end();
-    return app;
-  }
 
-  static DexApplication readAppForTesting(
+  public static DexApplication readAppForTesting(
       Path desugaredJDKLib,
       Path androidLib,
       InternalOptions options,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineEmulatedInterfaceConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineEmulatedInterfaceConverter.java
index 198fbc2..182202b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineEmulatedInterfaceConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineEmulatedInterfaceConverter.java
@@ -93,7 +93,8 @@
     List<DexType> subInterfaces = emulatedInterfaceHierarchy.get(method.getHolderType());
     LinkedHashMap<DexType, DerivedMethod> extraDispatchCases = new LinkedHashMap<>();
     // Retarget core lib emulated dispatch handled as part of emulated interface dispatch.
-    Map<DexMethod, DexType> retargetCoreLibMember = rewritingFlags.getRetargetMethod();
+    Map<DexMethod, DexType> retargetCoreLibMember =
+        rewritingFlags.getRetargetMethodEmulatedDispatch();
     for (DexMethod retarget : retargetCoreLibMember.keySet()) {
       if (retarget.match(method)) {
         DexClass inClass = appInfo.definitionFor(retarget.getHolderType());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
index e0ae560..87c03e3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -22,6 +23,7 @@
   private final AppInfoWithClassHierarchy appInfo;
   private final MachineRewritingFlags.Builder builder;
   private final String synthesizedPrefix;
+  private final boolean libraryCompilation;
   private final Map<DexString, DexString> descriptorPrefix;
   private final Map<DexString, Map<DexString, DexString>> descriptorDifferentPrefix;
   private final Set<DexString> usedPrefix = Sets.newIdentityHashSet();
@@ -29,11 +31,12 @@
   public HumanToMachinePrefixConverter(
       AppInfoWithClassHierarchy appInfo,
       MachineRewritingFlags.Builder builder,
-      String synthesizedPrefix,
+      HumanDesugaredLibrarySpecification humanSpec,
       HumanRewritingFlags rewritingFlags) {
     this.appInfo = appInfo;
     this.builder = builder;
-    this.synthesizedPrefix = synthesizedPrefix;
+    this.synthesizedPrefix = humanSpec.getSynthesizedLibraryClassesPackagePrefix();
+    this.libraryCompilation = humanSpec.isLibraryCompilation();
     this.descriptorPrefix = convertRewritePrefix(rewritingFlags.getRewritePrefix());
     this.descriptorDifferentPrefix =
         convertRewriteDifferentPrefix(rewritingFlags.getRewriteDerivedPrefix());
@@ -43,9 +46,10 @@
       HumanRewritingFlags rewritingFlags, BiConsumer<String, Set<DexString>> warnConsumer) {
     rewriteClasses();
     rewriteValues(rewritingFlags.getRetargetMethod());
+    rewriteValues(rewritingFlags.getRetargetMethodEmulatedDispatch());
     rewriteValues(rewritingFlags.getCustomConversions());
     rewriteEmulatedInterface(rewritingFlags.getEmulatedInterfaces());
-    rewriteRetargetKeys(rewritingFlags.getRetargetMethod());
+    rewriteRetargetKeys(rewritingFlags.getRetargetMethodEmulatedDispatch());
     warnIfUnusedPrefix(warnConsumer);
   }
 
@@ -88,7 +92,9 @@
 
   private void rewriteClasses() {
     appInfo.app().forEachLibraryType(this::registerClassType);
-    appInfo.app().forEachProgramType(this::registerClassType);
+    if (libraryCompilation) {
+      appInfo.app().forEachProgramType(this::registerClassType);
+    }
   }
 
   private void registerClassType(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java
index a5a27aa..6db6518 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java
@@ -38,13 +38,16 @@
       BiConsumer<String, Set<? extends DexReference>> warnConsumer) {
     rewritingFlags
         .getRetargetMethod()
+        .forEach((method, type) -> convertRetargetMethod(builder, method, type));
+    rewritingFlags
+        .getRetargetMethodEmulatedDispatch()
         .forEach(
             (method, type) ->
-                convertRetargetCoreLibMemberFlag(builder, rewritingFlags, method, type));
+                convertRetargetMethodEmulatedDispatch(builder, rewritingFlags, method, type));
     warnConsumer.accept("Cannot retarget missing methods: ", missingMethods);
   }
 
-  private void convertRetargetCoreLibMemberFlag(
+  private void convertRetargetMethodEmulatedDispatch(
       MachineRewritingFlags.Builder builder,
       HumanRewritingFlags rewritingFlags,
       DexMethod method,
@@ -56,14 +59,53 @@
       return;
     }
     if (foundMethod.isStatic()) {
+      appInfo
+          .app()
+          .options
+          .reporter
+          .error("Cannot generate emulated dispatch for static method " + foundMethod);
+      return;
+    }
+    if (!seemsToNeedEmulatedDispatch(holder, foundMethod)) {
+      appInfo
+          .app()
+          .options
+          .reporter
+          .warning(
+              "Generating (seemingly unnecessary) emulated dispatch for final method "
+                  + foundMethod);
+    }
+    convertEmulatedVirtualRetarget(builder, rewritingFlags, foundMethod, type);
+  }
+
+  private void convertRetargetMethod(
+      MachineRewritingFlags.Builder builder, DexMethod method, DexType type) {
+    DexClass holder = appInfo.definitionFor(method.holder);
+    DexEncodedMethod foundMethod = holder.lookupMethod(method);
+    if (foundMethod == null) {
+      missingMethods.add(method);
+      return;
+    }
+    if (foundMethod.isStatic()) {
       convertStaticRetarget(builder, foundMethod, type);
       return;
     }
-    if (holder.isFinal() || foundMethod.isFinal()) {
-      convertNonEmulatedVirtualRetarget(builder, foundMethod, type);
-      return;
+    if (seemsToNeedEmulatedDispatch(holder, foundMethod)) {
+      appInfo
+          .app()
+          .options
+          .reporter
+          .warning(
+              "Retargeting non final method "
+                  + foundMethod
+                  + " which could lead to invalid runtime execution in overrides.");
     }
-    convertEmulatedVirtualRetarget(builder, rewritingFlags, foundMethod, type);
+    convertNonEmulatedVirtualRetarget(builder, foundMethod, type);
+  }
+
+  private boolean seemsToNeedEmulatedDispatch(DexClass holder, DexEncodedMethod method) {
+    assert !method.isStatic();
+    return !(holder.isFinal() || method.isFinal());
   }
 
   private void convertEmulatedVirtualRetarget(
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 c0bcd82..5ea225d 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
@@ -21,7 +21,6 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineTopLevelFlags;
-import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.Timing;
@@ -43,14 +42,6 @@
     this.timing = timing;
   }
 
-  public MachineDesugaredLibrarySpecification convert(
-      HumanDesugaredLibrarySpecification humanSpec, AndroidApp inputApp, InternalOptions options)
-      throws IOException {
-    DexApplication app =
-        AppForSpecConversion.readApp(inputApp, options, humanSpec.isLibraryCompilation(), timing);
-    return convert(humanSpec, app);
-  }
-
   public MachineDesugaredLibrarySpecification convertForTesting(
       HumanDesugaredLibrarySpecification humanSpec,
       Path desugaredJDKLib,
@@ -63,7 +54,7 @@
     return convert(humanSpec, app);
   }
 
-  private MachineDesugaredLibrarySpecification convert(
+  public MachineDesugaredLibrarySpecification convert(
       HumanDesugaredLibrarySpecification humanSpec, DexApplication app) {
     timing.begin("Human to machine convert");
     reporter = app.options.reporter;
@@ -72,9 +63,7 @@
         app,
         humanSpec.isLibraryCompilation(),
         humanSpec.getTopLevelFlags().getRequiredCompilationAPILevel());
-    MachineRewritingFlags machineRewritingFlags =
-        convertRewritingFlags(
-            humanSpec.getSynthesizedLibraryClassesPackagePrefix(), humanSpec.getRewritingFlags());
+    MachineRewritingFlags machineRewritingFlags = convertRewritingFlags(humanSpec);
     MachineTopLevelFlags topLevelFlags = convertTopLevelFlags(humanSpec.getTopLevelFlags());
     timing.end();
     return new MachineDesugaredLibrarySpecification(
@@ -92,8 +81,9 @@
   }
 
   private MachineRewritingFlags convertRewritingFlags(
-      String synthesizedPrefix, HumanRewritingFlags rewritingFlags) {
+      HumanDesugaredLibrarySpecification humanSpec) {
     timing.begin("convert rewriting flags");
+    HumanRewritingFlags rewritingFlags = humanSpec.getRewritingFlags();
     MachineRewritingFlags.Builder builder = MachineRewritingFlags.builder();
     DesugaredLibraryAmender.run(
         rewritingFlags.getAmendLibraryMethod(), appInfo, reporter, ComputedApiLevel.unknown());
@@ -102,7 +92,7 @@
         .convertRetargetFlags(rewritingFlags, builder, this::warnMissingReferences);
     new HumanToMachineEmulatedInterfaceConverter(appInfo)
         .convertEmulatedInterfaces(rewritingFlags, appInfo, builder, this::warnMissingReferences);
-    new HumanToMachinePrefixConverter(appInfo, builder, synthesizedPrefix, rewritingFlags)
+    new HumanToMachinePrefixConverter(appInfo, builder, humanSpec, rewritingFlags)
         .convertPrefixFlags(rewritingFlags, this::warnMissingDexString);
     new HumanToMachineWrapperConverter(appInfo)
         .convertWrappers(rewritingFlags, builder, this::warnMissingReferences);
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 815cb45..9487210 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
@@ -33,25 +33,29 @@
       HumanRewritingFlags rewritingFlags,
       MachineRewritingFlags.Builder builder,
       BiConsumer<String, Set<? extends DexReference>> warnConsumer) {
-    for (DexType wrapperType : rewritingFlags.getWrapperConversions()) {
-      DexClass wrapperClass = appInfo.definitionFor(wrapperType);
-      if (wrapperClass == null) {
-        missingClasses.add(wrapperType);
-        continue;
-      }
-      List<DexMethod> methods;
-      if (wrapperClass.isEnum()) {
-        methods = ImmutableList.of();
-      } else {
-        methods = allImplementedMethods(wrapperClass);
-        methods.sort(DexMethod::compareTo);
-      }
-      builder.addWrapper(wrapperType, methods);
-    }
+    rewritingFlags
+        .getWrapperConversions()
+        .forEach(
+            (wrapperType, excludedMethods) -> {
+              DexClass wrapperClass = appInfo.definitionFor(wrapperType);
+              if (wrapperClass == null) {
+                missingClasses.add(wrapperType);
+                return;
+              }
+              List<DexMethod> methods;
+              if (wrapperClass.isEnum()) {
+                methods = ImmutableList.of();
+              } else {
+                methods = allImplementedMethods(wrapperClass, excludedMethods);
+                methods.sort(DexMethod::compareTo);
+              }
+              builder.addWrapper(wrapperType, methods);
+            });
     warnConsumer.accept("The following types to wrap are missing: ", missingClasses);
   }
 
-  private List<DexMethod> allImplementedMethods(DexClass wrapperClass) {
+  private List<DexMethod> allImplementedMethods(
+      DexClass wrapperClass, Set<DexMethod> excludedMethods) {
     LinkedList<DexClass> workList = new LinkedList<>();
     List<DexMethod> implementedMethods = new ArrayList<>();
     workList.add(wrapperClass);
@@ -60,13 +64,15 @@
       for (DexEncodedMethod virtualMethod : dexClass.virtualMethods()) {
         if (!virtualMethod.isPrivateMethod()) {
           assert virtualMethod.isProtectedMethod() || virtualMethod.isPublicMethod();
-          boolean alreadyAdded = false;
+          boolean alreadyAdded = excludedMethods.contains(virtualMethod.getReference());
           // This looks quadratic but given the size of the collections met in practice for
           // desugared libraries (Max ~15) it does not matter.
-          for (DexMethod alreadyImplementedMethod : implementedMethods) {
-            if (alreadyImplementedMethod.match(virtualMethod.getReference())) {
-              alreadyAdded = true;
-              break;
+          if (!alreadyAdded) {
+            for (DexMethod alreadyImplementedMethod : implementedMethods) {
+              if (alreadyImplementedMethod.match(virtualMethod.getReference())) {
+                alreadyAdded = true;
+                break;
+              }
             }
           }
           if (!alreadyAdded) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
index 47ef9e3..bd26b562 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
@@ -30,7 +30,6 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.MultiAPILevelLegacyDesugaredLibrarySpecificationParser;
 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 com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.Timing;
@@ -100,14 +99,6 @@
     return humanSpec;
   }
 
-  public HumanDesugaredLibrarySpecification convert(
-      LegacyDesugaredLibrarySpecification legacySpec, AndroidApp inputApp, InternalOptions options)
-      throws IOException {
-    DexApplication app =
-        AppForSpecConversion.readApp(inputApp, options, legacySpec.isLegacy(), timing);
-    return convert(legacySpec, app, options);
-  }
-
   public HumanDesugaredLibrarySpecification convertForTesting(
       LegacyDesugaredLibrarySpecification legacySpec,
       Path desugaredJDKLib,
@@ -117,12 +108,11 @@
     DexApplication app =
         AppForSpecConversion.readAppForTesting(
             desugaredJDKLib, androidLib, options, legacySpec.isLibraryCompilation(), timing);
-    return convert(legacySpec, app, options);
+    return convert(legacySpec, app);
   }
 
   public HumanDesugaredLibrarySpecification convert(
-      LegacyDesugaredLibrarySpecification legacySpec, DexApplication app, InternalOptions options)
-      throws IOException {
+      LegacyDesugaredLibrarySpecification legacySpec, DexApplication app) throws IOException {
     timing.begin("Legacy to Human convert");
     LibraryValidator.validate(
         app,
@@ -136,7 +126,7 @@
     Origin origin = Origin.unknown();
     HumanRewritingFlags humanRewritingFlags =
         convertRewritingFlags(legacySpec.getRewritingFlags(), app, origin);
-    if (options.getMinApiLevel().isLessThanOrEqualTo(LEGACY_HACK_LEVEL)
+    if (app.options.getMinApiLevel().isLessThanOrEqualTo(LEGACY_HACK_LEVEL)
         && legacySpec.isLibraryCompilation()) {
       timing.begin("Legacy hacks");
       HumanRewritingFlags.Builder builder =
@@ -271,7 +261,15 @@
           List<DexClassAndMethod> methodsWithName =
               findMethodsWithName(name, dexClass, builder, app);
           for (DexClassAndMethod dexClassAndMethod : methodsWithName) {
-            builder.retargetMethod(dexClassAndMethod.getReference(), rewrittenType);
+            DexEncodedMethod definition = dexClassAndMethod.getDefinition();
+            if (definition.isStatic()
+                || definition.isFinal()
+                || dexClassAndMethod.getHolder().isFinal()) {
+              builder.retargetMethod(dexClassAndMethod.getReference(), rewrittenType);
+            } else {
+              builder.retargetMethodEmulatedDispatch(
+                  dexClassAndMethod.getReference(), rewrittenType);
+            }
           }
         });
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
index 33f7037..da7d679 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
@@ -42,6 +42,7 @@
 import com.google.common.collect.ImmutableSet;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -408,11 +409,15 @@
 
   // We introduce forwarding methods only once all desugaring has been performed to avoid
   // confusing the look-up with inserted forwarding methods.
-  public final void finalizeProcessing(InterfaceProcessingDesugaringEventConsumer eventConsumer) {
+  public void finalizeProcessing(InterfaceProcessingDesugaringEventConsumer eventConsumer) {
     newSyntheticMethods.forEach(
         (clazz, newForwardingMethods) -> {
-          clazz.addVirtualMethods(newForwardingMethods.toDefinitionSet());
-          newForwardingMethods.forEach(eventConsumer::acceptForwardingMethod);
+          List<ProgramMethod> sorted = new ArrayList<>(newForwardingMethods.toCollection());
+          sorted.sort(Comparator.comparing(ProgramMethod::getReference));
+          for (ProgramMethod method : sorted) {
+            clazz.addVirtualMethod(method.getDefinition());
+            eventConsumer.acceptForwardingMethod(method);
+          }
         });
     newExtraInterfaceSignatures.forEach(
         (clazz, extraInterfaceSignatures) -> {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
index 4c45086..e305984 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.cf.code.CfNew;
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
-import com.android.tools.r8.cf.code.CfStaticFieldRead;
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
@@ -32,7 +31,6 @@
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.Box;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
 import java.util.Collection;
@@ -121,11 +119,6 @@
 
     eventConsumer.acceptLambdaClass(lambdaClass, context);
 
-    if (lambdaClass.isStateless()) {
-      return ImmutableList.of(
-          new CfStaticFieldRead(lambdaClass.lambdaField, lambdaClass.lambdaField));
-    }
-
     DexTypeList captureTypes = lambdaClass.descriptor.captures;
     Deque<CfInstruction> replacement = new ArrayDeque<>(3 + captureTypes.size() * 2);
     replacement.add(new CfNew(lambdaClass.getType()));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index cd28d57..204b1a6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1496,6 +1496,12 @@
       }
     }
 
+    if (!appView
+        .getOpenClosedInterfacesCollection()
+        .isDefinitelyInstanceOfStaticType(appViewWithLiveness, inValue)) {
+      return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
+    }
+
     // If the in-value is `null` and the cast-type is a float-array type, then trivial check-cast
     // elimination may lead to verification errors. See b/123269162.
     if (options.canHaveArtCheckCastVerifierBug()) {
@@ -1609,6 +1615,12 @@
     }
 
     Value inValue = instanceOf.value();
+    if (!appView
+        .getOpenClosedInterfacesCollection()
+        .isDefinitelyInstanceOfStaticType(appViewWithLiveness, inValue)) {
+      return false;
+    }
+
     TypeElement inType = inValue.getType();
     TypeElement instanceOfType =
         TypeElement.fromDexType(instanceOf.type(), inType.nullability(), appView);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 4ad92d7..9490667 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -206,13 +206,17 @@
   }
 
   public boolean satisfiesRequirementsForSimpleInlining(InvokeMethod invoke, ProgramMethod target) {
-    // If we are looking for a simple method, only inline if actually simple.
-    Code code = target.getDefinition().getCode();
-    int instructionLimit =
-        inlinerOptions.getSimpleInliningInstructionLimit()
-            + getInliningInstructionLimitIncrement(invoke, target);
-    if (code.estimatedSizeForInliningAtMost(instructionLimit)) {
-      return true;
+    // Code size modified by inlining, so only read for non-concurrent methods.
+    boolean deterministic = !methodProcessor.isProcessedConcurrently(target);
+    if (deterministic) {
+      // If we are looking for a simple method, only inline if actually simple.
+      Code code = target.getDefinition().getCode();
+      int instructionLimit =
+          inlinerOptions.getSimpleInliningInstructionLimit()
+              + getInliningInstructionLimitIncrement(invoke, target);
+      if (code.estimatedSizeForInliningAtMost(instructionLimit)) {
+        return true;
+      }
     }
     // Even if the inlinee is big it may become simple after inlining. We therefore check if the
     // inlinee's simple inlining constraint is satisfied by the invoke.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 27a4893..2ef8dd2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.DominatorTree;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -93,10 +92,10 @@
             InvokeVirtual devirtualizedInvoke = devirtualizedCall.get(origin.asInvokeInterface());
 
             // Extract the newly added check-cast instruction, if any.
-            CheckCast newCheckCast = null;
+            SafeCheckCast newCheckCast = null;
             Value newReceiver = devirtualizedInvoke.getReceiver();
-            if (!newReceiver.isPhi() && newReceiver.definition.isCheckCast()) {
-              CheckCast definition = newReceiver.definition.asCheckCast();
+            if (!newReceiver.isPhi() && newReceiver.definition.isSafeCheckCast()) {
+              SafeCheckCast definition = newReceiver.definition.asSafeCheckCast();
               if (newCheckCastInstructions.contains(definition)) {
                 newCheckCast = definition;
               }
@@ -136,8 +135,7 @@
                   InvokeVirtual.lookupSingleTarget(
                       appView,
                       context,
-                      invoke.getReceiver().getDynamicUpperBoundType(appView),
-                      invoke.getReceiver().getDynamicLowerBoundType(appView),
+                      invoke.getReceiver().getDynamicType(appView),
                       invokedMethod);
               if (newSingleTarget != null
                   && newSingleTarget.getReference() == singleTarget.getReference()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index b4584c4..08edc70 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -124,7 +124,7 @@
 
   private boolean mayPropagateValueFor(DexClassAndField field) {
     if (field.isProgramField()) {
-      return appView.appInfo().mayPropagateValueFor(field.getReference());
+      return appView.appInfo().mayPropagateValueFor(appView, field.getReference());
     }
     return appView.appInfo().assumedValues.containsKey(field.getReference())
         || appView.appInfo().noSideEffects.containsKey(field.getReference());
@@ -132,7 +132,7 @@
 
   private boolean mayPropagateValueFor(DexClassAndMethod method) {
     if (method.isProgramMethod()) {
-      return appView.appInfo().mayPropagateValueFor(method.getReference());
+      return appView.appInfo().mayPropagateValueFor(appView, method.getReference());
     }
     return appView.appInfo().assumedValues.containsKey(method.getReference())
         || appView.appInfo().noSideEffects.containsKey(method.getReference());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
index 715dfdd..d758988 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
@@ -40,6 +40,7 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
@@ -448,11 +449,13 @@
   }
 
   private void handleInvokeDirect(InvokeDirect invoke) {
-    if (!appView.enableWholeProgramOptimizations()) {
+    if (!appView.hasLiveness()) {
       killAllNonFinalActiveFields();
       return;
     }
 
+    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+
     DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, method);
     if (singleTarget == null || !singleTarget.getDefinition().isInstanceInitializer()) {
       killAllNonFinalActiveFields();
@@ -470,7 +473,9 @@
     fieldInitializationInfos.forEachWithDeterministicOrder(
         appView,
         (field, info) -> {
-          if (!appView.appInfo().withLiveness().mayPropagateValueFor(field.getReference())) {
+          if (!appViewWithLiveness
+              .appInfo()
+              .mayPropagateValueFor(appViewWithLiveness, field.getReference())) {
             return;
           }
           if (info.isArgumentInitializationInfo()) {
@@ -485,7 +490,7 @@
             }
           } else if (info.isSingleValue()) {
             SingleValue value = info.asSingleValue();
-            if (value.isMaterializableInContext(appView.withLiveness(), method)) {
+            if (value.isMaterializableInContext(appViewWithLiveness, method)) {
               Value object = invoke.getReceiver().getAliasedValue();
               FieldAndObject fieldAndObject = new FieldAndObject(field.getReference(), object);
               if (field.isFinal()) {
@@ -782,12 +787,13 @@
   }
 
   private void applyObjectState(Value value, ObjectState objectState) {
+    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
     objectState.forEachAbstractFieldValue(
         (field, fieldValue) -> {
-          if (appView.appInfoWithLiveness().mayPropagateValueFor(field)
+          if (appViewWithLiveness.appInfo().mayPropagateValueFor(appViewWithLiveness, field)
               && fieldValue.isSingleValue()) {
             SingleValue singleFieldValue = fieldValue.asSingleValue();
-            if (singleFieldValue.isMaterializableInContext(appView.withLiveness(), method)) {
+            if (singleFieldValue.isMaterializableInContext(appViewWithLiveness, method)) {
               activeState.putFinalInstanceField(
                   new FieldAndObject(field, value), new MaterializableValue(singleFieldValue));
             }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index e6494f3..c3c21f1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -487,11 +487,12 @@
             continue;
           }
 
-          ClassTypeElement exactReceiverType =
-              ClassTypeElement.create(eligibleClass.type, Nullability.definitelyNotNull(), appView);
+          DynamicType exactReceiverType =
+              DynamicType.createExact(
+                  ClassTypeElement.create(
+                      eligibleClass.getType(), Nullability.definitelyNotNull(), appView));
           ProgramMethod singleTarget =
-              invoke.lookupSingleProgramTarget(
-                  appView, method, exactReceiverType, exactReceiverType);
+              invoke.lookupSingleProgramTarget(appView, method, exactReceiverType);
           if (singleTarget == null || !indirectMethodCallsOnInstance.contains(singleTarget)) {
             throw new IllegalClassInlinerStateException();
           }
@@ -1046,12 +1047,12 @@
     int parameter = 0;
     if (root.isNewInstance()) {
       return classInlinerMethodConstraint.isEligibleForNewInstanceClassInlining(
-          singleTarget, parameter);
+          appView, eligibleClass, singleTarget, parameter);
     }
 
     assert root.isStaticGet();
     return classInlinerMethodConstraint.isEligibleForStaticGetClassInlining(
-        appView, parameter, objectState, method);
+        appView, eligibleClass, parameter, objectState, method);
   }
 
   // Analyzes if a method invoke the eligible instance is passed to is eligible. In short,
@@ -1155,13 +1156,13 @@
         singleTarget.getDefinition().getOptimizationInfo().getClassInlinerMethodConstraint();
     if (root.isNewInstance()) {
       if (!classInlinerMethodConstraint.isEligibleForNewInstanceClassInlining(
-          singleTarget, parameter)) {
+          appView, eligibleClass, singleTarget, parameter)) {
         return false;
       }
     } else {
       assert root.isStaticGet();
       if (!classInlinerMethodConstraint.isEligibleForStaticGetClassInlining(
-          appView, parameter, objectState, method)) {
+          appView, eligibleClass, parameter, objectState, method)) {
         return false;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsage.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsage.java
index 930edc5..5d7f1e3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsage.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsage.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.classinliner.analysis;
 
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 
 class BottomParameterUsage extends ParameterUsage {
@@ -18,6 +19,11 @@
   }
 
   @Override
+  ParameterUsage addCastWithParameter(DexType castType) {
+    return InternalNonEmptyParameterUsage.builder().addCastWithParameter(castType).build();
+  }
+
+  @Override
   ParameterUsage addFieldReadFromParameter(DexField field) {
     return InternalNonEmptyParameterUsage.builder().addFieldReadFromParameter(field).build();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/InternalNonEmptyParameterUsage.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/InternalNonEmptyParameterUsage.java
index 4bba181..29274b2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/InternalNonEmptyParameterUsage.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/InternalNonEmptyParameterUsage.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableMultiset;
@@ -23,6 +24,7 @@
  */
 public class InternalNonEmptyParameterUsage extends ParameterUsage {
 
+  private Set<DexType> castsWithParameter;
   private Set<DexField> fieldsReadFromParameter;
   private Set<InvokeMethodWithReceiver> methodCallsWithParameterAsReceiver;
 
@@ -31,16 +33,19 @@
   private boolean isParameterUsedAsLock;
 
   InternalNonEmptyParameterUsage(
+      Set<DexType> castsWithParameter,
       Set<DexField> fieldsReadFromParameter,
       Set<InvokeMethodWithReceiver> methodCallsWithParameterAsReceiver,
       boolean isParameterMutated,
       boolean isParameterReturned,
       boolean isParameterUsedAsLock) {
-    assert !fieldsReadFromParameter.isEmpty()
+    assert !castsWithParameter.isEmpty()
+        || !fieldsReadFromParameter.isEmpty()
         || !methodCallsWithParameterAsReceiver.isEmpty()
         || isParameterMutated
         || isParameterReturned
         || isParameterUsedAsLock;
+    this.castsWithParameter = castsWithParameter;
     this.fieldsReadFromParameter = fieldsReadFromParameter;
     this.methodCallsWithParameterAsReceiver = methodCallsWithParameterAsReceiver;
     this.isParameterMutated = isParameterMutated;
@@ -57,11 +62,26 @@
   }
 
   @Override
+  ParameterUsage addCastWithParameter(DexType castType) {
+    ImmutableSet.Builder<DexType> newCastsWithParameter = ImmutableSet.builder();
+    newCastsWithParameter.addAll(castsWithParameter);
+    newCastsWithParameter.add(castType);
+    return new InternalNonEmptyParameterUsage(
+        newCastsWithParameter.build(),
+        fieldsReadFromParameter,
+        methodCallsWithParameterAsReceiver,
+        isParameterMutated,
+        isParameterReturned,
+        isParameterUsedAsLock);
+  }
+
+  @Override
   InternalNonEmptyParameterUsage addFieldReadFromParameter(DexField field) {
     ImmutableSet.Builder<DexField> newFieldsReadFromParameterBuilder = ImmutableSet.builder();
     newFieldsReadFromParameterBuilder.addAll(fieldsReadFromParameter);
     newFieldsReadFromParameterBuilder.add(field);
     return new InternalNonEmptyParameterUsage(
+        castsWithParameter,
         newFieldsReadFromParameterBuilder.build(),
         methodCallsWithParameterAsReceiver,
         isParameterMutated,
@@ -77,6 +97,7 @@
     newMethodCallsWithParameterAsReceiverBuilder.addAll(methodCallsWithParameterAsReceiver);
     newMethodCallsWithParameterAsReceiverBuilder.add(invoke);
     return new InternalNonEmptyParameterUsage(
+        castsWithParameter,
         fieldsReadFromParameter,
         newMethodCallsWithParameterAsReceiverBuilder.build(),
         isParameterMutated,
@@ -96,6 +117,7 @@
     methodCallsWithParameterAsReceiver.forEach(
         invoke -> methodCallsWithParameterAsReceiverBuilder.add(invoke.getInvokedMethod()));
     return new NonEmptyParameterUsage(
+        castsWithParameter,
         fieldsReadFromParameter,
         methodCallsWithParameterAsReceiverBuilder.build(),
         isParameterMutated,
@@ -120,6 +142,7 @@
 
   InternalNonEmptyParameterUsage join(InternalNonEmptyParameterUsage other) {
     return builderFromInstance()
+        .addCastsWithParameter(other.castsWithParameter)
         .addFieldsReadFromParameter(other.fieldsReadFromParameter)
         .addMethodCallsWithParameterAsReceiver(other.methodCallsWithParameterAsReceiver)
         .joinIsReceiverMutated(other.isParameterMutated)
@@ -134,6 +157,7 @@
       return this;
     }
     return new InternalNonEmptyParameterUsage(
+        castsWithParameter,
         fieldsReadFromParameter,
         methodCallsWithParameterAsReceiver,
         true,
@@ -147,6 +171,7 @@
       return this;
     }
     return new InternalNonEmptyParameterUsage(
+        castsWithParameter,
         fieldsReadFromParameter,
         methodCallsWithParameterAsReceiver,
         isParameterMutated,
@@ -160,6 +185,7 @@
       return this;
     }
     return new InternalNonEmptyParameterUsage(
+        castsWithParameter,
         fieldsReadFromParameter,
         methodCallsWithParameterAsReceiver,
         isParameterMutated,
@@ -169,6 +195,9 @@
 
   @Override
   public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    }
     if (obj == null || obj.getClass() != getClass()) {
       return false;
     }
@@ -176,6 +205,7 @@
     return isParameterMutated == knownParameterUsage.isParameterMutated
         && isParameterReturned == knownParameterUsage.isParameterReturned
         && isParameterUsedAsLock == knownParameterUsage.isParameterUsedAsLock
+        && castsWithParameter.equals(knownParameterUsage.castsWithParameter)
         && fieldsReadFromParameter.equals(knownParameterUsage.fieldsReadFromParameter)
         && methodCallsWithParameterAsReceiver.equals(
             knownParameterUsage.methodCallsWithParameterAsReceiver);
@@ -184,9 +214,11 @@
   @Override
   public int hashCode() {
     int hash =
-        31 * (31 + fieldsReadFromParameter.hashCode())
+        31 * (31 * (31 + castsWithParameter.hashCode()) + fieldsReadFromParameter.hashCode())
             + methodCallsWithParameterAsReceiver.hashCode();
-    assert hash == Objects.hash(fieldsReadFromParameter, methodCallsWithParameterAsReceiver);
+    assert hash
+        == Objects.hash(
+            castsWithParameter, fieldsReadFromParameter, methodCallsWithParameterAsReceiver);
     hash = (hash << 1) | BooleanUtils.intValue(isParameterMutated);
     hash = (hash << 1) | BooleanUtils.intValue(isParameterReturned);
     hash = (hash << 1) | BooleanUtils.intValue(isParameterUsedAsLock);
@@ -195,6 +227,7 @@
 
   static class Builder {
 
+    private ImmutableSet.Builder<DexType> castsWithParameterBuilder;
     private ImmutableSet.Builder<DexField> fieldsReadFromParameterBuilder;
     private ImmutableSet.Builder<InvokeMethodWithReceiver>
         methodCallsWithParameterAsReceiverBuilder;
@@ -203,11 +236,14 @@
     private boolean isParameterUsedAsLock;
 
     Builder() {
+      castsWithParameterBuilder = ImmutableSet.builder();
       fieldsReadFromParameterBuilder = ImmutableSet.builder();
       methodCallsWithParameterAsReceiverBuilder = ImmutableSet.builder();
     }
 
     Builder(InternalNonEmptyParameterUsage methodBehavior) {
+      castsWithParameterBuilder =
+          ImmutableSet.<DexType>builder().addAll(methodBehavior.castsWithParameter);
       fieldsReadFromParameterBuilder =
           ImmutableSet.<DexField>builder().addAll(methodBehavior.fieldsReadFromParameter);
       methodCallsWithParameterAsReceiverBuilder =
@@ -218,6 +254,16 @@
       isParameterUsedAsLock = methodBehavior.isParameterUsedAsLock;
     }
 
+    Builder addCastWithParameter(DexType castType) {
+      castsWithParameterBuilder.add(castType);
+      return this;
+    }
+
+    Builder addCastsWithParameter(Collection<DexType> castTypes) {
+      castsWithParameterBuilder.addAll(castTypes);
+      return this;
+    }
+
     Builder addFieldReadFromParameter(DexField fieldReadFromParameter) {
       fieldsReadFromParameterBuilder.add(fieldReadFromParameter);
       return this;
@@ -272,6 +318,7 @@
 
     InternalNonEmptyParameterUsage build() {
       return new InternalNonEmptyParameterUsage(
+          castsWithParameterBuilder.build(),
           fieldsReadFromParameterBuilder.build(),
           methodCallsWithParameterAsReceiverBuilder.build(),
           isParameterMutated,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsage.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsage.java
index d377305..38056c8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsage.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsage.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.Multiset;
@@ -15,6 +16,7 @@
 
 public class NonEmptyParameterUsage extends ParameterUsage {
 
+  private Set<DexType> castsWithParameter;
   private Set<DexField> fieldsReadFromParameter;
   private Multiset<DexMethod> methodCallsWithParameterAsReceiver;
 
@@ -23,16 +25,19 @@
   private boolean isParameterUsedAsLock;
 
   NonEmptyParameterUsage(
+      Set<DexType> castsWithParameter,
       Set<DexField> fieldsReadFromParameter,
       Multiset<DexMethod> methodCallsWithParameterAsReceiver,
       boolean isParameterMutated,
       boolean isParameterReturned,
       boolean isParameterUsedAsLock) {
-    assert !fieldsReadFromParameter.isEmpty()
+    assert !castsWithParameter.isEmpty()
+        || !fieldsReadFromParameter.isEmpty()
         || !methodCallsWithParameterAsReceiver.isEmpty()
         || isParameterMutated
         || isParameterReturned
         || isParameterUsedAsLock;
+    this.castsWithParameter = castsWithParameter;
     this.fieldsReadFromParameter = fieldsReadFromParameter;
     this.methodCallsWithParameterAsReceiver = methodCallsWithParameterAsReceiver;
     this.isParameterMutated = isParameterMutated;
@@ -41,6 +46,11 @@
   }
 
   @Override
+  ParameterUsage addCastWithParameter(DexType castType) {
+    throw new Unreachable();
+  }
+
+  @Override
   ParameterUsage addFieldReadFromParameter(DexField field) {
     throw new Unreachable();
   }
@@ -64,6 +74,10 @@
     return !getFieldsReadFromParameter().isEmpty();
   }
 
+  public Set<DexType> getCastsWithParameter() {
+    return castsWithParameter;
+  }
+
   public Set<DexField> getFieldsReadFromParameter() {
     return fieldsReadFromParameter;
   }
@@ -104,6 +118,9 @@
 
   @Override
   public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    }
     if (obj == null || obj.getClass() != getClass()) {
       return false;
     }
@@ -111,6 +128,7 @@
     return isParameterMutated == knownParameterUsage.isParameterMutated
         && isParameterReturned == knownParameterUsage.isParameterReturned
         && isParameterUsedAsLock == knownParameterUsage.isParameterUsedAsLock
+        && castsWithParameter.equals(knownParameterUsage.castsWithParameter)
         && fieldsReadFromParameter.equals(knownParameterUsage.fieldsReadFromParameter)
         && methodCallsWithParameterAsReceiver.equals(
             knownParameterUsage.methodCallsWithParameterAsReceiver);
@@ -119,9 +137,11 @@
   @Override
   public int hashCode() {
     int hash =
-        31 * (31 + fieldsReadFromParameter.hashCode())
+        31 * (31 * (31 + castsWithParameter.hashCode()) + fieldsReadFromParameter.hashCode())
             + methodCallsWithParameterAsReceiver.hashCode();
-    assert hash == Objects.hash(fieldsReadFromParameter, methodCallsWithParameterAsReceiver);
+    assert hash
+        == Objects.hash(
+            castsWithParameter, fieldsReadFromParameter, methodCallsWithParameterAsReceiver);
     hash = (hash << 1) | BooleanUtils.intValue(isParameterMutated);
     hash = (hash << 1) | BooleanUtils.intValue(isParameterReturned);
     hash = (hash << 1) | BooleanUtils.intValue(isParameterUsedAsLock);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsage.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsage.java
index e2ad8da..2822d6f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsage.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsage.java
@@ -5,10 +5,13 @@
 package com.android.tools.r8.ir.optimize.classinliner.analysis;
 
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 
 public abstract class ParameterUsage {
 
+  abstract ParameterUsage addCastWithParameter(DexType castType);
+
   abstract ParameterUsage addFieldReadFromParameter(DexField field);
 
   abstract ParameterUsage addMethodCallWithParameterAsReceiver(InvokeMethodWithReceiver invoke);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
index 152c212..4d45a08 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
@@ -171,7 +171,11 @@
 
   private ParameterUsages analyzeCheckCast(CheckCast checkCast, NonEmptyParameterUsages state) {
     // Mark the value as ineligible for class inlining if it has phi users.
-    return checkCast.outValue().hasPhiUsers() ? fail(checkCast, state) : state;
+    if (checkCast.outValue().hasPhiUsers()) {
+      return fail(checkCast, state);
+    }
+    return state.rebuildParameter(
+        checkCast.object(), (context, usage) -> usage.addCastWithParameter(checkCast.getType()));
   }
 
   private ParameterUsages analyzeIf(If theIf, NonEmptyParameterUsages state) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsage.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsage.java
index 93577ad..bf49e63 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsage.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsage.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.classinliner.analysis;
 
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 
 class UnknownParameterUsage extends ParameterUsage {
@@ -18,6 +19,11 @@
   }
 
   @Override
+  ParameterUsage addCastWithParameter(DexType castType) {
+    return this;
+  }
+
+  @Override
   UnknownParameterUsage addFieldReadFromParameter(DexField field) {
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
index 1826502..f46ae29 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.classinliner.constraint;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
 import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
@@ -34,13 +35,18 @@
   }
 
   @Override
-  public boolean isEligibleForNewInstanceClassInlining(ProgramMethod method, int parameter) {
+  public boolean isEligibleForNewInstanceClassInlining(
+      AppView<AppInfoWithLiveness> appView,
+      DexProgramClass candidateClass,
+      ProgramMethod method,
+      int parameter) {
     return false;
   }
 
   @Override
   public boolean isEligibleForStaticGetClassInlining(
       AppView<AppInfoWithLiveness> appView,
+      DexProgramClass candidateClass,
       int parameter,
       ObjectState objectState,
       ProgramMethod context) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java
index 1f33962..8efec57 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.classinliner.constraint;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
 import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
@@ -34,13 +35,18 @@
   }
 
   @Override
-  public boolean isEligibleForNewInstanceClassInlining(ProgramMethod method, int parameter) {
+  public boolean isEligibleForNewInstanceClassInlining(
+      AppView<AppInfoWithLiveness> appView,
+      DexProgramClass candidateClass,
+      ProgramMethod method,
+      int parameter) {
     return true;
   }
 
   @Override
   public boolean isEligibleForStaticGetClassInlining(
       AppView<AppInfoWithLiveness> appView,
+      DexProgramClass candidateClass,
       int parameter,
       ObjectState objectState,
       ProgramMethod context) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
index 5bc6317..994cdaa 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.classinliner.constraint;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
 import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
@@ -18,10 +19,15 @@
 
   ParameterUsage getParameterUsage(int parameter);
 
-  boolean isEligibleForNewInstanceClassInlining(ProgramMethod method, int parameter);
+  boolean isEligibleForNewInstanceClassInlining(
+      AppView<AppInfoWithLiveness> appView,
+      DexProgramClass candidateClass,
+      ProgramMethod method,
+      int parameter);
 
   boolean isEligibleForStaticGetClassInlining(
       AppView<AppInfoWithLiveness> appView,
+      DexProgramClass candidateClass,
       int parameter,
       ObjectState objectState,
       ProgramMethod context);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
index eaae1fe..bd524a5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
@@ -8,6 +8,8 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.proto.ArgumentInfo;
 import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
@@ -68,15 +70,30 @@
   }
 
   @Override
-  public boolean isEligibleForNewInstanceClassInlining(ProgramMethod method, int parameter) {
+  public boolean isEligibleForNewInstanceClassInlining(
+      AppView<AppInfoWithLiveness> appView,
+      DexProgramClass candidateClass,
+      ProgramMethod method,
+      int parameter) {
     AnalysisContext defaultContext = AnalysisContext.getDefaultContext();
     ParameterUsage usage = usages.get(parameter).get(defaultContext);
-    return !usage.isTop();
+    if (usage.isBottom()) {
+      return true;
+    }
+    if (usage.isTop()) {
+      return false;
+    }
+    NonEmptyParameterUsage knownUsage = usage.asNonEmpty();
+    if (hasUnsafeCast(appView, candidateClass, knownUsage)) {
+      return false;
+    }
+    return true;
   }
 
   @Override
   public boolean isEligibleForStaticGetClassInlining(
       AppView<AppInfoWithLiveness> appView,
+      DexProgramClass candidateClass,
       int parameter,
       ObjectState objectState,
       ProgramMethod context) {
@@ -100,6 +117,9 @@
       // We will not be able to remove the monitor instruction afterwards.
       return false;
     }
+    if (hasUnsafeCast(appView, candidateClass, knownUsage)) {
+      return false;
+    }
     for (DexField fieldReadFromParameter : knownUsage.getFieldsReadFromParameter()) {
       DexClass holder = appView.definitionFor(fieldReadFromParameter.getHolderType());
       DexEncodedField definition = fieldReadFromParameter.lookupOnClass(holder);
@@ -117,4 +137,20 @@
     }
     return true;
   }
+
+  private boolean hasUnsafeCast(
+      AppView<AppInfoWithLiveness> appView,
+      DexProgramClass candidateClass,
+      NonEmptyParameterUsage knownUsage) {
+    for (DexType castType : knownUsage.getCastsWithParameter()) {
+      if (!castType.isClassType()) {
+        return true;
+      }
+      DexClass castClass = appView.definitionFor(castType);
+      if (castClass == null || !appView.appInfo().isSubtype(candidateClass, castClass)) {
+        return true;
+      }
+    }
+    return false;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index 40ccc32..700c1d7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -12,6 +12,7 @@
 import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
 import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS;
 import static com.android.tools.r8.ir.code.Opcodes.IF;
+import static com.android.tools.r8.ir.code.Opcodes.INIT_CLASS;
 import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
 import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_CUSTOM;
@@ -62,6 +63,7 @@
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.InitClass;
 import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeCustom;
@@ -267,6 +269,9 @@
           case CHECK_CAST:
             analyzeCheckCast(instruction.asCheckCast(), eligibleEnums);
             break;
+          case INIT_CLASS:
+            analyzeInitClass(instruction.asInitClass(), eligibleEnums);
+            break;
           case INVOKE_CUSTOM:
             analyzeInvokeCustom(instruction.asInvokeCustom(), eligibleEnums, code.context());
             break;
@@ -428,6 +433,13 @@
     markEnumAsUnboxable(Reason.DOWN_CAST, enumClass);
   }
 
+  private void analyzeInitClass(InitClass initClass, Set<DexType> eligibleEnums) {
+    DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(initClass.getClassValue());
+    if (enumClass != null) {
+      eligibleEnums.add(enumClass.getType());
+    }
+  }
+
   private boolean allowCheckCast(CheckCast checkCast) {
     TypeElement objectType = checkCast.object().getDynamicUpperBoundType(appView);
     return objectType.equalUpToNullability(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 1a62ad5..f223300 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.InitClass;
 import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -139,6 +140,15 @@
           continue;
         }
 
+        if (instruction.isInitClass()) {
+          InitClass initClass = instruction.asInitClass();
+          DexType enumType = getEnumTypeOrNull(initClass.getClassValue());
+          if (enumType != null) {
+            iterator.removeOrReplaceByDebugLocalRead();
+          }
+          continue;
+        }
+
         if (instruction.isIf()) {
           If ifInstruction = instruction.asIf();
           if (!ifInstruction.isZeroTest()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 65b9aaa..8853607 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -155,7 +155,7 @@
         .getFieldAccessInfoCollection()
         .get(field.getReference())
         .hasReflectiveAccess();
-    if (appView.appInfo().mayPropagateValueFor(field.getReference())) {
+    if (appView.appInfo().mayPropagateValueFor(appView, field.getReference())) {
       getFieldOptimizationInfoForUpdating(field).setAbstractValue(abstractValue);
     }
   }
@@ -192,7 +192,7 @@
   @Override
   public synchronized void methodReturnsAbstractValue(
       DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue value) {
-    if (appView.appInfo().mayPropagateValueFor(method.getReference())) {
+    if (appView.appInfo().mayPropagateValueFor(appView, method.getReference())) {
       getMethodOptimizationInfoForUpdating(method).markReturnsAbstractValue(value);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index a9e3cba..5b65a8a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -63,7 +63,7 @@
   @Override
   public void recordFieldHasAbstractValue(
       DexEncodedField field, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue) {
-    if (appView.appInfo().mayPropagateValueFor(field.getReference())) {
+    if (appView.appInfo().mayPropagateValueFor(appView, field.getReference())) {
       field.getMutableOptimizationInfo().setAbstractValue(abstractValue);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index f24cac2..8055dc4 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -52,9 +52,16 @@
       return false;
     }
     // This is a visibility forward, so check for the direct target.
-    DexEncodedMethod targetMethod =
-        appView.appInfo().unsafeResolveMethodDueToDexFormat(target).getSingleTarget();
-    if (targetMethod == null || !targetMethod.accessFlags.isPublic()) {
+    ProgramMethod targetMethod =
+        appView.appInfo().unsafeResolveMethodDueToDexFormat(target).getResolvedProgramMethod();
+    if (targetMethod == null || !targetMethod.getAccessFlags().isPublic()) {
+      return false;
+    }
+    if (definition.isStatic()
+        && method.getHolder().hasClassInitializer()
+        && method
+            .getHolder()
+            .classInitializationMayHaveSideEffectsInContext(appView, targetMethod)) {
       return false;
     }
     if (Log.ENABLED) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
index cca53b7..4599085 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -435,7 +435,7 @@
           // OK, this can be rewritten to have void return type.
           continue;
         }
-        if (!appView.appInfo().mayPropagateValueFor(method)) {
+        if (!appView.appInfo().mayPropagateValueFor(appView, method)) {
           return null;
         }
         AbstractValue returnValueForMethod =
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/EmptyOpenClosedInterfacesAnalysis.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/EmptyOpenClosedInterfacesAnalysis.java
new file mode 100644
index 0000000..6e09082
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/EmptyOpenClosedInterfacesAnalysis.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.optimize.interfaces.analysis;
+
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.IRCode;
+
+public class EmptyOpenClosedInterfacesAnalysis extends OpenClosedInterfacesAnalysis {
+
+  private static final EmptyOpenClosedInterfacesAnalysis INSTANCE =
+      new EmptyOpenClosedInterfacesAnalysis();
+
+  private EmptyOpenClosedInterfacesAnalysis() {}
+
+  static EmptyOpenClosedInterfacesAnalysis getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public void analyze(ProgramMethod method, IRCode code) {
+    // Intentionally empty.
+  }
+
+  @Override
+  public void prepareForPrimaryOptimizationPass() {
+    // Intentionally empty.
+  }
+
+  @Override
+  public void onPrimaryOptimizationPassComplete() {
+    // Intentionally empty.
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/OpenClosedInterfacesAnalysis.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/OpenClosedInterfacesAnalysis.java
new file mode 100644
index 0000000..dc13d50
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/OpenClosedInterfacesAnalysis.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.optimize.interfaces.analysis;
+
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.IRCode;
+
+public abstract class OpenClosedInterfacesAnalysis {
+
+  public static EmptyOpenClosedInterfacesAnalysis empty() {
+    return EmptyOpenClosedInterfacesAnalysis.getInstance();
+  }
+
+  public abstract void analyze(ProgramMethod method, IRCode code);
+
+  public abstract void prepareForPrimaryOptimizationPass();
+
+  public abstract void onPrimaryOptimizationPassComplete();
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/OpenClosedInterfacesAnalysisImpl.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/OpenClosedInterfacesAnalysisImpl.java
new file mode 100644
index 0000000..4e474f4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/OpenClosedInterfacesAnalysisImpl.java
@@ -0,0 +1,200 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.optimize.interfaces.analysis;
+
+import static com.android.tools.r8.ir.code.Opcodes.ARRAY_PUT;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+import static com.android.tools.r8.ir.code.Opcodes.RETURN;
+import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.InterfaceCollection;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.ArrayPut;
+import com.android.tools.r8.ir.code.FieldPut;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.optimize.interfaces.collection.NonEmptyOpenClosedInterfacesCollection;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions.OpenClosedInterfacesOptions;
+import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.Sets;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class OpenClosedInterfacesAnalysisImpl extends OpenClosedInterfacesAnalysis {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final DexItemFactory dexItemFactory;
+
+  private Set<DexClass> openInterfaces;
+
+  public OpenClosedInterfacesAnalysisImpl(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+    this.dexItemFactory = appView.dexItemFactory();
+  }
+
+  @Override
+  public void analyze(ProgramMethod method, IRCode code) {
+    if (openInterfaces == null) {
+      return;
+    }
+    // Analyze each instruction that may assign to an interface type.
+    for (Instruction instruction : code.instructions()) {
+      switch (instruction.opcode()) {
+        case ARRAY_PUT:
+          analyzeArrayPut(instruction.asArrayPut());
+          break;
+        case INSTANCE_PUT:
+        case STATIC_PUT:
+          analyzeFieldPut(instruction.asFieldPut());
+          break;
+        case INVOKE_DIRECT:
+        case INVOKE_INTERFACE:
+        case INVOKE_STATIC:
+        case INVOKE_SUPER:
+        case INVOKE_VIRTUAL:
+          analyzeInvokeMethod(instruction.asInvokeMethod());
+          break;
+        case RETURN:
+          analyzeReturn(instruction.asReturn(), method);
+          break;
+        default:
+          break;
+      }
+    }
+  }
+
+  private void analyzeArrayPut(ArrayPut arrayPut) {
+    Value array = arrayPut.array();
+    TypeElement arrayType = array.getType();
+    if (!arrayType.isArrayType()) {
+      return;
+    }
+    TypeElement valueType = arrayPut.value().getType();
+    TypeElement arrayMemberType = arrayType.asArrayType().getMemberType();
+    checkAssignment(valueType, arrayMemberType);
+  }
+
+  private void analyzeFieldPut(FieldPut fieldPut) {
+    TypeElement valueType = fieldPut.value().getType();
+    TypeElement fieldType = fieldPut.getField().getTypeElement(appView);
+    checkAssignment(valueType, fieldType);
+  }
+
+  private void analyzeInvokeMethod(InvokeMethod invoke) {
+    DexTypeList parameters = invoke.getInvokedMethod().getParameters();
+    for (int parameterIndex = 0; parameterIndex < parameters.size(); parameterIndex++) {
+      Value argument = invoke.getArgumentForParameter(parameterIndex);
+      TypeElement argumentType = argument.getType();
+      TypeElement parameterType = parameters.get(parameterIndex).toTypeElement(appView);
+      checkAssignment(argumentType, parameterType);
+    }
+  }
+
+  private void analyzeReturn(Return returnInstruction, ProgramMethod context) {
+    if (returnInstruction.isReturnVoid()) {
+      return;
+    }
+    TypeElement valueType = returnInstruction.returnValue().getType();
+    TypeElement returnType = context.getReturnType().toTypeElement(appView);
+    checkAssignment(valueType, returnType);
+  }
+
+  private void checkAssignment(TypeElement fromType, TypeElement toType) {
+    // If the type is an interface type, then check that the assigned value is a subtype of the
+    // interface type, or mark the interface as open.
+    if (!toType.isClassType()) {
+      return;
+    }
+    ClassTypeElement toClassType = toType.asClassType();
+    if (toClassType.getClassType() != dexItemFactory.objectType) {
+      return;
+    }
+    InterfaceCollection interfaceCollection = toClassType.getInterfaces();
+    interfaceCollection.forEachKnownInterface(
+        knownInterfaceType -> {
+          DexClass knownInterface = appView.definitionFor(knownInterfaceType);
+          if (knownInterface == null) {
+            return;
+          }
+          assert knownInterface.isInterface();
+          if (fromType.lessThanOrEqualUpToNullability(toType, appView)) {
+            return;
+          }
+          assert verifyOpenInterfaceWitnessIsSuppressed(fromType, knownInterface);
+          openInterfaces.add(knownInterface);
+        });
+  }
+
+  @Override
+  public void prepareForPrimaryOptimizationPass() {
+    openInterfaces = Sets.newConcurrentHashSet();
+  }
+
+  @Override
+  public void onPrimaryOptimizationPassComplete() {
+    // If open interfaces are not allowed and there are one or more suppressions, we should find at
+    // least one open interface.
+    OpenClosedInterfacesOptions options = appView.options().getOpenClosedInterfacesOptions();
+    assert options.isOpenInterfacesAllowed()
+            || !options.hasSuppressions()
+            || !openInterfaces.isEmpty()
+        : "Expected to find at least one open interface";
+
+    includeParentOpenInterfaces();
+    appView.setOpenClosedInterfacesCollection(
+        new NonEmptyOpenClosedInterfacesCollection(
+            openInterfaces.stream()
+                .map(DexClass::getType)
+                .collect(
+                    Collectors.toCollection(
+                        () -> SetUtils.newIdentityHashSet(openInterfaces.size())))));
+    openInterfaces = null;
+  }
+
+  private void includeParentOpenInterfaces() {
+    // This includes all parent interfaces of each open interface in the set of open interfaces,
+    // by using the open interfaces as the seen set.
+    WorkList<DexClass> worklist = WorkList.newWorkList(openInterfaces);
+    worklist.addAllIgnoringSeenSet(openInterfaces);
+    while (worklist.hasNext()) {
+      DexClass openInterface = worklist.next();
+      for (DexType indirectOpenInterfaceType : openInterface.getInterfaces()) {
+        DexClass indirectOpenInterfaceDefinition = appView.definitionFor(indirectOpenInterfaceType);
+        if (indirectOpenInterfaceDefinition != null) {
+          worklist.addIfNotSeen(indirectOpenInterfaceDefinition);
+        }
+      }
+    }
+  }
+
+  private boolean verifyOpenInterfaceWitnessIsSuppressed(
+      TypeElement valueType, DexClass openInterface) {
+    OpenClosedInterfacesOptions options = appView.options().getOpenClosedInterfacesOptions();
+    assert options.isSuppressed(appView, valueType, openInterface)
+        : "Unexpected open interface "
+            + openInterface.getTypeName()
+            + " (assignment: "
+            + valueType
+            + ")";
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java
new file mode 100644
index 0000000..df264e4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.optimize.interfaces.collection;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
+
+/** Default oracle for that answers "maybe open" for each interface. */
+public class DefaultOpenClosedInterfacesCollection extends OpenClosedInterfacesCollection {
+
+  private static final DefaultOpenClosedInterfacesCollection INSTANCE =
+      new DefaultOpenClosedInterfacesCollection();
+
+  private DefaultOpenClosedInterfacesCollection() {}
+
+  static DefaultOpenClosedInterfacesCollection getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public boolean isDefinitelyClosed(DexClass clazz) {
+    return false;
+  }
+
+  @Override
+  public OpenClosedInterfacesCollection rewrittenWithLens(GraphLens graphLens) {
+    return this;
+  }
+
+  @Override
+  public OpenClosedInterfacesCollection withoutPrunedItems(PrunedItems prunedItems) {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/NonEmptyOpenClosedInterfacesCollection.java b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/NonEmptyOpenClosedInterfacesCollection.java
new file mode 100644
index 0000000..b3382e0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/NonEmptyOpenClosedInterfacesCollection.java
@@ -0,0 +1,51 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.optimize.interfaces.collection;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.utils.SetUtils;
+import java.util.Set;
+
+public class NonEmptyOpenClosedInterfacesCollection extends OpenClosedInterfacesCollection {
+
+  private final Set<DexType> openInterfaceTypes;
+
+  public NonEmptyOpenClosedInterfacesCollection(Set<DexType> openInterfaceTypes) {
+    this.openInterfaceTypes = openInterfaceTypes;
+  }
+
+  @Override
+  public boolean isDefinitelyClosed(DexClass clazz) {
+    assert clazz.isInterface();
+    return !openInterfaceTypes.contains(clazz.getType());
+  }
+
+  @Override
+  public OpenClosedInterfacesCollection rewrittenWithLens(GraphLens graphLens) {
+    Set<DexType> rewrittenOpenInterfaceTypes =
+        SetUtils.newIdentityHashSet(openInterfaceTypes.size());
+    for (DexType openInterfaceType : openInterfaceTypes) {
+      rewrittenOpenInterfaceTypes.add(graphLens.lookupType(openInterfaceType));
+    }
+    return new NonEmptyOpenClosedInterfacesCollection(rewrittenOpenInterfaceTypes);
+  }
+
+  @Override
+  public OpenClosedInterfacesCollection withoutPrunedItems(PrunedItems prunedItems) {
+    if (!prunedItems.hasRemovedClasses()) {
+      return this;
+    }
+    Set<DexType> prunedOpenInterfaceTypes = SetUtils.newIdentityHashSet(openInterfaceTypes.size());
+    for (DexType openInterfaceType : openInterfaceTypes) {
+      if (!prunedItems.isRemoved(openInterfaceType)) {
+        prunedOpenInterfaceTypes.add(openInterfaceType);
+      }
+    }
+    return new NonEmptyOpenClosedInterfacesCollection(prunedOpenInterfaceTypes);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/OpenClosedInterfacesCollection.java b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/OpenClosedInterfacesCollection.java
new file mode 100644
index 0000000..5b009ba
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/OpenClosedInterfacesCollection.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.optimize.interfaces.collection;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.function.Supplier;
+
+/**
+ * Knowledge about open/closed interfaces.
+ *
+ * <p>An interface type is "open" if it may store an instance that is not a subtype of the given
+ * interface.
+ *
+ * <p>An interface type is "closed" if it is guaranteed to store instances that are subtypes of the
+ * given interface.
+ */
+public abstract class OpenClosedInterfacesCollection {
+
+  public static DefaultOpenClosedInterfacesCollection getDefault() {
+    return DefaultOpenClosedInterfacesCollection.getInstance();
+  }
+
+  public abstract boolean isDefinitelyClosed(DexClass clazz);
+
+  public final boolean isMaybeOpen(DexClass clazz) {
+    return !isDefinitelyClosed(clazz);
+  }
+
+  public final boolean isDefinitelyInstanceOfStaticType(
+      AppView<AppInfoWithLiveness> appView, Value value) {
+    return isDefinitelyInstanceOfStaticType(
+        appView, () -> value.getDynamicType(appView), value.getType());
+  }
+
+  public final boolean isDefinitelyInstanceOfStaticType(
+      AppView<?> appView, Supplier<DynamicType> dynamicTypeSupplier, TypeElement staticType) {
+    if (!staticType.isClassType()) {
+      // Only interface types may store instances that are not a subtype of the static type.
+      return true;
+    }
+    ClassTypeElement staticClassType = staticType.asClassType();
+    if (staticClassType.getClassType() != appView.dexItemFactory().objectType) {
+      // Ditto.
+      return true;
+    }
+    if (staticClassType.nullability().isDefinitelyNull()) {
+      // The null value is definitely an instance of the static type.
+      return true;
+    }
+    boolean isStaticTypeDefinitelyClosed =
+        staticClassType
+            .getInterfaces()
+            .allKnownInterfacesMatch(
+                knownInterfaceType -> {
+                  DexClass knownInterface = appView.definitionFor(knownInterfaceType);
+                  return knownInterface != null && isDefinitelyClosed(knownInterface);
+                });
+    if (isStaticTypeDefinitelyClosed) {
+      return true;
+    }
+    DynamicType dynamicType = dynamicTypeSupplier.get();
+    if (dynamicType.isNullType()) {
+      return true;
+    }
+    if (dynamicType.isUnknown()) {
+      return false;
+    }
+    TypeElement dynamicUpperBoundType = dynamicType.getDynamicUpperBoundType(staticType);
+    if (!dynamicUpperBoundType.isClassType()) {
+      // Should not happen, since the dynamic type should be assignable to the static type.
+      assert false;
+      return false;
+    }
+    ClassTypeElement dynamicUpperBoundClassType = dynamicUpperBoundType.asClassType();
+    if (dynamicUpperBoundClassType.getClassType() != appView.dexItemFactory().objectType) {
+      // The dynamic upper bound type is a non-interface type. Check if this non-interface type is a
+      // subtype of the static interface type.
+      return dynamicUpperBoundClassType.lessThanOrEqualUpToNullability(staticType, appView);
+    }
+    return false;
+  }
+
+  public abstract OpenClosedInterfacesCollection rewrittenWithLens(GraphLens graphLens);
+
+  public abstract OpenClosedInterfacesCollection withoutPrunedItems(PrunedItems prunedItems);
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingPartition.java b/src/main/java/com/android/tools/r8/retrace/MappingPartition.java
new file mode 100644
index 0000000..a925cc3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/MappingPartition.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public interface MappingPartition {
+
+  String getKey();
+
+  byte[] getPayload();
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingPartitionKeyInfo.java b/src/main/java/com/android/tools/r8/retrace/MappingPartitionKeyInfo.java
new file mode 100644
index 0000000..56f20b1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/MappingPartitionKeyInfo.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.TypeReference;
+import java.util.function.Consumer;
+
+@Keep
+public interface MappingPartitionKeyInfo {
+
+  void getKeysForClass(ClassReference reference, Consumer<String> keyConsumer);
+
+  void getKeysForClassAndMethodName(
+      ClassReference reference, String methodName, Consumer<String> keyConsumer);
+
+  void getKeysForMethod(MethodReference reference, Consumer<String> keyConsumer);
+
+  void getKeysForField(FieldReference fieldReference, Consumer<String> keyConsumer);
+
+  void getKeysForType(TypeReference typeReference, Consumer<String> keyConsumer);
+
+  static MappingPartitionKeyInfo getDefault(byte[] metadata) {
+    return null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingPartitioner.java b/src/main/java/com/android/tools/r8/retrace/MappingPartitioner.java
new file mode 100644
index 0000000..10c641a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/MappingPartitioner.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public interface MappingPartitioner {
+
+  MappingPartitions partition(ProguardMapProducer mapProducer);
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingPartitions.java b/src/main/java/com/android/tools/r8/retrace/MappingPartitions.java
new file mode 100644
index 0000000..9bb9eb7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/MappingPartitions.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+import java.util.function.Consumer;
+
+@Keep
+public interface MappingPartitions {
+
+  byte[] getMetadata();
+
+  void visitPartitions(Consumer<MappingPartition> consumer);
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/ProguardMapProducer.java b/src/main/java/com/android/tools/r8/retrace/ProguardMapProducer.java
index 81b7f30..c3f94be 100644
--- a/src/main/java/com/android/tools/r8/retrace/ProguardMapProducer.java
+++ b/src/main/java/com/android/tools/r8/retrace/ProguardMapProducer.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.retrace;
 
 import com.android.tools.r8.Keep;
+import com.google.common.primitives.Bytes;
 import java.io.IOException;
 import java.io.Reader;
 import java.io.StringReader;
@@ -25,4 +26,8 @@
   static ProguardMapProducer fromPath(Path path) {
     return () -> Files.newBufferedReader(path, StandardCharsets.UTF_8);
   }
+
+  static ProguardMapProducer fromBytes(byte[]... partitions) {
+    return fromString(new String(Bytes.concat(partitions), StandardCharsets.UTF_8));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 7464171..d601873 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -48,6 +48,9 @@
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter;
@@ -1035,28 +1038,33 @@
         && !keepInfo.getMethodInfo(method).isPinned(options());
   }
 
-  public boolean mayPropagateValueFor(DexClassAndMember<?, ?> member) {
+  public boolean mayPropagateValueFor(
+      AppView<AppInfoWithLiveness> appView, DexClassAndMember<?, ?> member) {
     assert checkIfObsolete();
-    return member.getReference().apply(this::mayPropagateValueFor, this::mayPropagateValueFor);
+    return member
+        .getReference()
+        .apply(
+            field -> mayPropagateValueFor(appView, field),
+            method -> mayPropagateValueFor(appView, method));
   }
 
-  public boolean mayPropagateValueFor(DexField field) {
+  public boolean mayPropagateValueFor(AppView<AppInfoWithLiveness> appView, DexField field) {
     assert checkIfObsolete();
     if (neverPropagateValue.contains(field)) {
       return false;
     }
-    if (isPinned(field) && !field.getType().isAlwaysNull(this)) {
+    if (isPinned(field) && !field.getType().isAlwaysNull(appView)) {
       return false;
     }
     return true;
   }
 
-  public boolean mayPropagateValueFor(DexMethod method) {
+  public boolean mayPropagateValueFor(AppView<AppInfoWithLiveness> appView, DexMethod method) {
     assert checkIfObsolete();
     if (neverPropagateValue.contains(method)) {
       return false;
     }
-    if (!method.getReturnType().isAlwaysNull(this)
+    if (!method.getReturnType().isAlwaysNull(appView)
         && !getKeepInfo().getMethodInfo(method, this).isOptimizationAllowed(options())) {
       return false;
     }
@@ -1285,6 +1293,7 @@
   }
 
   public DexEncodedMethod lookupSingleTarget(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       Type type,
       DexMethod target,
       ProgramMethod context,
@@ -1296,9 +1305,9 @@
     }
     switch (type) {
       case VIRTUAL:
-        return lookupSingleVirtualTarget(target, context, false, modeledPredicate);
+        return lookupSingleVirtualTarget(appView, target, context, false, modeledPredicate);
       case INTERFACE:
-        return lookupSingleVirtualTarget(target, context, true, modeledPredicate);
+        return lookupSingleVirtualTarget(appView, target, context, true, modeledPredicate);
       case DIRECT:
         return lookupDirectTarget(target, context);
       case STATIC:
@@ -1311,56 +1320,67 @@
   }
 
   public ProgramMethod lookupSingleProgramTarget(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       Type type,
       DexMethod target,
       ProgramMethod context,
       LibraryModeledPredicate modeledPredicate) {
-    return asProgramMethodOrNull(lookupSingleTarget(type, target, context, modeledPredicate), this);
+    return asProgramMethodOrNull(
+        lookupSingleTarget(appView, type, target, context, modeledPredicate), this);
   }
 
   /** For mapping invoke virtual instruction to single target method. */
   public DexEncodedMethod lookupSingleVirtualTarget(
-      DexMethod method, ProgramMethod context, boolean isInterface) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      DexMethod method,
+      ProgramMethod context,
+      boolean isInterface) {
     assert checkIfObsolete();
-    return lookupSingleVirtualTarget(
-        method, context, isInterface, type -> false, method.holder, null);
+    return lookupSingleVirtualTarget(appView, method, context, isInterface, type -> false);
   }
 
   /** For mapping invoke virtual instruction to single target method. */
   public DexEncodedMethod lookupSingleVirtualTarget(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       DexMethod method,
       ProgramMethod context,
       boolean isInterface,
       LibraryModeledPredicate modeledPredicate) {
     assert checkIfObsolete();
     return lookupSingleVirtualTarget(
-        method, context, isInterface, modeledPredicate, method.holder, null);
+        appView, method, context, isInterface, modeledPredicate, DynamicType.unknown());
   }
 
   public DexEncodedMethod lookupSingleVirtualTarget(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       DexMethod method,
       ProgramMethod context,
       boolean isInterface,
       LibraryModeledPredicate modeledPredicate,
-      DexType refinedReceiverType,
-      ClassTypeElement receiverLowerBoundType) {
+      DynamicType dynamicReceiverType) {
     assert checkIfObsolete();
-    assert refinedReceiverType != null;
-    if (!refinedReceiverType.isClassType()) {
-      // The refined receiver is not of class type and we will not be able to find a single target
-      // (it is either primitive or array).
+    assert dynamicReceiverType != null;
+    if (method.getHolderType().isArrayType()) {
+      return null;
+    }
+    TypeElement staticReceiverType = method.getHolderType().toTypeElement(appView);
+    if (!appView
+        .getOpenClosedInterfacesCollection()
+        .isDefinitelyInstanceOfStaticType(appView, () -> dynamicReceiverType, staticReceiverType)) {
       return null;
     }
     DexClass initialResolutionHolder = definitionFor(method.holder);
     if (initialResolutionHolder == null || initialResolutionHolder.isInterface() != isInterface) {
       return null;
     }
+    DexType refinedReceiverType =
+        TypeAnalysis.toRefinedReceiverType(dynamicReceiverType, method, appView);
     DexClass refinedReceiverClass = definitionFor(refinedReceiverType);
     if (refinedReceiverClass == null) {
       // The refined receiver is not defined in the program and we cannot determine the target.
       return null;
     }
-    if (receiverLowerBoundType == null
+    if (!dynamicReceiverType.hasDynamicLowerBoundType()
         && singleTargetLookupCache.hasCachedItem(refinedReceiverType, method)) {
       DexEncodedMethod cachedItem =
           singleTargetLookupCache.getCachedItem(refinedReceiverType, method);
@@ -1383,7 +1403,10 @@
     }
     DexEncodedMethod exactTarget =
         getMethodTargetFromExactRuntimeInformation(
-            refinedReceiverType, receiverLowerBoundType, resolution, refinedReceiverClass);
+            refinedReceiverType,
+            dynamicReceiverType.getDynamicLowerBoundType(),
+            resolution,
+            refinedReceiverClass);
     if (exactTarget != null) {
       // We are not caching single targets here because the cache does not include the
       // lower bound dimension.
@@ -1405,8 +1428,9 @@
     }
     DexEncodedMethod singleMethodTarget = null;
     DexProgramClass refinedLowerBound = null;
-    if (receiverLowerBoundType != null) {
-      DexClass refinedLowerBoundClass = definitionFor(receiverLowerBoundType.getClassType());
+    if (dynamicReceiverType.hasDynamicLowerBoundType()) {
+      DexClass refinedLowerBoundClass =
+          definitionFor(dynamicReceiverType.getDynamicLowerBoundType().getClassType());
       if (refinedLowerBoundClass != null) {
         refinedLowerBound = refinedLowerBoundClass.asProgramClass();
         // TODO(b/154822960): Check if the lower bound is a subtype of the upper bound.
@@ -1426,7 +1450,7 @@
         singleMethodTarget = singleTarget.asMethodTarget().getDefinition();
       }
     }
-    if (receiverLowerBoundType == null) {
+    if (!dynamicReceiverType.hasDynamicLowerBoundType()) {
       singleTargetLookupCache.addToCache(refinedReceiverType, method, singleMethodTarget);
     }
     return singleMethodTarget;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 4ed94e5..c4af05f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptor;
 
+import com.android.tools.r8.InputDependencyGraphConsumer;
 import com.android.tools.r8.Version;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.DexField;
@@ -50,6 +51,7 @@
   private final DexItemFactory dexItemFactory;
 
   private final Reporter reporter;
+  private final InputDependencyGraphConsumer inputDependencyConsumer;
   private final boolean allowTestOptions;
 
   public static final String FLATTEN_PACKAGE_HIERARCHY = "flattenpackagehierarchy";
@@ -120,18 +122,39 @@
 
   public ProguardConfigurationParser(
       DexItemFactory dexItemFactory, Reporter reporter) {
-    this(dexItemFactory, reporter, false);
+    this(dexItemFactory, reporter, null, false);
   }
 
   public ProguardConfigurationParser(
-      DexItemFactory dexItemFactory, Reporter reporter, boolean allowTestOptions) {
+      DexItemFactory dexItemFactory,
+      Reporter reporter,
+      InputDependencyGraphConsumer inputDependencyConsumer,
+      boolean allowTestOptions) {
     this.dexItemFactory = dexItemFactory;
     configurationBuilder = ProguardConfiguration.builder(dexItemFactory, reporter);
 
     this.reporter = reporter;
+    this.inputDependencyConsumer =
+        inputDependencyConsumer != null
+            ? inputDependencyConsumer
+            : emptyInputDependencyGraphConsumer();
     this.allowTestOptions = allowTestOptions;
   }
 
+  private static InputDependencyGraphConsumer emptyInputDependencyGraphConsumer() {
+    return new InputDependencyGraphConsumer() {
+      @Override
+      public void accept(Origin dependent, Path dependency) {
+        // ignored.
+      }
+
+      @Override
+      public void finished() {
+        // ignored.
+      }
+    };
+  }
+
   public ProguardConfiguration.Builder getConfigurationBuilder() {
     return configurationBuilder;
   }
@@ -370,7 +393,8 @@
           configurationBuilder.setPrintMappingFile(parseFileName(false));
         }
       } else if (acceptString("applymapping")) {
-        configurationBuilder.setApplyMappingFile(parseFileName(false));
+        configurationBuilder.setApplyMappingFile(
+            parseFileInputDependency(inputDependencyConsumer::acceptProguardApplyMapping));
       } else if (acceptString("assumenosideeffects")) {
         ProguardAssumeNoSideEffectRule rule = parseAssumeNoSideEffectsRule(optionStart);
         configurationBuilder.addRule(rule);
@@ -388,9 +412,11 @@
         skipWhitespace();
         baseDirectory = parseFileName(false);
       } else if (acceptString("injars")) {
-        configurationBuilder.addInjars(parseClassPath());
+        configurationBuilder.addInjars(
+            parseClassPath(inputDependencyConsumer::acceptProguardInJars));
       } else if (acceptString("libraryjars")) {
-        configurationBuilder.addLibraryJars(parseClassPath());
+        configurationBuilder.addLibraryJars(
+            parseClassPath(inputDependencyConsumer::acceptProguardLibraryJars));
       } else if (acceptString("printseeds")) {
         configurationBuilder.setPrintSeeds(true);
         skipWhitespace();
@@ -398,11 +424,16 @@
           configurationBuilder.setSeedFile(parseFileName(false));
         }
       } else if (acceptString("obfuscationdictionary")) {
-        configurationBuilder.setObfuscationDictionary(parseFileName(false));
+        configurationBuilder.setObfuscationDictionary(
+            parseFileInputDependency(inputDependencyConsumer::acceptProguardObfuscationDictionary));
       } else if (acceptString("classobfuscationdictionary")) {
-        configurationBuilder.setClassObfuscationDictionary(parseFileName(false));
+        configurationBuilder.setClassObfuscationDictionary(
+            parseFileInputDependency(
+                inputDependencyConsumer::acceptProguardClassObfuscationDictionary));
       } else if (acceptString("packageobfuscationdictionary")) {
-        configurationBuilder.setPackageObfuscationDictionary(parseFileName(false));
+        configurationBuilder.setPackageObfuscationDictionary(
+            parseFileInputDependency(
+                inputDependencyConsumer::acceptProguardPackageObfuscationDictionary));
       } else if (acceptString("alwaysinline")) {
         InlineRule rule = parseInlineRule(InlineRule.Type.ALWAYS, optionStart);
         configurationBuilder.addRule(rule);
@@ -635,7 +666,7 @@
 
     private void parseInclude() throws ProguardRuleParserException {
       TextPosition start = getPosition();
-      Path included = parseFileName(false);
+      Path included = parseFileInputDependency(inputDependencyConsumer::acceptProguardInclude);
       try {
         new ProguardConfigurationSourceParser(new ProguardConfigurationSourceFile(included))
             .parse();
@@ -1536,6 +1567,13 @@
       return result.toString();
     }
 
+    private Path parseFileInputDependency(BiConsumer<Origin, Path> dependencyConsumer)
+        throws ProguardRuleParserException {
+      Path file = parseFileName(false);
+      dependencyConsumer.accept(origin, file);
+      return file;
+    }
+
     private Path parseFileName(boolean stopAfterPathSeparator) throws ProguardRuleParserException {
       TextPosition start = getPosition();
       skipWhitespace();
@@ -1566,15 +1604,18 @@
       return baseDirectory.resolve(fileName);
     }
 
-    private List<FilteredClassPath> parseClassPath() throws ProguardRuleParserException {
+    private List<FilteredClassPath> parseClassPath(BiConsumer<Origin, Path> dependencyCallback)
+        throws ProguardRuleParserException {
       List<FilteredClassPath> classPath = new ArrayList<>();
       skipWhitespace();
       TextPosition position = getPosition();
       Path file = parseFileName(true);
+      dependencyCallback.accept(origin, file);
       ImmutableList<String> filters = parseClassPathFilters();
       classPath.add(new FilteredClassPath(file, filters, origin, position));
       while (acceptChar(File.pathSeparatorChar)) {
         file = parseFileName(true);
+        dependencyCallback.accept(origin, file);
         filters = parseClassPathFilters();
         classPath.add(new FilteredClassPath(file, filters, origin, position));
       }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 5cc5876..533e24f 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -575,10 +575,7 @@
         });
     groupsPerPrefix.forEach(
         (externalSyntheticTypePrefix, groups) -> {
-          // Sort the equivalence groups that go into 'context' including the context type of the
-          // representative which is equal to 'context' here (see assert below).
-          Comparator<EquivalenceGroup<T>> comparator =
-              (a, b) -> a.compareToIncludingContext(b, appView.graphLens(), classToFeatureSplitMap);
+          Comparator<EquivalenceGroup<T>> comparator = this::compareForFinalGroupSorting;
           ListUtils.destructiveSort(groups, comparator);
           for (int i = 0; i < groups.size(); i++) {
             EquivalenceGroup<T> group = groups.get(i);
@@ -588,9 +585,7 @@
                 .equals(externalSyntheticTypePrefix);
             // Two equivalence groups in same context type must be distinct otherwise the assignment
             // of the synthetic name will be non-deterministic between the two.
-            assert i == 0
-                || checkGroupsAreDistinct(
-                    groups.get(i - 1), group, appView.graphLens(), classToFeatureSplitMap);
+            assert i == 0 || checkGroupsAreDistinct(groups.get(i - 1), group, comparator);
             SyntheticKind kind = group.getRepresentative().getKind();
             DexType representativeType =
                 intermediate
@@ -617,6 +612,17 @@
     return equivalences;
   }
 
+  private <T extends SyntheticDefinition<?, T, ?>> int compareForFinalGroupSorting(
+      EquivalenceGroup<T> a, EquivalenceGroup<T> b) {
+    // Sort the equivalence groups based on the representative types. The representatives are
+    // deterministically chosen and the internal synthetics deterministically named so using
+    // the internal type as the order is deterministic.
+    return a.getRepresentative()
+        .getHolder()
+        .getType()
+        .compareTo(b.getRepresentative().getHolder().getType());
+  }
+
   private static <T extends SyntheticDefinition<?, T, ?>> List<EquivalenceGroup<T>> groupEquivalent(
       AppView<?> appView,
       List<T> potentialEquivalence,
@@ -695,13 +701,11 @@
   }
 
   private static <T extends SyntheticDefinition<?, T, ?>> boolean checkGroupsAreDistinct(
-      EquivalenceGroup<T> g1,
-      EquivalenceGroup<T> g2,
-      GraphLens graphLens,
-      ClassToFeatureSplitMap classToFeatureSplitMap) {
-    int order = g1.compareToIncludingContext(g2, graphLens, classToFeatureSplitMap);
-    assert order != 0;
-    assert order != g2.compareToIncludingContext(g1, graphLens, classToFeatureSplitMap);
+      EquivalenceGroup<T> g1, EquivalenceGroup<T> g2, Comparator<EquivalenceGroup<T>> comparator) {
+    int smaller = comparator.compare(g1, g2);
+    assert smaller < 0;
+    int bigger = comparator.compare(g2, g1);
+    assert bigger > 0;
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 959238e..3b7706b 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -21,7 +21,6 @@
 import com.android.tools.r8.DexFilePerClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DirectoryClassFileProvider;
-import com.android.tools.r8.DumpOptions;
 import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.ProgramResource;
@@ -31,6 +30,7 @@
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.Version;
+import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
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 2b8d72a..672e322 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.DesugarGraphConsumer;
 import com.android.tools.r8.DexFilePerClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.DumpOptions;
 import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.MapIdProvider;
 import com.android.tools.r8.ProgramConsumer;
@@ -23,6 +22,7 @@
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Backend;
 import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.ExperimentalClassFileVersionDiagnostic;
 import com.android.tools.r8.errors.IncompleteNestNestDesugarDiagnosic;
@@ -34,7 +34,9 @@
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.experimental.startup.StartupConfiguration;
 import com.android.tools.r8.features.FeatureSplitConfiguration;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -51,6 +53,7 @@
 import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
 import com.android.tools.r8.horizontalclassmerging.Policy;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.desugar.TypeRewriter;
 import com.android.tools.r8.ir.desugar.TypeRewriter.MachineDesugarPrefixRewritingMapper;
@@ -87,7 +90,6 @@
 import com.google.common.collect.Sets;
 import java.io.IOException;
 import java.io.PrintStream;
-import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -176,7 +178,7 @@
     enableMinification = false;
   }
 
-  // Constructor for D8.
+  // Constructor for D8, L8, Lint and other non-shrinkers.
   public InternalOptions(DexItemFactory factory, Reporter reporter) {
     assert reporter != null;
     assert factory != null;
@@ -329,8 +331,6 @@
   public final OutlineOptions outline = new OutlineOptions();
   public boolean enableInitializedClassesInInstanceMethodsAnalysis = true;
   public boolean enableRedundantFieldLoadElimination = true;
-  // Currently disabled, see b/146957343.
-  public boolean enableUninstantiatedTypeOptimizationForInterfaces = false;
   // TODO(b/138917494): Disable until we have numbers on potential performance penalties.
   public boolean enableRedundantConstNumberOptimization = false;
 
@@ -734,6 +734,8 @@
   private final InlinerOptions inlinerOptions = new InlinerOptions();
   private final HorizontalClassMergerOptions horizontalClassMergerOptions =
       new HorizontalClassMergerOptions();
+  private final OpenClosedInterfacesOptions openClosedInterfacesOptions =
+      new OpenClosedInterfacesOptions();
   private final ProtoShrinkingOptions protoShrinking = new ProtoShrinkingOptions();
   private final KotlinOptimizationOptions kotlinOptimizationOptions =
       new KotlinOptimizationOptions();
@@ -786,6 +788,10 @@
     return desugarSpecificOptions;
   }
 
+  public OpenClosedInterfacesOptions getOpenClosedInterfacesOptions() {
+    return openClosedInterfacesOptions;
+  }
+
   private static Set<String> getExtensiveLoggingFilter() {
     String property = System.getProperty("com.android.tools.r8.extensiveLoggingFilter");
     if (property != null) {
@@ -890,37 +896,27 @@
   // If non-null, configuration must be passed to the consumer.
   public StringConsumer configurationConsumer = null;
 
-  public void setDesugaredLibrarySpecification(
-      DesugaredLibrarySpecification specification, AndroidApp app) {
+  public void setDesugaredLibrarySpecification(DesugaredLibrarySpecification specification) {
     if (specification.isEmpty()) {
       return;
     }
-    try {
-      // TODO(b/221224178): Move the timing to top level timing in D8, R8 and L8.
-      Timing timing = Timing.create("Desugared library specification conversion", this);
-      machineDesugaredLibrarySpecification =
-          specification.toMachineSpecification(this, app, timing);
-      if (printTimes) {
-        timing.report();
-      }
-    } catch (IOException e) {
-      reporter.error(new ExceptionDiagnostic(e, Origin.unknown()));
-    }
+    loadMachineDesugaredLibrarySpecification =
+        (timing, app) ->
+            machineDesugaredLibrarySpecification =
+                specification.toMachineSpecification(app, timing);
   }
 
-  public void setDesugaredLibrarySpecificationForTesting(
-      DesugaredLibrarySpecification specification, Path desugaredJDKLib, Path library)
+  private ThrowingBiConsumer<Timing, DexApplication, IOException>
+      loadMachineDesugaredLibrarySpecification = null;
+
+  public void loadMachineDesugaredLibrarySpecification(Timing timing, DexApplication app)
       throws IOException {
-    if (specification.isEmpty()) {
+    if (loadMachineDesugaredLibrarySpecification == null) {
       return;
     }
-    // TODO(b/221224178): Move the timing to top level timing in D8, R8 and L8.
-    Timing timing = Timing.create("Desugared library specification conversion", this);
-    machineDesugaredLibrarySpecification =
-        specification.toMachineSpecification(this, library, timing, desugaredJDKLib);
-    if (printTimes) {
-      timing.report();
-    }
+    timing.begin("Load machine specification");
+    loadMachineDesugaredLibrarySpecification.accept(timing, app);
+    timing.end();
   }
 
   // Contains flags describing library desugaring.
@@ -1518,6 +1514,77 @@
     }
   }
 
+  public static class OpenClosedInterfacesOptions {
+
+    public interface OpenInterfaceWitnessSuppression {
+
+      boolean isSuppressed(
+          AppView<? extends AppInfoWithClassHierarchy> appView,
+          TypeElement valueType,
+          DexClass openInterface);
+    }
+
+    // Allow open interfaces by default. This is set to false in testing.
+    private boolean allowOpenInterfaces = true;
+
+    // When open interfaces are not allowed, compilation fails with an assertion error unless each
+    // open interface witness is expected according to some suppression.
+    private List<OpenInterfaceWitnessSuppression> suppressions = new ArrayList<>();
+
+    public void disallowOpenInterfaces() {
+      allowOpenInterfaces = false;
+    }
+
+    public void suppressAllOpenInterfaces() {
+      assert !allowOpenInterfaces;
+      suppressions.add((appView, valueType, openInterface) -> true);
+    }
+
+    public void suppressAllOpenInterfacesDueToMissingClasses() {
+      assert !allowOpenInterfaces;
+      suppressions.add(
+          (appView, valueType, openInterface) -> valueType.isBasedOnMissingClass(appView));
+    }
+
+    public void suppressArrayAssignmentsToJavaLangSerializable() {
+      assert !allowOpenInterfaces;
+      suppressions.add(
+          (appView, valueType, openInterface) ->
+              valueType.isArrayType()
+                  && openInterface.getTypeName().equals("java.io.Serializable"));
+    }
+
+    public void suppressZipFileAssignmentsToJavaLangAutoCloseable() {
+      assert !allowOpenInterfaces;
+      suppressions.add(
+          (appView, valueType, openInterface) ->
+              valueType.isClassType()
+                  && valueType
+                      .asClassType()
+                      .getClassType()
+                      .getTypeName()
+                      .equals("java.util.zip.ZipFile")
+                  && openInterface.getTypeName().equals("java.lang.AutoCloseable"));
+    }
+
+    public boolean isOpenInterfacesAllowed() {
+      return allowOpenInterfaces;
+    }
+
+    public boolean hasSuppressions() {
+      return !suppressions.isEmpty();
+    }
+
+    public boolean isSuppressed(
+        AppView<? extends AppInfoWithClassHierarchy> appView,
+        TypeElement valueType,
+        DexClass openInterface) {
+      return allowOpenInterfaces
+          || suppressions.stream()
+              .anyMatch(suppression -> suppression.isSuppressed(appView, valueType, openInterface));
+    }
+  }
+
   public static class ApiModelTestingOptions {
 
     public boolean enableApiCallerIdentification =
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
index cc8bc7f..f42a8cd 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.utils.SetUtils;
+import java.util.Collection;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.Map;
@@ -96,6 +97,10 @@
     return backing.values().stream();
   }
 
+  public Collection<T> toCollection() {
+    return backing.values();
+  }
+
   public Set<DexEncodedMethod> toDefinitionSet() {
     assert backing instanceof IdentityHashMap;
     return toDefinitionSet(SetUtils::newIdentityHashSet);
diff --git a/src/test/examplesJava9/transferto/MyInputStream.java b/src/test/examplesJava9/transferto/MyInputStream.java
new file mode 100644
index 0000000..36a5e8b
--- /dev/null
+++ b/src/test/examplesJava9/transferto/MyInputStream.java
@@ -0,0 +1,22 @@
+// 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 transferto;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class MyInputStream extends ByteArrayInputStream {
+
+  public MyInputStream(byte[] buf) {
+    super(buf);
+  }
+
+  @Override
+  public long transferTo(OutputStream out) throws IOException {
+    out.write((int) '$');
+    return super.transferTo(out);
+  }
+}
diff --git a/src/test/examplesJava9/transferto/TestClass.java b/src/test/examplesJava9/transferto/TestClass.java
new file mode 100644
index 0000000..fd3c91b
--- /dev/null
+++ b/src/test/examplesJava9/transferto/TestClass.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 transferto;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class TestClass {
+  public static void main(String[] args) throws IOException {
+    transferTo();
+    transferToOverride();
+  }
+
+  public static void transferTo() throws IOException {
+    String initialString = "Hello World!";
+    System.out.println(initialString);
+
+    try (InputStream inputStream = new ByteArrayInputStream(initialString.getBytes());
+        ByteArrayOutputStream targetStream = new ByteArrayOutputStream()) {
+      inputStream.transferTo(targetStream);
+      String copied = new String(targetStream.toByteArray());
+      System.out.println(copied);
+    }
+  }
+
+  public static void transferToOverride() throws IOException {
+    String initialString = "Hello World!";
+    System.out.println(initialString);
+
+    try (MyInputStream inputStream = new MyInputStream(initialString.getBytes());
+        ByteArrayOutputStream targetStream = new ByteArrayOutputStream()) {
+      inputStream.transferTo(targetStream);
+      String copied = new String(targetStream.toByteArray());
+      System.out.println(copied);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java b/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
index 7a35696..28be79a 100644
--- a/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
+++ b/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.DataResourceProvider.Visitor;
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
diff --git a/src/test/java/com/android/tools/r8/CommandTestBase.java b/src/test/java/com/android/tools/r8/CommandTestBase.java
index dcfbeca..733fb9a 100644
--- a/src/test/java/com/android/tools/r8/CommandTestBase.java
+++ b/src/test/java/com/android/tools/r8/CommandTestBase.java
@@ -7,7 +7,12 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
+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;
+import java.io.IOException;
 import org.junit.Test;
 
 public abstract class CommandTestBase<C extends BaseCompilerCommand> extends TestBase {
@@ -303,6 +308,20 @@
     return parse(handler, prepareArgs(args));
   }
 
+  protected InternalOptions getOptionsWithLoadedDesugaredLibraryConfiguration(
+      C command, boolean libraryCompilation) throws IOException {
+    InternalOptions options = command.getInternalOptions();
+    options.loadMachineDesugaredLibrarySpecification(
+        Timing.empty(),
+        AppForSpecConversion.readAppForTesting(
+            libraryCompilation ? ToolHelper.getDesugarJDKLibs() : null,
+            ToolHelper.getAndroidJar(AndroidApiLevel.R),
+            options,
+            libraryCompilation,
+            Timing.empty()));
+    return options;
+  }
+
   /**
    * Tests in this class are executed for all of D8, R8 and L8. When testing arguments this can add
    * additional required arguments to all tests
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 7a8c4e4..78be322 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.collect.ImmutableList;
@@ -694,19 +695,15 @@
   }
 
   @Test
-  public void desugaredLibrary() throws CompilationFailedException {
+  public void desugaredLibrary() throws CompilationFailedException, IOException {
     D8Command d8Command =
         parse(
             "--desugared-lib",
             "src/library_desugar/desugar_jdk_libs.json",
             "--lib",
-            ToolHelper.getAndroidJar(AndroidApiLevel.P).toString());
-    assertFalse(
-        d8Command
-            .getInternalOptions()
-            .machineDesugaredLibrarySpecification
-            .getRewriteType()
-            .isEmpty());
+            ToolHelper.getAndroidJar(AndroidApiLevel.R).toString());
+    InternalOptions options = getOptionsWithLoadedDesugaredLibraryConfiguration(d8Command, false);
+    assertFalse(options.machineDesugaredLibrarySpecification.getRewriteType().isEmpty());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/L8CommandTest.java b/src/test/java/com/android/tools/r8/L8CommandTest.java
index 2e54294..0dac7a3 100644
--- a/src/test/java/com/android/tools/r8/L8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/L8CommandTest.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.ImmutableList;
+import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -355,19 +356,15 @@
   }
 
   @Test
-  public void desugaredLibrary() throws CompilationFailedException {
+  public void desugaredLibrary() throws CompilationFailedException, IOException {
     L8Command l8Command =
         parse(
             "--desugared-lib",
             ToolHelper.getDesugarLibJsonForTesting().toString(),
             "--lib",
-            ToolHelper.getAndroidJar(AndroidApiLevel.P).toString());
-    assertFalse(
-        l8Command
-            .getInternalOptions()
-            .machineDesugaredLibrarySpecification
-            .getRewriteType()
-            .isEmpty());
+            ToolHelper.getAndroidJar(AndroidApiLevel.R).toString());
+    InternalOptions options = getOptionsWithLoadedDesugaredLibraryConfiguration(l8Command, true);
+    assertFalse(options.machineDesugaredLibrarySpecification.getRewriteType().isEmpty());
   }
 
   private void checkSingleForceAllAssertion(
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 536f5bb..d076ec0 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -24,6 +24,7 @@
 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;
 import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.collect.ImmutableList;
@@ -825,38 +826,30 @@
   }
 
   @Test
-  public void desugaredLibrary() throws CompilationFailedException {
+  public void desugaredLibrary() throws CompilationFailedException, IOException {
     R8Command r8Command =
         parse(
             "--desugared-lib",
             "src/library_desugar/desugar_jdk_libs.json",
             "--lib",
-            ToolHelper.getAndroidJar(AndroidApiLevel.P).toString());
-    assertFalse(
-        r8Command
-            .getInternalOptions()
-            .machineDesugaredLibrarySpecification
-            .getRewriteType()
-            .isEmpty());
+            ToolHelper.getAndroidJar(AndroidApiLevel.R).toString());
+    InternalOptions options = getOptionsWithLoadedDesugaredLibraryConfiguration(r8Command, false);
+    assertFalse(options.machineDesugaredLibrarySpecification.getRewriteType().isEmpty());
   }
 
   @Test
-  public void desugaredLibraryWithOutputConf() throws CompilationFailedException {
+  public void desugaredLibraryWithOutputConf() throws CompilationFailedException, IOException {
     Path pgout = temp.getRoot().toPath().resolve("pgout.conf");
     R8Command r8Command =
         parse(
             "--desugared-lib",
             "src/library_desugar/desugar_jdk_libs.json",
             "--lib",
-            ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
+            ToolHelper.getAndroidJar(AndroidApiLevel.R).toString(),
             "--desugared-lib-pg-conf-output",
             pgout.toString());
-    assertFalse(
-        r8Command
-            .getInternalOptions()
-            .machineDesugaredLibrarySpecification
-            .getRewriteType()
-            .isEmpty());
+    InternalOptions options = getOptionsWithLoadedDesugaredLibraryConfiguration(r8Command, false);
+    assertFalse(options.machineDesugaredLibrarySpecification.getRewriteType().isEmpty());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index ec8d314..8478b17 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -131,7 +131,7 @@
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 4, "lambdadesugaring"))
         .run();
   }
 
@@ -170,7 +170,7 @@
         .withMinApiLevel(AndroidApiLevel.N)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 4, "lambdadesugaring"))
         .run();
   }
 
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index ab32b4f..10bc820 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -125,7 +125,7 @@
         builder, rules -> box.syntheticProguardRules = rules);
     libraryDesugaringTestConfiguration.configure(builder);
     ToolHelper.runAndBenchmarkR8WithoutResult(
-        builder.build(),
+        builder,
         optionsConsumer.andThen(
             options -> box.proguardConfiguration = options.getProguardConfiguration()),
         benchmarkResults);
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 545f842..0f3f71d 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -8,9 +8,7 @@
 import static com.android.tools.r8.ToolHelper.R8_TEST_BUCKET;
 import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
 import static com.google.common.collect.Lists.cartesianProduct;
-import static com.google.common.io.ByteStreams.toByteArray;
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -87,6 +85,7 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.base.Predicates;
 import com.google.common.cache.CacheBuilder;
@@ -94,6 +93,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
+import com.google.common.collect.Sets.SetView;
 import com.google.common.io.ByteStreams;
 import java.io.BufferedInputStream;
 import java.io.File;
@@ -110,8 +110,10 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.IdentityHashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
@@ -126,6 +128,7 @@
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 import org.junit.AfterClass;
+import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.rules.ExpectedException;
@@ -1943,22 +1946,102 @@
     return false;
   }
 
-  public static boolean assertProgramsEqual(Path expectedJar, Path actualJar) throws Exception {
+  public static boolean assertProgramsEqual(Path expectedJar, Path actualJar) throws IOException {
     if (filesAreEqual(expectedJar, actualJar)) {
       return true;
     }
-    ArchiveClassFileProvider expected = new ArchiveClassFileProvider(expectedJar);
-    ArchiveClassFileProvider actual = new ArchiveClassFileProvider(actualJar);
-    assertEquals(getSortedDescriptorList(expected), getSortedDescriptorList(actual));
-    for (String descriptor : expected.getClassDescriptors()) {
-      assertArrayEquals(
-          "Class " + descriptor + " differs",
-          getClassAsBytes(expected, descriptor),
-          getClassAsBytes(actual, descriptor));
-    }
+    assertIdenticalInspectors(new CodeInspector(expectedJar), new CodeInspector(actualJar));
     return false;
   }
 
+  public static void assertIdenticalInspectors(CodeInspector inspector1, CodeInspector inspector2) {
+    Set<String> classes1Set =
+        inspector1.allClasses().stream()
+            .map(c -> c.getDexProgramClass().getType().toString())
+            .collect(Collectors.toSet());
+    Set<String> classes2Set =
+        inspector2.allClasses().stream()
+            .map(c -> c.getDexProgramClass().getType().toString())
+            .collect(Collectors.toSet());
+    SetView<String> classUnion = Sets.union(classes1Set, classes2Set);
+    Assert.assertEquals(
+        "Inspector 1 contains more classes",
+        Collections.emptySet(),
+        Sets.difference(classUnion, classes1Set));
+    Assert.assertEquals(
+        "Inspector 2 contains more classes",
+        Collections.emptySet(),
+        Sets.difference(classUnion, classes2Set));
+    Map<DexEncodedMethod, DexEncodedMethod> diffs = new IdentityHashMap<>();
+    for (FoundClassSubject clazz1 : inspector1.allClasses()) {
+      ClassSubject clazz = inspector2.clazz(clazz1.getOriginalName());
+      assertTrue(clazz.isPresent());
+      FoundClassSubject clazz2 = clazz.asFoundClassSubject();
+      Set<String> methods1 =
+          clazz1.allMethods().stream()
+              .map(FoundMethodSubject::toString)
+              .collect(Collectors.toSet());
+      Set<String> methods2 =
+          clazz2.allMethods().stream()
+              .map(FoundMethodSubject::toString)
+              .collect(Collectors.toSet());
+      SetView<String> union = Sets.union(methods1, methods2);
+      Assert.assertEquals(
+          "Inspector 1 contains more methods",
+          Collections.emptySet(),
+          Sets.difference(union, methods1));
+      Assert.assertEquals(
+          "Inspector 2 contains more methods",
+          Collections.emptySet(),
+          Sets.difference(union, methods2));
+      Assert.assertEquals(clazz1.allMethods().size(), clazz2.allMethods().size());
+      for (FoundMethodSubject method1 : clazz1.allMethods()) {
+        MethodSubject method = clazz2.method(method1.asMethodReference());
+        assertTrue(method.isPresent());
+        FoundMethodSubject method2 = method.asFoundMethodSubject();
+        if (method1.hasCode()) {
+          assertTrue(method2.hasCode());
+          if (!(method1
+              .getMethod()
+              .getCode()
+              .toString()
+              .equals(method2.getMethod().getCode().toString()))) {
+            diffs.put(method1.getMethod(), method2.getMethod());
+          }
+        }
+      }
+    }
+    assertTrue(printDiffs(diffs), diffs.isEmpty());
+  }
+
+  private static String printDiffs(Map<DexEncodedMethod, DexEncodedMethod> diffs) {
+    StringBuilder sb = new StringBuilder();
+    sb.append("The following methods had differences from one dex file to the other (")
+        .append(diffs.size())
+        .append("):\n");
+    diffs.forEach(
+        (m1, m2) -> {
+          sb.append(m1.toSourceString()).append("\n");
+          String[] lines1 = m1.getCode().toString().split("\n");
+          String[] lines2 = m2.getCode().toString().split("\n");
+          if (lines1.length != lines2.length) {
+            sb.append("Different number of lines.");
+            sb.append("\n");
+          } else {
+            for (int i = 0; i < lines1.length; i++) {
+              if (!lines1[i].equals(lines2[i])) {
+                sb.append(lines1[i]);
+                sb.append("\n");
+                sb.append(lines2[i]);
+                sb.append("\n");
+                return;
+              }
+            }
+          }
+        });
+    return sb.toString();
+  }
+
   public static boolean filesAreEqual(Path file1, Path file2) throws IOException {
     long size = Files.size(file1);
     long sizeOther = Files.size(file2);
@@ -1981,15 +2064,4 @@
     }
     return byteRead1 == byteRead2;
   }
-
-  private static List<String> getSortedDescriptorList(ArchiveClassFileProvider inputJar) {
-    ArrayList<String> descriptorList = new ArrayList<>(inputJar.getClassDescriptors());
-    Collections.sort(descriptorList);
-    return descriptorList;
-  }
-
-  private static byte[] getClassAsBytes(ArchiveClassFileProvider inputJar, String descriptor)
-      throws Exception {
-    return toByteArray(inputJar.getProgramResource(descriptor).getByteStream());
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index efe353d..a476d13 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -509,7 +509,7 @@
   }
 
   public CR assertNoInfoMessages() {
-    getDiagnosticMessages().assertInfosCount(0);
+    getDiagnosticMessages().assertNoInfos();
     return self();
   }
 
@@ -529,7 +529,7 @@
   }
 
   public CR assertNoWarningMessages() {
-    getDiagnosticMessages().assertWarningsCount(0);
+    getDiagnosticMessages().assertNoWarnings();
     return self();
   }
 
@@ -544,7 +544,7 @@
   }
 
   public CR assertNoErrorMessages() {
-    getDiagnosticMessages().assertErrorsCount(0);
+    getDiagnosticMessages().assertNoErrors();
     return self();
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index ac6ca8e..b315027 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -59,6 +59,7 @@
         options.testing.reportUnusedProguardConfigurationRules = true;
         options.horizontalClassMergerOptions().enable();
         options.horizontalClassMergerOptions().setEnableInterfaceMerging();
+        options.getOpenClosedInterfacesOptions().disallowOpenInterfaces();
       };
 
   final Backend backend;
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index dc4d111..2b6d46b 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1272,23 +1272,18 @@
     return runR8WithFullResult(command, optionsConsumer);
   }
 
-  public static void runR8WithoutResult(
-      R8Command command, Consumer<InternalOptions> optionsConsumer)
-      throws CompilationFailedException {
-    runAndBenchmarkR8WithoutResult(command, optionsConsumer, null);
-  }
-
   public static void runAndBenchmarkR8WithoutResult(
-      R8Command command,
+      R8Command.Builder commandBuilder,
       Consumer<InternalOptions> optionsConsumer,
       BenchmarkResults benchmarkResults)
       throws CompilationFailedException {
-    InternalOptions internalOptions = command.getInternalOptions();
-    optionsConsumer.accept(internalOptions);
     long start = 0;
     if (benchmarkResults != null) {
       start = System.nanoTime();
     }
+    R8Command command = commandBuilder.build();
+    InternalOptions internalOptions = command.getInternalOptions();
+    optionsConsumer.accept(internalOptions);
     R8.runForTesting(command.getInputApp(), internalOptions);
     if (benchmarkResults != null) {
       long end = System.nanoTime();
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassInstanceInitTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassInstanceInitTest.java
index a868cf1..784fce6 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassInstanceInitTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassInstanceInitTest.java
@@ -10,13 +10,18 @@
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -35,38 +40,88 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
+  private boolean isGreaterOrEqualToMockLevel() {
+    return parameters.isDexRuntime() && parameters.getApiLevel().isGreaterThanOrEqualTo(mockLevel);
+  }
+
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) {
+    testBuilder
+        .addProgramClasses(Main.class, TestClass.class)
+        .addLibraryClasses(LibraryClass.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .setMinApi(parameters.getApiLevel())
+        .addAndroidBuildVersion()
+        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(setMockApiLevelForClass(LibraryClass.class, mockLevel))
+        .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, mockLevel));
+  }
+
+  @Test
+  public void testD8Debug() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeTrue(
+        parameters.isDexRuntime()
+            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(isGreaterOrEqualToMockLevel(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput)
+        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(isGreaterOrEqualToMockLevel(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput)
+        .inspect(this::inspect);
+  }
+
   @Test
   public void testR8() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeFalse(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
-    boolean isMockApiLevel =
-        parameters.isDexRuntime() && parameters.getApiLevel().isGreaterThanOrEqualTo(mockLevel);
     testForR8(parameters.getBackend())
-        .addProgramClasses(Main.class, TestClass.class)
-        .addLibraryClasses(LibraryClass.class)
-        .addDefaultRuntimeLibrary(parameters)
-        .setMinApi(parameters.getApiLevel())
+        .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
-        .addAndroidBuildVersion()
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
-        .apply(setMockApiLevelForClass(LibraryClass.class, mockLevel))
-        .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, mockLevel))
         .enableInliningAnnotations()
         .compile()
-        .applyIf(isMockApiLevel, b -> b.addBootClasspathClasses(LibraryClass.class))
+        .applyIf(isGreaterOrEqualToMockLevel(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLinesIf(isMockApiLevel, "LibraryClass::foo")
-        .assertSuccessWithOutputLinesIf(!isMockApiLevel, "NoClassDefFoundError")
-        .inspect(
-            inspector ->
-                verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel))
-        .applyIf(
-            !isMockApiLevel
-                && parameters.isDexRuntime()
-                && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V7_0_0),
-            result -> result.assertStderrMatches(not(containsString("This dex file is invalid"))));
+        .apply(this::checkOutput)
+        .inspect(this::inspect);
+  }
+
+  private void checkOutput(SingleTestRunResult<?> runResult) {
+    if (isGreaterOrEqualToMockLevel()) {
+      runResult.assertSuccessWithOutputLines("LibraryClass::foo");
+    } else {
+      runResult.assertSuccessWithOutputLines("NoClassDefFoundError");
+    }
+    runResult.applyIf(
+        !isGreaterOrEqualToMockLevel()
+            && parameters.isDexRuntime()
+            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V7_0_0),
+        result -> result.assertStderrMatches(not(containsString("This dex file is invalid"))));
+  }
+
+  private void inspect(CodeInspector inspector) {
+    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel);
   }
 
   // Only present from api level 23.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java
index 916badd..8651a23 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java
@@ -6,13 +6,19 @@
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -31,28 +37,79 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
+  private boolean isGreaterOrEqualToMockLevel() {
+    return parameters.isDexRuntime() && parameters.getApiLevel().isGreaterThanOrEqualTo(mockLevel);
+  }
+
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) {
+    testBuilder
+        .addProgramClasses(Main.class)
+        .addLibraryClasses(LibraryClass.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .setMinApi(parameters.getApiLevel())
+        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(setMockApiLevelForClass(LibraryClass.class, mockLevel))
+        .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, mockLevel));
+  }
+
+  @Test
+  public void testD8Debug() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeTrue(
+        parameters.isDexRuntime()
+            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
+        .applyIf(isGreaterOrEqualToMockLevel(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .inspect(this::inspect)
+        .applyIf(isGreaterOrEqualToMockLevel(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
   @Test
   public void testR8() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeFalse(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
-    boolean isLibraryOnBootClassPath =
-        parameters.isDexRuntime()
-            && parameters.getRuntime().maxSupportedApiLevel().isGreaterThanOrEqualTo(mockLevel);
     testForR8(parameters.getBackend())
-        .addProgramClasses(Main.class)
-        .addLibraryClasses(LibraryClass.class)
-        .addDefaultRuntimeLibrary(parameters)
-        .setMinApi(parameters.getApiLevel())
+        .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
-        .apply(setMockApiLevelForClass(LibraryClass.class, mockLevel))
-        .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, mockLevel))
         .compile()
-        .applyIf(isLibraryOnBootClassPath, b -> b.addBootClasspathClasses(LibraryClass.class))
+        .inspect(this::inspect)
+        .applyIf(isGreaterOrEqualToMockLevel(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLinesIf(isLibraryOnBootClassPath, "Hello World");
+        .apply(this::checkOutput);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel);
+  }
+
+  private void checkOutput(SingleTestRunResult<?> runResult) {
+    runResult.assertSuccessWithOutputLinesIf(isGreaterOrEqualToMockLevel(), "Hello World");
+    runResult.assertFailureWithErrorThatThrowsIf(
+        !isGreaterOrEqualToMockLevel(), NoClassDefFoundError.class);
   }
 
   // Only present form api level 23.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java
index 51d34a9..1397da7 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java
@@ -8,14 +8,19 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -34,36 +39,88 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
+  private boolean isGreaterOrEqualToMockLevel() {
+    return parameters.isDexRuntime() && parameters.getApiLevel().isGreaterThanOrEqualTo(mockLevel);
+  }
+
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) {
+    testBuilder
+        .addProgramClasses(Main.class, TestClass.class)
+        .addLibraryClasses(LibraryClass.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .setMinApi(parameters.getApiLevel())
+        .addAndroidBuildVersion()
+        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(setMockApiLevelForClass(LibraryClass.class, mockLevel))
+        .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, mockLevel));
+  }
+
+  private boolean addToBootClasspath() {
+    return parameters.isDexRuntime()
+        && parameters.getRuntime().maxSupportedApiLevel().isGreaterThanOrEqualTo(mockLevel);
+  }
+
+  @Test
+  public void testD8Debug() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeTrue(
+        parameters.isDexRuntime()
+            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput)
+        .inspect(this::inspect);
+  }
+
   @Test
   public void testR8() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeFalse(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
-    boolean isMockApiLevel =
-        parameters.isDexRuntime() && parameters.getApiLevel().isGreaterThanOrEqualTo(mockLevel);
     testForR8(parameters.getBackend())
-        .addProgramClasses(Main.class, TestClass.class)
-        .addLibraryClasses(LibraryClass.class)
-        .addDefaultRuntimeLibrary(parameters)
-        .setMinApi(parameters.getApiLevel())
+        .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
-        .addAndroidBuildVersion()
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
-        .apply(setMockApiLevelForClass(LibraryClass.class, mockLevel))
-        .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, mockLevel))
         .enableInliningAnnotations()
         .compile()
-        .applyIf(
-            parameters.isDexRuntime()
-                && parameters.getRuntime().maxSupportedApiLevel().isGreaterThanOrEqualTo(mockLevel),
-            b -> b.addBootClasspathClasses(LibraryClass.class))
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLinesIf(isMockApiLevel, "LibraryClass::foo")
-        .assertSuccessWithOutputLinesIf(!isMockApiLevel, "Hello World")
-        .inspect(
-            inspector ->
-                verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel));
+        .apply(this::checkOutput)
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel);
+  }
+
+  private void checkOutput(SingleTestRunResult<?> runResult) {
+    if (isGreaterOrEqualToMockLevel()) {
+      runResult.assertSuccessWithOutputLines("LibraryClass::foo");
+    } else {
+      runResult.assertSuccessWithOutputLines("Hello World");
+    }
   }
 
   // Only present from api level 23.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockInheritedClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockInheritedClassTest.java
index 4f1c13b..3d6d30c 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockInheritedClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockInheritedClassTest.java
@@ -8,13 +8,18 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -33,36 +38,88 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
+  private boolean isGreaterOrEqualToMockLevel() {
+    return parameters.isDexRuntime() && parameters.getApiLevel().isGreaterThanOrEqualTo(mockLevel);
+  }
+
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) {
+    testBuilder
+        .addProgramClasses(Main.class, ProgramClass.class)
+        .addLibraryClasses(LibraryClass.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .setMinApi(parameters.getApiLevel())
+        .addAndroidBuildVersion()
+        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(setMockApiLevelForClass(LibraryClass.class, mockLevel))
+        .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, mockLevel));
+  }
+
+  private boolean addToBootClasspath() {
+    return parameters.isDexRuntime()
+        && parameters.getRuntime().maxSupportedApiLevel().isGreaterThanOrEqualTo(mockLevel);
+  }
+
+  @Test
+  public void testD8Debug() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeTrue(
+        parameters.isDexRuntime()
+            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput)
+        .inspect(this::inspect);
+  }
+
   @Test
   public void testR8() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeFalse(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
-    boolean isMockApiLevel =
-        parameters.isDexRuntime() && parameters.getApiLevel().isGreaterThanOrEqualTo(mockLevel);
     testForR8(parameters.getBackend())
-        .addProgramClasses(Main.class, ProgramClass.class)
-        .addLibraryClasses(LibraryClass.class)
-        .addDefaultRuntimeLibrary(parameters)
-        .setMinApi(parameters.getApiLevel())
+        .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
         .addKeepClassRules(ProgramClass.class)
-        .addAndroidBuildVersion()
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
-        .apply(setMockApiLevelForClass(LibraryClass.class, mockLevel))
-        .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, mockLevel))
         .compile()
-        .applyIf(
-            parameters.isDexRuntime()
-                && parameters.getRuntime().maxSupportedApiLevel().isGreaterThanOrEqualTo(mockLevel),
-            b -> b.addBootClasspathClasses(LibraryClass.class))
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLinesIf(isMockApiLevel, "ProgramClass::foo")
-        .assertSuccessWithOutputLinesIf(!isMockApiLevel, "Hello World")
-        .inspect(
-            inspector ->
-                verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel));
+        .apply(this::checkOutput)
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel);
+  }
+
+  private void checkOutput(SingleTestRunResult<?> runResult) {
+    if (isGreaterOrEqualToMockLevel()) {
+      runResult.assertSuccessWithOutputLines("ProgramClass::foo");
+    } else {
+      runResult.assertSuccessWithOutputLines("Hello World");
+    }
   }
 
   // Only present from api level 23.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockSuperChainClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockSuperChainClassTest.java
index 94eb314..c2a6d4d 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockSuperChainClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockSuperChainClassTest.java
@@ -8,13 +8,18 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -34,53 +39,113 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  @Test
-  public void testR8() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
-    boolean isMockApiLevel =
-        parameters.isDexRuntime() && parameters.getApiLevel().isGreaterThanOrEqualTo(mockApiLevel);
-    testForR8(parameters.getBackend())
+  private boolean isGreaterOrEqualToMockLevel() {
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(mockApiLevel);
+  }
+
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) {
+    testBuilder
         .addProgramClasses(Main.class, ProgramClass.class)
         .addLibraryClasses(LibraryClass.class, OtherLibraryClass.class, LibraryInterface.class)
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters.getApiLevel())
-        .addKeepMainRule(Main.class)
-        .addKeepClassRules(ProgramClass.class)
         .addAndroidBuildVersion()
         .apply(ApiModelingTestHelper::enableStubbingOfClasses)
         .apply(setMockApiLevelForClass(LibraryClass.class, lowerMockApiLevel))
         .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, lowerMockApiLevel))
         .apply(setMockApiLevelForClass(OtherLibraryClass.class, mockApiLevel))
         .apply(setMockApiLevelForDefaultInstanceInitializer(OtherLibraryClass.class, mockApiLevel))
-        .apply(setMockApiLevelForClass(LibraryInterface.class, lowerMockApiLevel))
+        .apply(setMockApiLevelForClass(LibraryInterface.class, lowerMockApiLevel));
+  }
+
+  private boolean addLibraryClassesToBootClasspath() {
+    return parameters.isDexRuntime()
+        && parameters.getRuntime().maxSupportedApiLevel().isGreaterThanOrEqualTo(lowerMockApiLevel);
+  }
+
+  private boolean addOtherLibraryClassesToBootClasspath() {
+    return parameters.isDexRuntime()
+        && parameters.getRuntime().maxSupportedApiLevel().isGreaterThanOrEqualTo(mockApiLevel);
+  }
+
+  @Test
+  public void testD8Debug() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeTrue(
+        parameters.isDexRuntime()
+            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
         .compile()
+        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
         .applyIf(
-            parameters.isDexRuntime()
-                && parameters
-                    .getRuntime()
-                    .maxSupportedApiLevel()
-                    .isGreaterThanOrEqualTo(lowerMockApiLevel),
+            addLibraryClassesToBootClasspath(),
             b -> b.addBootClasspathClasses(LibraryClass.class, LibraryInterface.class))
         .applyIf(
-            parameters.isDexRuntime()
-                && parameters
-                    .getRuntime()
-                    .maxSupportedApiLevel()
-                    .isGreaterThanOrEqualTo(mockApiLevel),
+            addOtherLibraryClassesToBootClasspath(),
             b -> b.addBootClasspathClasses(OtherLibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLinesIf(isMockApiLevel, "ProgramClass::foo")
-        .assertSuccessWithOutputLinesIf(!isMockApiLevel, "Hello World")
-        .inspect(
-            inspector -> {
-              verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(lowerMockApiLevel);
-              verifyThat(inspector, parameters, LibraryInterface.class)
-                  .stubbedUntil(lowerMockApiLevel);
-              verifyThat(inspector, parameters, OtherLibraryClass.class).stubbedUntil(mockApiLevel);
-            });
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(
+            addLibraryClassesToBootClasspath(),
+            b -> b.addBootClasspathClasses(LibraryClass.class, LibraryInterface.class))
+        .applyIf(
+            addOtherLibraryClassesToBootClasspath(),
+            b -> b.addBootClasspathClasses(OtherLibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput)
+        .inspect(this::inspect);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeFalse(
+        parameters.isDexRuntime()
+            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForR8(parameters.getBackend())
+        .apply(this::setupTestBuilder)
+        .addKeepMainRule(Main.class)
+        .addKeepClassRules(ProgramClass.class)
+        .compile()
+        .applyIf(
+            addLibraryClassesToBootClasspath(),
+            b -> b.addBootClasspathClasses(LibraryClass.class, LibraryInterface.class))
+        .applyIf(
+            addOtherLibraryClassesToBootClasspath(),
+            b -> b.addBootClasspathClasses(OtherLibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(lowerMockApiLevel);
+    verifyThat(inspector, parameters, LibraryInterface.class).stubbedUntil(lowerMockApiLevel);
+    verifyThat(inspector, parameters, OtherLibraryClass.class).stubbedUntil(mockApiLevel);
+  }
+
+  private void checkOutput(SingleTestRunResult<?> runResult) {
+    if (isGreaterOrEqualToMockLevel()) {
+      runResult.assertSuccessWithOutputLines("ProgramClass::foo");
+    } else {
+      runResult.assertSuccessWithOutputLines("Hello World");
+    }
   }
 
   // Only present from api level 23.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoOutlineForFullyMockedTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoOutlineForFullyMockedTest.java
index db72c72..85429ea 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoOutlineForFullyMockedTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoOutlineForFullyMockedTest.java
@@ -9,17 +9,23 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoReturnTypeStrengthening;
+import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.lang.reflect.Method;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -39,51 +45,86 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    Method methodOn23 = LibraryClass.class.getDeclaredMethod("methodOn23");
+    testBuilder
+        .addProgramClasses(Main.class)
+        .addLibraryClasses(LibraryClass.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .setMinApi(parameters.getApiLevel())
+        .addAndroidBuildVersion()
+        .apply(setMockApiLevelForClass(LibraryClass.class, libraryApiLevel))
+        .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, libraryApiLevel))
+        .apply(setMockApiLevelForMethod(methodOn23, libraryApiLevel))
+        .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+        .apply(ApiModelingTestHelper::enableStubbingOfClasses);
+  }
+
+  private boolean addToBootClasspath() {
+    return parameters.isDexRuntime()
+        && parameters.getRuntime().maxSupportedApiLevel().isGreaterThanOrEqualTo(libraryApiLevel);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeTrue(
+        parameters.isDexRuntime()
+            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    String result;
+    if (parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(libraryApiLevel)) {
+      result = StringUtils.lines("LibraryClass::methodOn23", "Hello World");
+    } else {
+      result = StringUtils.lines("Hello World");
+    }
+    testForD8()
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(result)
+        // TODO(b/213552119): Add stubs to D8
+        .inspect(inspector -> inspect(inspector, false));
+  }
+
   @Test
   public void testR8() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeFalse(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
-    boolean isLibraryApiLevel =
-        parameters.isDexRuntime()
-            && parameters.getApiLevel().isGreaterThanOrEqualTo(libraryApiLevel);
-    Method methodOn23 = LibraryClass.class.getDeclaredMethod("methodOn23");
-    Method mainMethod = Main.class.getDeclaredMethod("main", String[].class);
     testForR8(parameters.getBackend())
-        .addProgramClasses(Main.class)
-        .addLibraryClasses(LibraryClass.class)
-        .addDefaultRuntimeLibrary(parameters)
-        .setMinApi(parameters.getApiLevel())
+        .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
-        .addAndroidBuildVersion()
-        .apply(setMockApiLevelForClass(LibraryClass.class, libraryApiLevel))
-        .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, libraryApiLevel))
-        .apply(setMockApiLevelForMethod(methodOn23, libraryApiLevel))
-        .apply(ApiModelingTestHelper::enableOutliningOfMethods)
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
         .enableInliningAnnotations()
         .enableNoReturnTypeStrengtheningAnnotations()
         .compile()
-        .applyIf(
-            parameters.isDexRuntime()
-                && parameters
-                    .getRuntime()
-                    .maxSupportedApiLevel()
-                    .isGreaterThanOrEqualTo(libraryApiLevel),
-            b -> b.addBootClasspathClasses(LibraryClass.class))
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLinesIf(!isLibraryApiLevel, "Hello World")
-        .assertSuccessWithOutputLinesIf(
-            isLibraryApiLevel, "LibraryClass::methodOn23", "Hello World")
-        .inspect(
-            inspector -> {
-              assertThat(inspector.method(mainMethod), isPresent());
-              // TODO(b/211720912): We should never outline since the library class and method is
-              //  introduced at the same level.
-              verifyThat(inspector, parameters, methodOn23).isNotOutlinedFrom(mainMethod);
-              verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(libraryApiLevel);
-            });
+        .apply(this::checkOutput)
+        .inspect(inspector -> inspect(inspector, true));
+  }
+
+  private void checkOutput(SingleTestRunResult<?> runResult) {
+    if (parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(libraryApiLevel)) {
+      runResult.assertSuccessWithOutputLines("LibraryClass::methodOn23", "Hello World");
+    } else {
+      runResult.assertSuccessWithOutputLines("Hello World");
+    }
+  }
+
+  private void inspect(CodeInspector inspector, boolean canStub) throws Exception {
+    Method methodOn23 = LibraryClass.class.getDeclaredMethod("methodOn23");
+    Method mainMethod = Main.class.getDeclaredMethod("main", String[].class);
+    assertThat(inspector.method(mainMethod), isPresent());
+    verifyThat(inspector, parameters, methodOn23).isNotOutlinedFrom(mainMethod);
+    if (canStub) {
+      verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(libraryApiLevel);
+    } else {
+      assertThat(inspector.clazz(LibraryClass.class), not(isPresent()));
+    }
   }
 
   // Only present from api level 23.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java
index 67fad04..2e96c90 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java
@@ -14,9 +14,12 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
@@ -45,41 +48,59 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
+  private Method addedOn23() throws Exception {
+    return LibraryClass.class.getMethod("addedOn23");
+  }
+
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    testBuilder
+        .addProgramClasses(Main.class, TestClass.class)
+        .addLibraryClasses(LibraryClass.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .setMinApi(parameters.getApiLevel())
+        .addAndroidBuildVersion()
+        .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
+        .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, classApiLevel))
+        .apply(setMockApiLevelForMethod(addedOn23(), methodApiLevel))
+        .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+        .apply(ApiModelingTestHelper::disableStubbingOfClasses);
+  }
+
+  public boolean addToBootClasspath() {
+    return parameters.isDexRuntime()
+        && parameters.getRuntime().maxSupportedApiLevel().isGreaterThanOrEqualTo(classApiLevel);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeTrue(
+        parameters.isDexRuntime()
+            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    testForD8()
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput)
+        // TODO(b/213552119): Assert that we did not outline any methods.
+        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses);
+  }
+
   @Test
   public void testR8() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeFalse(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
-    boolean isMethodApiLevel =
-        parameters.isDexRuntime()
-            && parameters.getApiLevel().isGreaterThanOrEqualTo(methodApiLevel);
-    Method adeddOn23 = LibraryClass.class.getMethod("addedOn23");
     testForR8(parameters.getBackend())
-        .addProgramClasses(Main.class, TestClass.class)
-        .addLibraryClasses(LibraryClass.class)
-        .addDefaultRuntimeLibrary(parameters)
-        .setMinApi(parameters.getApiLevel())
+        .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
-        .addAndroidBuildVersion()
-        .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
-        .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, classApiLevel))
-        .apply(setMockApiLevelForMethod(adeddOn23, methodApiLevel))
-        .apply(ApiModelingTestHelper::enableOutliningOfMethods)
-        .apply(ApiModelingTestHelper::disableStubbingOfClasses)
         .enableInliningAnnotations()
         .compile()
-        .applyIf(
-            parameters.isDexRuntime()
-                && parameters
-                    .getRuntime()
-                    .maxSupportedApiLevel()
-                    .isGreaterThanOrEqualTo(classApiLevel),
-            b -> b.addBootClasspathClasses(LibraryClass.class))
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLinesIf(!isMethodApiLevel, "Hello World")
-        .assertSuccessWithOutputLinesIf(
-            isMethodApiLevel, "LibraryClass::addedOn23", "LibraryClass::addedOn23", "Hello World")
+        .apply(this::checkOutput)
         .inspect(
             inspector -> {
               int classCount =
@@ -88,7 +109,7 @@
                       : 3;
               assertEquals(classCount, inspector.allClasses().size());
               Method testMethod = TestClass.class.getDeclaredMethod("test");
-              verifyThat(inspector, parameters, adeddOn23)
+              verifyThat(inspector, parameters, addedOn23())
                   .isOutlinedFromUntil(testMethod, methodApiLevel);
               if (parameters.isDexRuntime()
                   && parameters.getApiLevel().isLessThan(methodApiLevel)) {
@@ -123,6 +144,16 @@
             });
   }
 
+  private void checkOutput(SingleTestRunResult<?> runResult) {
+    if (parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(methodApiLevel)) {
+      runResult.assertSuccessWithOutputLines(
+          "LibraryClass::addedOn23", "LibraryClass::addedOn23", "Hello World");
+    } else {
+      runResult.assertSuccessWithOutputLines("Hello World");
+    }
+  }
+
   // Only present from api level 19.
   public static class LibraryClass {
 
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java
index 97bced2..497e0fd 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java
@@ -11,9 +11,12 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
@@ -43,24 +46,12 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  @Test
-  public void testR8() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
-    boolean beforeFirstApiMethodLevel =
-        parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(firstMethodApiLevel);
-    boolean afterSecondApiMethodLevel =
-        parameters.isDexRuntime()
-            && parameters.getApiLevel().isGreaterThanOrEqualTo(secondMethodApiLevel);
-    boolean betweenMethodApiLevels = !beforeFirstApiMethodLevel && !afterSecondApiMethodLevel;
-    testForR8(parameters.getBackend())
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    testBuilder
         .addProgramClasses(Main.class, TestClass.class)
         .addLibraryClasses(LibraryClass.class, OtherLibraryClass.class)
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters.getApiLevel())
-        .addKeepMainRule(Main.class)
         .addAndroidBuildVersion()
         .apply(setMockApiLevelForClass(LibraryClass.class, libraryClassApiLevel))
         .apply(
@@ -82,30 +73,51 @@
             setMockApiLevelForMethod(
                 OtherLibraryClass.class.getMethod("addedOn27"), secondMethodApiLevel))
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
-        .apply(ApiModelingTestHelper::disableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::disableStubbingOfClasses);
+  }
+
+  public boolean addToBootClasspath() {
+    return parameters.isDexRuntime()
+        && parameters
+            .getRuntime()
+            .maxSupportedApiLevel()
+            .isGreaterThanOrEqualTo(libraryClassApiLevel);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeTrue(
+        parameters.isDexRuntime()
+            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    testForD8(parameters.getBackend())
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(
+            addToBootClasspath(),
+            b -> b.addBootClasspathClasses(LibraryClass.class, OtherLibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput)
+        // TODO(b/213552119): Assert that we did not outline any methods.
+        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeFalse(
+        parameters.isDexRuntime()
+            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForR8(parameters.getBackend())
+        .apply(this::setupTestBuilder)
+        .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .compile()
         .applyIf(
-            parameters.isDexRuntime()
-                && parameters
-                    .getRuntime()
-                    .maxSupportedApiLevel()
-                    .isGreaterThanOrEqualTo(libraryClassApiLevel),
+            addToBootClasspath(),
             b -> b.addBootClasspathClasses(LibraryClass.class, OtherLibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLinesIf(beforeFirstApiMethodLevel, "Hello World")
-        .assertSuccessWithOutputLinesIf(
-            betweenMethodApiLevels,
-            "LibraryClass::addedOn23",
-            "OtherLibraryClass::addedOn23",
-            "Hello World")
-        .assertSuccessWithOutputLinesIf(
-            afterSecondApiMethodLevel,
-            "LibraryClass::addedOn23",
-            "LibraryClass::addedOn27",
-            "OtherLibraryClass::addedOn23",
-            "OtherLibraryClass::addedOn27",
-            "Hello World")
+        .apply(this::checkOutput)
         .inspect(
             inspector -> {
               // No need to check further on CF.
@@ -166,6 +178,27 @@
             });
   }
 
+  private void checkOutput(SingleTestRunResult<?> runResult) {
+    boolean beforeFirstApiMethodLevel =
+        parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(firstMethodApiLevel);
+    boolean afterSecondApiMethodLevel =
+        parameters.isDexRuntime()
+            && parameters.getApiLevel().isGreaterThanOrEqualTo(secondMethodApiLevel);
+    if (beforeFirstApiMethodLevel) {
+      runResult.assertSuccessWithOutputLines("Hello World");
+    } else if (afterSecondApiMethodLevel) {
+      runResult.assertSuccessWithOutputLines(
+          "LibraryClass::addedOn23",
+          "LibraryClass::addedOn27",
+          "OtherLibraryClass::addedOn23",
+          "OtherLibraryClass::addedOn27",
+          "Hello World");
+    } else {
+      runResult.assertSuccessWithOutputLines(
+          "LibraryClass::addedOn23", "OtherLibraryClass::addedOn23", "Hello World");
+    }
+  }
+
   // Only present from api level 19.
   public static class LibraryClass {
 
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java
index d71eec4..89c2f76 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java
@@ -9,8 +9,11 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
@@ -37,48 +40,76 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
+  public Method apiMethod() throws Exception {
+    return LibraryClass.class.getDeclaredMethod("foo");
+  }
+
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    testBuilder
+        .addProgramClasses(Main.class, TestClass.class)
+        .addLibraryClasses(LibraryClass.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .setMinApi(parameters.getApiLevel())
+        .addAndroidBuildVersion()
+        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+        .apply(setMockApiLevelForClass(LibraryClass.class, libraryClassLevel))
+        .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, libraryClassLevel))
+        .apply(setMockApiLevelForMethod(apiMethod(), libraryMethodLevel));
+  }
+
+  public boolean addToBootClasspath() {
+    return parameters.isDexRuntime()
+        && parameters.getRuntime().maxSupportedApiLevel().isGreaterThanOrEqualTo(libraryClassLevel);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeTrue(
+        parameters.isDexRuntime()
+            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    testForD8(parameters.getBackend())
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput)
+        // TODO(b/213552119): Assert that we did not outline any methods.
+        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses);
+  }
+
   @Test
   public void testR8() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeFalse(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
-    boolean libraryClassNotStubbed =
-        parameters.isDexRuntime()
-            && parameters.getApiLevel().isGreaterThanOrEqualTo(libraryClassLevel);
-    Method apiMethod = LibraryClass.class.getDeclaredMethod("foo");
     testForR8(parameters.getBackend())
-        .addProgramClasses(Main.class, TestClass.class)
-        .addLibraryClasses(LibraryClass.class)
-        .addDefaultRuntimeLibrary(parameters)
-        .setMinApi(parameters.getApiLevel())
+        .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
-        .addAndroidBuildVersion()
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
-        .apply(ApiModelingTestHelper::enableOutliningOfMethods)
-        .apply(setMockApiLevelForClass(LibraryClass.class, libraryClassLevel))
-        .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, libraryClassLevel))
-        .apply(setMockApiLevelForMethod(apiMethod, libraryMethodLevel))
         .compile()
-        .applyIf(
-            parameters.isDexRuntime()
-                && parameters
-                    .getRuntime()
-                    .maxSupportedApiLevel()
-                    .isGreaterThanOrEqualTo(libraryClassLevel),
-            b -> b.addBootClasspathClasses(LibraryClass.class))
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLinesIf(libraryClassNotStubbed, "LibraryClass::foo")
-        .assertSuccessWithOutputLinesIf(!libraryClassNotStubbed, "Hello World")
+        .apply(this::checkOutput)
         .inspect(
             inspector -> {
               verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(libraryClassLevel);
-              verifyThat(inspector, parameters, apiMethod)
+              verifyThat(inspector, parameters, apiMethod())
                   .isOutlinedFromUntil(
                       Main.class.getDeclaredMethod("main", String[].class), libraryMethodLevel);
             });
   }
 
+  private void checkOutput(SingleTestRunResult<?> runResult) {
+    if (parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(libraryClassLevel)) {
+      runResult.assertSuccessWithOutputLines("LibraryClass::foo");
+    } else {
+      runResult.assertSuccessWithOutputLines("Hello World");
+    }
+  }
+
   // Only present from api level 23.
   public static class LibraryClass {
 
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java
index b537125..8ee8ad4 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java
@@ -14,9 +14,12 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
@@ -45,56 +48,69 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
+  private Method addedOn23() throws Exception {
+    return LibraryClass.class.getMethod("addedOn23");
+  }
+
+  private Method addedOn27() throws Exception {
+    return LibraryClass.class.getMethod("addedOn27");
+  }
+
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    testBuilder
+        .addProgramClasses(Main.class, TestClass.class)
+        .addLibraryClasses(LibraryClass.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .setMinApi(parameters.getApiLevel())
+        .addAndroidBuildVersion()
+        .apply(setMockApiLevelForClass(LibraryClass.class, initialLibraryMockLevel))
+        .apply(
+            setMockApiLevelForDefaultInstanceInitializer(
+                LibraryClass.class, initialLibraryMockLevel))
+        .apply(setMockApiLevelForMethod(addedOn23(), initialLibraryMockLevel))
+        .apply(setMockApiLevelForMethod(addedOn27(), finalLibraryMethodLevel))
+        .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+        .apply(ApiModelingTestHelper::disableStubbingOfClasses);
+  }
+
+  public boolean addToBootClasspath() {
+    return parameters.isDexRuntime()
+        && parameters
+            .getRuntime()
+            .maxSupportedApiLevel()
+            .isGreaterThanOrEqualTo(initialLibraryMockLevel);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeTrue(
+        parameters.isDexRuntime()
+            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    testForD8(parameters.getBackend())
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput)
+        // TODO(b/213552119): Assert that we did not outline any methods.
+        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses);
+  }
+
   @Test
   public void testR8() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeFalse(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
-    boolean preMockApis =
-        parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(initialLibraryMockLevel);
-    boolean postMockApis =
-        !preMockApis && parameters.getApiLevel().isGreaterThanOrEqualTo(finalLibraryMethodLevel);
-    boolean betweenMockApis = !preMockApis && !postMockApis;
-    Method addedOn23 = LibraryClass.class.getMethod("addedOn23");
-    Method addedOn27 = LibraryClass.class.getMethod("addedOn27");
     testForR8(parameters.getBackend())
-        .addProgramClasses(Main.class, TestClass.class)
-        .addLibraryClasses(LibraryClass.class)
-        .addDefaultRuntimeLibrary(parameters)
-        .setMinApi(parameters.getApiLevel())
+        .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
-        .addAndroidBuildVersion()
-        .apply(setMockApiLevelForClass(LibraryClass.class, initialLibraryMockLevel))
-        .apply(
-            setMockApiLevelForDefaultInstanceInitializer(
-                LibraryClass.class, initialLibraryMockLevel))
-        .apply(setMockApiLevelForMethod(addedOn23, initialLibraryMockLevel))
-        .apply(setMockApiLevelForMethod(addedOn27, finalLibraryMethodLevel))
-        .apply(ApiModelingTestHelper::enableOutliningOfMethods)
-        .apply(ApiModelingTestHelper::disableStubbingOfClasses)
         .enableInliningAnnotations()
         .compile()
-        .applyIf(
-            parameters.isDexRuntime()
-                && parameters
-                    .getRuntime()
-                    .maxSupportedApiLevel()
-                    .isGreaterThanOrEqualTo(initialLibraryMockLevel),
-            b -> b.addBootClasspathClasses(LibraryClass.class))
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLinesIf(preMockApis, "Hello World")
-        .assertSuccessWithOutputLinesIf(
-            betweenMockApis,
-            "LibraryClass::addedOn23",
-            "LibraryClass::missingAndReferenced",
-            "Hello World")
-        .assertSuccessWithOutputLinesIf(
-            postMockApis,
-            "LibraryClass::addedOn23",
-            "LibraryClass::missingAndReferenced",
-            "LibraryCLass::addedOn27",
-            "Hello World")
+        .apply(this::checkOutput)
         .inspect(
             inspector -> {
               // No need to check further on CF.
@@ -115,8 +131,8 @@
                                       .matches(methodSubject))
                       .findFirst();
               assertFalse(synthesizedMissingNotReferenced.isPresent());
-              verifyThat(inspector, parameters, addedOn23).isNotOutlinedFrom(testMethod);
-              verifyThat(inspector, parameters, addedOn27)
+              verifyThat(inspector, parameters, addedOn23()).isNotOutlinedFrom(testMethod);
+              verifyThat(inspector, parameters, addedOn27())
                   .isOutlinedFromUntil(testMethod, finalLibraryMethodLevel);
               verifyThat(
                       inspector,
@@ -131,6 +147,25 @@
             });
   }
 
+  private void checkOutput(SingleTestRunResult<?> runResult) {
+    boolean preMockApis =
+        parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(initialLibraryMockLevel);
+    boolean postMockApis =
+        !preMockApis && parameters.getApiLevel().isGreaterThanOrEqualTo(finalLibraryMethodLevel);
+    if (preMockApis) {
+      runResult.assertSuccessWithOutputLines("Hello World");
+    } else if (postMockApis) {
+      runResult.assertSuccessWithOutputLines(
+          "LibraryClass::addedOn23",
+          "LibraryClass::missingAndReferenced",
+          "LibraryCLass::addedOn27",
+          "Hello World");
+    } else {
+      runResult.assertSuccessWithOutputLines(
+          "LibraryClass::addedOn23", "LibraryClass::missingAndReferenced", "Hello World");
+    }
+  }
+
   // Only present from api level 23.
   public static class LibraryClass {
 
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodProtectedTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodProtectedTest.java
index 33a73cf..4428a33 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodProtectedTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodProtectedTest.java
@@ -8,10 +8,13 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
@@ -41,62 +44,93 @@
     return getTestParameters().withAllRuntimes().build();
   }
 
-  @Test
-  public void testR8() throws Exception {
-    // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
-    Method[] apiMethods =
-        new Method[] {
-          LibraryClass.class.getDeclaredMethod("addedOn27"),
-          LibraryClass.class.getDeclaredMethod("alsoAddedOn27"),
-          LibraryClass.class.getDeclaredMethod("superInvokeOn27")
-        };
-    AndroidApiLevel runApiLevel =
-        parameters.isCfRuntime()
-            ? AndroidApiLevel.B
-            : parameters.getRuntime().maxSupportedApiLevel();
-    boolean willInvokeLibraryMethods =
-        parameters.isDexRuntime() && runApiLevel.isGreaterThanOrEqualTo(methodApiLevel);
-    testForR8(parameters.getBackend())
+  private AndroidApiLevel runApiLevel() {
+    return parameters.isCfRuntime()
+        ? AndroidApiLevel.B
+        : parameters.getRuntime().maxSupportedApiLevel();
+  }
+
+  private Method[] apiMethods() throws Exception {
+    return new Method[] {
+      LibraryClass.class.getDeclaredMethod("addedOn27"),
+      LibraryClass.class.getDeclaredMethod("alsoAddedOn27"),
+      LibraryClass.class.getDeclaredMethod("superInvokeOn27")
+    };
+  }
+
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    testBuilder
         .addLibraryClasses(LibraryClass.class)
         .addLibraryFiles(
             parameters.isCfRuntime()
                 ? ToolHelper.getJava8RuntimeJar()
-                : ToolHelper.getFirstSupportedAndroidJar(runApiLevel))
+                : ToolHelper.getFirstSupportedAndroidJar(runApiLevel()))
         .addProgramClassFileData(
             transformer(TestClass.class).setClassDescriptor(TESTCLASS_DESCRIPTOR).transform(),
             transformer(Main.class)
                 .replaceClassDescriptorInMethodInstructions(
                     descriptor(TestClass.class), TESTCLASS_DESCRIPTOR)
                 .transform())
-        .addKeepMainRule(Main.class)
         .setMinApi(AndroidApiLevel.B)
-        .addAndroidBuildVersion(runApiLevel)
+        .addAndroidBuildVersion(runApiLevel())
         .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
         .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, classApiLevel))
         .apply(
             builder -> {
-              for (Method apiMethod : apiMethods) {
+              for (Method apiMethod : apiMethods()) {
                 setMockApiLevelForMethod(apiMethod, methodApiLevel).accept(builder);
               }
             })
-        .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+        .apply(ApiModelingTestHelper::enableOutliningOfMethods);
+  }
+
+  public boolean addToBootClasspath() {
+    return parameters.isDexRuntime() && runApiLevel().isGreaterThanOrEqualTo(methodApiLevel);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeTrue(
+        parameters.isDexRuntime()
+            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    testForD8()
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeFalse(
+        parameters.isDexRuntime()
+            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForR8(parameters.getBackend())
+        .apply(this::setupTestBuilder)
+        .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .compile()
-        .applyIf(willInvokeLibraryMethods, b -> b.addBootClasspathClasses(LibraryClass.class))
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLinesIf(!willInvokeLibraryMethods, "Not calling API")
-        .assertSuccessWithOutputLinesIf(
-            willInvokeLibraryMethods,
-            "Could not access LibraryClass::addedOn27",
-            "LibraryClass::addedOn27",
-            "LibraryClass::addedOn27",
-            "LibraryCLass::alsoAddedOn27",
-            "TestClass::superInvokeOn27",
-            "LibraryCLass::superInvokeOn27");
+        .apply(this::checkOutput);
+  }
+
+  public void checkOutput(SingleTestRunResult<?> runResult) {
+    if (parameters.isDexRuntime() && runApiLevel().isGreaterThanOrEqualTo(methodApiLevel)) {
+      runResult.assertSuccessWithOutputLines(
+          "Could not access LibraryClass::addedOn27",
+          "LibraryClass::addedOn27",
+          "LibraryClass::addedOn27",
+          "LibraryCLass::alsoAddedOn27",
+          "TestClass::superInvokeOn27",
+          "LibraryCLass::superInvokeOn27");
+    } else {
+      runResult.assertSuccessWithOutputLines("Not calling API");
+    }
   }
 
   // Only present from api level 23.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodUnknownTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodUnknownTest.java
index 0e9a5b3..2461448 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodUnknownTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodUnknownTest.java
@@ -5,16 +5,17 @@
 package com.android.tools.r8.apimodel;
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -33,38 +34,63 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) {
+    testBuilder
+        .addLibraryClasses(LibraryClass.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .addProgramClasses(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .addAndroidBuildVersion()
+        .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
+        .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+        .apply(ApiModelingTestHelper::disableStubbingOfClasses);
+  }
+
+  public boolean addToBootClasspath() {
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(classApiLevel);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeTrue(
+        parameters.isDexRuntime()
+            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    testForD8()
+        .apply(this::setupTestBuilder)
+        .compile()
+        // Assert that we did not outline any methods.
+        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
   @Test
   public void testR8() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeFalse(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
-    boolean willInvokeLibraryMethods =
-        parameters.isDexRuntime() && parameters.getApiLevel().isGreaterThanOrEqualTo(classApiLevel);
     testForR8(parameters.getBackend())
-        .addLibraryClasses(LibraryClass.class)
-        .addDefaultRuntimeLibrary(parameters)
-        .addProgramClasses(Main.class)
+        .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
-        .setMinApi(parameters.getApiLevel())
-        .addAndroidBuildVersion()
-        .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
-        .apply(ApiModelingTestHelper::enableOutliningOfMethods)
-        .apply(ApiModelingTestHelper::disableStubbingOfClasses)
         .compile()
-        .inspect(
-            inspector -> {
-              // Assert that we did not outline any methods.
-              assertEquals(
-                  0,
-                  inspector.allClasses().stream()
-                      .filter(FoundClassSubject::isCompilerSynthesized)
-                      .count());
-            })
-        .applyIf(willInvokeLibraryMethods, b -> b.addBootClasspathClasses(LibraryClass.class))
+        // Assert that we did not outline any methods.
+        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLinesIf(!willInvokeLibraryMethods, "Not calling API")
-        .assertSuccessWithOutputLinesIf(willInvokeLibraryMethods, "LibraryClass::unknownApiLevel");
+        .apply(this::checkOutput);
+  }
+
+  private void checkOutput(SingleTestRunResult<?> runResult) {
+    if (parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(classApiLevel)) {
+      runResult.assertSuccessWithOutputLines("LibraryClass::unknownApiLevel");
+    } else {
+      runResult.assertSuccessWithOutputLines("Not calling API");
+    }
   }
 
   // Only present from api level 23.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlinePackagePrivateTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlinePackagePrivateTest.java
index f4fe8d7..9e8115d 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlinePackagePrivateTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlinePackagePrivateTest.java
@@ -6,19 +6,18 @@
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -45,6 +44,7 @@
 
   @Test
   public void testD8BootClassPath() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
     assumeTrue(parameters.isDexRuntime());
     assumeTrue(parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
     compileOnD8()
@@ -72,6 +72,37 @@
         .compile();
   }
 
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    testBuilder
+        .addLibraryClasses(LibraryClass.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .addProgramClasses(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .addAndroidBuildVersion()
+        .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
+        .apply(
+            setMockApiLevelForMethod(
+                LibraryClass.class.getDeclaredMethod("addedOn10"), methodApiLevel))
+        .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+        .apply(ApiModelingTestHelper::disableStubbingOfClasses);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeTrue(
+        parameters.isDexRuntime()
+            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    testForD8()
+        .apply(this::setupTestBuilder)
+        .compile()
+        // Assert that we did not outline any methods.
+        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
+        .applyIf(willInvokeLibraryMethods(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkResultOnBootClassPath);
+  }
+
   @Test
   public void testR8() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
@@ -79,29 +110,12 @@
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
     testForR8(parameters.getBackend())
-        .addLibraryClasses(LibraryClass.class)
-        .addDefaultRuntimeLibrary(parameters)
-        .addProgramClasses(Main.class)
+        .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
-        .setMinApi(parameters.getApiLevel())
         .addKeepAttributeInnerClassesAndEnclosingMethod()
-        .addAndroidBuildVersion()
-        .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
-        .apply(
-            setMockApiLevelForMethod(
-                LibraryClass.class.getDeclaredMethod("addedOn10"), methodApiLevel))
-        .apply(ApiModelingTestHelper::enableOutliningOfMethods)
-        .apply(ApiModelingTestHelper::disableStubbingOfClasses)
         .compile()
-        .inspect(
-            inspector -> {
-              // Assert that we did not outline any methods.
-              assertEquals(
-                  0,
-                  inspector.allClasses().stream()
-                      .filter(FoundClassSubject::isCompilerSynthesized)
-                      .count());
-            })
+        // Assert that we did not outline any methods.
+        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
         .applyIf(willInvokeLibraryMethods(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkResultOnBootClassPath);
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
index 581fca7..40f9e10 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -28,6 +28,7 @@
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
+import java.util.Collections;
 import java.util.List;
 import java.util.function.BiConsumer;
 import java.util.stream.Collectors;
@@ -177,6 +178,14 @@
         inspector, parameters, Reference.methodFromMethod(method));
   }
 
+  public static void assertNoSynthesizedClasses(CodeInspector inspector) {
+    assertEquals(
+        Collections.emptySet(),
+        inspector.allClasses().stream()
+            .filter(FoundClassSubject::isSynthetic)
+            .collect(Collectors.toSet()));
+  }
+
   public static class ApiModelingClassVerificationHelper {
 
     private final CodeInspector inspector;
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
index c741f27..5dc4bd9 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
@@ -5,6 +5,7 @@
 
 import static java.util.Collections.emptyList;
 
+import com.android.tools.r8.benchmarks.appdumps.TiviBenchmarks;
 import com.android.tools.r8.benchmarks.desugaredlib.LegacyDesugaredLibraryBenchmark;
 import com.android.tools.r8.benchmarks.helloworld.HelloWorldBenchmark;
 import java.io.IOException;
@@ -45,6 +46,7 @@
     // Every benchmark that should be active on golem must be setup in this method.
     HelloWorldBenchmark.configs().forEach(collection::addBenchmark);
     LegacyDesugaredLibraryBenchmark.configs().forEach(collection::addBenchmark);
+    TiviBenchmarks.configs().forEach(collection::addBenchmark);
     return collection;
   }
 
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkTarget.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkTarget.java
index cda7f6e..0832e6c 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkTarget.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkTarget.java
@@ -4,10 +4,11 @@
 package com.android.tools.r8.benchmarks;
 
 public enum BenchmarkTarget {
+
   // Possible dashboard targets on golem.
   D8("d8", "D8"),
   R8_COMPAT("r8-compat", "R8"),
-  R8_NON_COMPAT("r8", "R8-full"),
+  R8_NON_COMPAT("r8-full", "R8-full"),
   R8_FORCE_OPT("r8-force", "R8-full-minify-optimize-shrink");
 
   private final String idName;
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
new file mode 100644
index 0000000..3cec248
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.benchmarks.appdumps;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.benchmarks.BenchmarkBase;
+import com.android.tools.r8.benchmarks.BenchmarkConfig;
+import com.android.tools.r8.benchmarks.BenchmarkConfigError;
+import com.android.tools.r8.benchmarks.BenchmarkDependency;
+import com.android.tools.r8.benchmarks.BenchmarkEnvironment;
+import com.android.tools.r8.benchmarks.BenchmarkMethod;
+import com.android.tools.r8.benchmarks.BenchmarkTarget;
+import com.android.tools.r8.dump.CompilerDump;
+import com.android.tools.r8.dump.DumpOptions;
+import java.io.IOException;
+import java.nio.file.Path;
+
+public class AppDumpBenchmarkBuilder {
+
+  public static AppDumpBenchmarkBuilder builder() {
+    return new AppDumpBenchmarkBuilder();
+  }
+
+  private String name;
+  private BenchmarkDependency dumpDependency;
+  private int fromRevision = -1;
+
+  public void verify() {
+    if (name == null) {
+      throw new BenchmarkConfigError("Missing name");
+    }
+    if (dumpDependency == null) {
+      throw new BenchmarkConfigError("Missing dump");
+    }
+    if (fromRevision < 0) {
+      throw new BenchmarkConfigError("Missing from-revision");
+    }
+  }
+
+  public AppDumpBenchmarkBuilder setName(String name) {
+    this.name = name;
+    return this;
+  }
+
+  public AppDumpBenchmarkBuilder setDumpDependencyPath(Path dumpDependencyPath) {
+    return setDumpDependency(
+        new BenchmarkDependency(
+            dumpDependencyPath.getFileName().toString(), dumpDependencyPath.getParent()));
+  }
+
+  public AppDumpBenchmarkBuilder setDumpDependency(BenchmarkDependency dependency) {
+    this.dumpDependency = dependency;
+    return this;
+  }
+
+  public AppDumpBenchmarkBuilder setFromRevision(int fromRevision) {
+    this.fromRevision = fromRevision;
+    return this;
+  }
+
+  public BenchmarkConfig build() {
+    verify();
+    return BenchmarkConfig.builder()
+        .setName(name)
+        .setTarget(BenchmarkTarget.R8_NON_COMPAT)
+        .setMethod(run(this))
+        .setFromRevision(fromRevision)
+        .addDependency(dumpDependency)
+        .measureRunTime()
+        .measureCodeSize()
+        .build();
+  }
+
+  private CompilerDump getExtractedDump(BenchmarkEnvironment environment) throws IOException {
+    Path dump = dumpDependency.getRoot(environment).resolve("dump_app.zip");
+    return CompilerDump.fromArchive(dump, environment.getTemp().newFolder().toPath());
+  }
+
+  private static BenchmarkMethod run(AppDumpBenchmarkBuilder builder) {
+    return environment ->
+        BenchmarkBase.runner(environment.getConfig())
+            .setWarmupIterations(1)
+            .run(
+                results -> {
+                  CompilerDump dump = builder.getExtractedDump(environment);
+                  DumpOptions dumpProperties = dump.getBuildProperties();
+                  TestBase.testForR8(environment.getTemp(), Backend.DEX)
+                      // TODO(b/221811893): mock a typical setup of program providers from agp.
+                      .addProgramFiles(dump.getProgramArchive())
+                      .addLibraryFiles(dump.getLibraryArchive())
+                      .addKeepRuleFiles(dump.getProguardConfigFile())
+                      .setMinApi(dumpProperties.getMinApi())
+                      .allowUnusedDontWarnPatterns()
+                      .allowUnusedProguardConfigurationRules()
+                      // TODO(b/222228826): Disallow unrecognized diagnostics and open interfaces.
+                      .allowDiagnosticMessages()
+                      .addOptionsModification(
+                          options ->
+                              options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
+                      .benchmarkCompile(results)
+                      .benchmarkCodeSize(results);
+                });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/TiviBenchmarks.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/TiviBenchmarks.java
new file mode 100644
index 0000000..235c559
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/TiviBenchmarks.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.benchmarks.appdumps;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.benchmarks.BenchmarkBase;
+import com.android.tools.r8.benchmarks.BenchmarkConfig;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class TiviBenchmarks extends BenchmarkBase {
+
+  private static final Path dump = Paths.get("third_party", "opensource-apps", "tivi");
+
+  public TiviBenchmarks(BenchmarkConfig config, TestParameters parameters) {
+    super(config, parameters);
+  }
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return parametersFromConfigs(configs());
+  }
+
+  public static List<BenchmarkConfig> configs() {
+    return ImmutableList.of(
+        AppDumpBenchmarkBuilder.builder()
+            .setName("Tivi")
+            .setDumpDependencyPath(dump)
+            .setFromRevision(12170)
+            .build());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java b/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java
index 5d84adf..7b52abd 100644
--- a/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -79,7 +78,9 @@
         testForR8Compat(parameters.getBackend())
             .addProgramClasses(getClasses())
             .setMinApi(parameters.getApiLevel())
-            .addKeepMainRule(getMainClass());
+            .addKeepMainRule(getMainClass())
+            .addOptionsModification(
+                options -> options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces());
     if (addKeepDeserializedLambdaRule) {
       builder.allowUnusedProguardConfigurationRules(parameters.isDexRuntime());
       builder.addKeepRules(
diff --git a/src/test/java/com/android/tools/r8/cf/PrintSeedsWithDeserializeLambdaMethodTest.java b/src/test/java/com/android/tools/r8/cf/PrintSeedsWithDeserializeLambdaMethodTest.java
index f5673dd..33b13a9 100644
--- a/src/test/java/com/android/tools/r8/cf/PrintSeedsWithDeserializeLambdaMethodTest.java
+++ b/src/test/java/com/android/tools/r8/cf/PrintSeedsWithDeserializeLambdaMethodTest.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -54,6 +53,9 @@
         .addProgramClasses(getClasses())
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(getMainClass())
+        // TODO(b/214496607): Improve analysis precision to ensure there are no open interfaces.
+        .addOptionsModification(
+            options -> options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
         .addPrintSeeds()
         .allowStdoutMessages()
         .noMinification()
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
index 12bbdf3..591b8ce 100644
--- a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
@@ -99,6 +99,11 @@
           .setMode(mode)
           .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
           .addKeepRuleFiles(MAIN_KEEP)
+          .addOptionsModification(
+              options ->
+                  options
+                      .getOpenClosedInterfacesOptions()
+                      .suppressZipFileAssignmentsToJavaLangAutoCloseable())
           .compile()
           .apply(c -> FileUtils.writeTextFile(map, c.getProguardMap()))
           .writeToZip(jar);
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
index 9fa9260..7081080 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
@@ -18,8 +18,8 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -46,13 +46,7 @@
         // I and J are not eligible for merging, since the lambda that implements I & J inherits a
         // default m() method from K, which is also on J.
         .addHorizontallyMergedClassesInspector(
-            inspector -> {
-              if (parameters.isCfRuntime()) {
-                inspector.assertIsCompleteMergeGroup(I.class, J.class);
-              } else {
-                inspector.assertNoClassesMerged();
-              }
-            })
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
@@ -66,25 +60,13 @@
               ClassSubject aClassSubject = inspector.clazz(A.class);
               if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
                 assertThat(aClassSubject, isPresent());
-                if (parameters.isCfRuntime()) {
-                  assertThat(aClassSubject, isImplementing(inspector.clazz(I.class)));
-                } else {
-                  assertThat(aClassSubject, isImplementing(inspector.clazz(J.class)));
-                }
+                assertThat(aClassSubject, isImplementing(inspector.clazz(J.class)));
               } else {
                 assertThat(aClassSubject, isAbsent());
               }
             })
         .run(parameters.getRuntime(), Main.class)
-        // TODO(b/173990042): Should succeed with "K", "J".
-        .applyIf(
-            parameters.isCfRuntime(),
-            builder ->
-                builder.assertFailureWithErrorThatThrows(
-                    parameters.isCfRuntime(CfVm.JDK11)
-                        ? AbstractMethodError.class
-                        : IncompatibleClassChangeError.class),
-            builder -> builder.assertSuccessWithOutputLines("K", "J"));
+        .assertSuccessWithOutputLines("K", "J");
   }
 
   static class Main {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
index 91f0fbe..7125324 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
@@ -33,8 +33,11 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addHorizontallyMergedClassesInspector(
-            inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
+        .applyIf(
+            parameters.isDexRuntime(),
+            testBuilder ->
+                testBuilder.addHorizontallyMergedClassesInspector(
+                    inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class)))
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .noClassInliningOfSynthetics()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
index 44bda30..6687fac 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
@@ -33,8 +33,11 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addHorizontallyMergedClassesInspector(
-            inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
+        .applyIf(
+            parameters.isDexRuntime(),
+            testBuilder ->
+                testBuilder.addHorizontallyMergedClassesInspector(
+                    inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class)))
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .noClassInliningOfSynthetics()
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
index 24e1de1..1ca2f2b 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.compilerapi.assertionconfiguration.AssertionConfigurationTest;
+import com.android.tools.r8.compilerapi.inputdependencies.InputDependenciesTest;
 import com.android.tools.r8.compilerapi.mapid.CustomMapIdTest;
 import com.android.tools.r8.compilerapi.mockdata.MockClass;
 import com.android.tools.r8.compilerapi.mockdata.MockClassWithAssertion;
@@ -34,7 +35,8 @@
           CustomSourceFileTest.ApiTest.class);
 
   private static final List<Class<? extends CompilerApiTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
-      ImmutableList.of(AssertionConfigurationTest.ApiTest.class);
+      ImmutableList.of(
+          AssertionConfigurationTest.ApiTest.class, InputDependenciesTest.ApiTest.class);
 
   private final TemporaryFolder temp;
 
@@ -73,6 +75,7 @@
   }
 
   // The API tests always link against the jar that the test runner is using.
+  @Override
   public Path getTargetJar() {
     return isTestingR8Lib() ? R8LIB_JAR : R8_JAR;
   }
diff --git a/src/test/java/com/android/tools/r8/compilerapi/inputdependencies/InputDependenciesTest.java b/src/test/java/com/android/tools/r8/compilerapi/inputdependencies/InputDependenciesTest.java
new file mode 100644
index 0000000..f6161fc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/inputdependencies/InputDependenciesTest.java
@@ -0,0 +1,183 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.compilerapi.inputdependencies;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.InputDependencyGraphConsumer;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.IntBox;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Arrays;
+import java.util.Collections;
+import org.junit.Test;
+
+public class InputDependenciesTest extends CompilerApiTestRunner {
+
+  public InputDependenciesTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<? extends CompilerApiTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  @Test
+  public void testInputDependencies() throws Exception {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    runTest(test::run);
+  }
+
+  private void checkPathOrigin(Path expected, Origin origin, Path base) {
+    assertTrue(origin instanceof PathOrigin);
+    assertEquals(base.relativize(expected), base.relativize(((PathOrigin) origin).getPath()));
+  }
+
+  private interface Runner {
+    void run(Path includePath, Path applymappingPath, InputDependencyGraphConsumer consumer)
+        throws Exception;
+  }
+
+  private void runTest(Runner test) throws Exception {
+    Path dir = temp.newFolder().toPath();
+    Path included1 =
+        Files.write(
+            dir.resolve("included1.rules"), Collections.emptyList(), StandardOpenOption.CREATE_NEW);
+    Path included2 =
+        Files.write(
+            dir.resolve("included2.rules"),
+            Arrays.asList("-include included1.rules"),
+            StandardOpenOption.CREATE_NEW);
+    Path applymapping =
+        Files.write(
+            dir.resolve("mymapping.txt"), Collections.emptyList(), StandardOpenOption.CREATE_NEW);
+
+    IntBox dependencyCount = new IntBox(0);
+    BooleanBox isFinished = new BooleanBox(false);
+    test.run(
+        included2,
+        applymapping,
+        new InputDependencyGraphConsumer() {
+          @Override
+          public void accept(Origin dependent, Path dependency) {
+            dependencyCount.increment();
+            assertEquals(Origin.unknown(), dependent);
+            assertEquals(dir.relativize(applymapping), dir.relativize(dependency));
+          }
+
+          @Override
+          public void acceptProguardInclude(Origin dependent, Path dependency) {
+            dependencyCount.increment();
+            if (dependent == Origin.unknown()) {
+              assertEquals(dir.relativize(included2), dir.relativize(dependency));
+            } else {
+              checkPathOrigin(included2, dependent, dir);
+              assertEquals(dir.relativize(included1), dir.relativize(dependency));
+            }
+          }
+
+          @Override
+          public void finished() {
+            isFinished.set(true);
+          }
+        });
+    assertEquals(3, dependencyCount.get());
+    assertTrue(isFinished.get());
+  }
+
+  public static class ApiTest extends CompilerApiTest {
+
+    public ApiTest(Object parameters) {
+      super(parameters);
+    }
+
+    public void run(
+        Path includedFile, Path applymapping, InputDependencyGraphConsumer dependencyConsumer)
+        throws Exception {
+      R8.run(
+          R8Command.builder()
+              .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+              .addProguardConfiguration(getKeepMainRules(getMockClass()), Origin.unknown())
+              .addProguardConfiguration(
+                  Arrays.asList("-include " + includedFile, "-applymapping" + applymapping),
+                  Origin.unknown())
+              .addLibraryFiles(getJava8RuntimeJar())
+              .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+              .setInputDependencyGraphConsumer(dependencyConsumer)
+              .build());
+    }
+
+    @Test
+    public void testInputDependencies() throws Exception {
+      Path emptyFile =
+          Files.write(
+              temp.newFolder().toPath().resolve("empty.txt"),
+              Collections.emptyList(),
+              StandardOpenOption.CREATE_NEW);
+      run(
+          emptyFile,
+          emptyFile,
+          new InputDependencyGraphConsumer() {
+            @Override
+            public void accept(Origin dependent, Path dependency) {
+              // ignore.
+            }
+
+            @Override
+            public void acceptProguardInclude(Origin dependent, Path dependency) {
+              accept(dependent, dependency);
+            }
+
+            @Override
+            public void acceptProguardInJars(Origin dependent, Path dependency) {
+              accept(dependent, dependency);
+            }
+
+            @Override
+            public void acceptProguardLibraryJars(Origin dependent, Path dependency) {
+              accept(dependent, dependency);
+            }
+
+            @Override
+            public void acceptProguardApplyMapping(Origin dependent, Path dependency) {
+              accept(dependent, dependency);
+            }
+
+            @Override
+            public void acceptProguardObfuscationDictionary(Origin dependent, Path dependency) {
+              accept(dependent, dependency);
+            }
+
+            @Override
+            public void acceptProguardClassObfuscationDictionary(
+                Origin dependent, Path dependency) {
+              accept(dependent, dependency);
+            }
+
+            @Override
+            public void acceptProguardPackageObfuscationDictionary(
+                Origin dependent, Path dependency) {
+              accept(dependent, dependency);
+            }
+
+            @Override
+            public void finished() {
+              // ignore.
+            }
+          });
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
index 2453bef..f6f59e8 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
@@ -293,9 +293,9 @@
     Set<MethodReference> expectedSynthetics =
         ImmutableSet.of(
             SyntheticItemsTestUtils.syntheticBackportMethod(
-                User1.class, 0, Character.class.getMethod("compare", char.class, char.class)),
+                User1.class, 0, Boolean.class.getMethod("compare", boolean.class, boolean.class)),
             SyntheticItemsTestUtils.syntheticBackportMethod(
-                User1.class, 1, Boolean.class.getMethod("compare", boolean.class, boolean.class)),
+                User1.class, 1, Character.class.getMethod("compare", char.class, char.class)),
             SyntheticItemsTestUtils.syntheticBackportMethod(
                 User2.class, 0, Integer.class.getMethod("compare", int.class, int.class)));
     assertEquals(expectedSynthetics, getSyntheticMethods(inspector));
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 2684b3b..3b41b98 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
@@ -4,10 +4,12 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 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;
 
@@ -63,7 +65,6 @@
       // Expected since we are compiling with an invalid set-up.
     }
     TestDiagnosticMessages diagnosticMessages = testBuilder.getState().getDiagnosticsMessages();
-    diagnosticMessages.assertOnlyWarnings();
     assertTrue(
         diagnosticMessages
             .getWarnings()
@@ -119,7 +120,13 @@
     ToolHelper.runL8(l8Builder.build(), options -> {});
     CodeInspector codeInspector = new CodeInspector(desugaredLib);
     assertCorrect(codeInspector);
-    diagnosticsHandler.assertNoMessages();
+    if (isJDK11DesugaredLibrary()) {
+      diagnosticsHandler.assertNoErrors();
+      diagnosticsHandler.assertAllWarningsMatch(
+          diagnosticMessage(containsString("Specification conversion")));
+    } else {
+      diagnosticsHandler.assertNoMessages();
+    }
   }
 
   private void assertCorrect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDeterminismTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDeterminismTest.java
index 9623e7c..5f060f1 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDeterminismTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDeterminismTest.java
@@ -3,30 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar.desugaredlibrary;
 
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.ZipUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
-import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.Sets;
-import com.google.common.collect.Sets.SetView;
-import java.io.IOException;
-import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.Collections;
-import java.util.IdentityHashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -49,98 +32,10 @@
   public void testDeterminism() throws Exception {
     AndroidApiLevel minApiLevel = parameters.getRuntime().asDex().getMinApiLevel();
     Assume.assumeTrue(minApiLevel.isLessThan(AndroidApiLevel.O));
-    Path libDexFile1 = buildDesugaredLibraryToBytes(minApiLevel);
-    Path libDexFile2 = buildDesugaredLibraryToBytes(minApiLevel);
-    assertIdenticalInspectors(libDexFile1, libDexFile2);
-    assertArrayEquals(getDexBytes(libDexFile1), getDexBytes(libDexFile2));
-  }
-
-  private void assertIdenticalInspectors(Path libDexFile1, Path libDexFile2) throws IOException {
-    CodeInspector i1 = new CodeInspector(libDexFile1.resolve("classes.dex"));
-    CodeInspector i2 = new CodeInspector(libDexFile2.resolve("classes.dex"));
-    assertIdenticalInspectors(i1, i2);
-  }
-
-  public static void assertIdenticalInspectors(CodeInspector i1, CodeInspector i2) {
-    assertEquals(i1.allClasses().size(), i2.allClasses().size());
-    Map<DexEncodedMethod, DexEncodedMethod> diffs = new IdentityHashMap<>();
-    for (FoundClassSubject clazz1 : i1.allClasses()) {
-      ClassSubject clazz = i2.clazz(clazz1.getOriginalName());
-      assertTrue(clazz.isPresent());
-      FoundClassSubject clazz2 = clazz.asFoundClassSubject();
-      Set<String> methods1 =
-          clazz1.allMethods().stream()
-              .map(FoundMethodSubject::toString)
-              .collect(Collectors.toSet());
-      Set<String> methods2 =
-          clazz2.allMethods().stream()
-              .map(FoundMethodSubject::toString)
-              .collect(Collectors.toSet());
-      SetView<String> union = Sets.union(methods1, methods2);
-      assertEquals(
-          "Inspector 1 contains more methods",
-          Collections.emptySet(),
-          Sets.difference(union, methods1));
-      assertEquals(
-          "Inspector 2 contains more methods",
-          Collections.emptySet(),
-          Sets.difference(union, methods2));
-      assertEquals(clazz1.allMethods().size(), clazz2.allMethods().size());
-      for (FoundMethodSubject method1 : clazz1.allMethods()) {
-        MethodSubject method = clazz2.method(method1.asMethodReference());
-        assertTrue(method.isPresent());
-        FoundMethodSubject method2 = method.asFoundMethodSubject();
-        if (method1.hasCode()) {
-          assertTrue(method2.hasCode());
-          if (!(method1
-              .getMethod()
-              .getCode()
-              .toString()
-              .equals(method2.getMethod().getCode().toString()))) {
-            diffs.put(method1.getMethod(), method2.getMethod());
-          }
-        }
-      }
-    }
-    assertTrue(printDiffs(diffs), diffs.isEmpty());
-  }
-
-  private static String printDiffs(Map<DexEncodedMethod, DexEncodedMethod> diffs) {
-    StringBuilder sb = new StringBuilder();
-    sb.append("The following methods had differences from one dex file to the other (")
-        .append(diffs.size())
-        .append("):\n");
-    diffs.forEach(
-        (m1, m2) -> {
-          sb.append(m1.toSourceString()).append("\n");
-          String[] lines1 = m1.getCode().toString().split("\n");
-          String[] lines2 = m2.getCode().toString().split("\n");
-          if (lines1.length != lines2.length) {
-            sb.append("Different number of lines.");
-            sb.append("\n");
-          } else {
-            for (int i = 0; i < lines1.length; i++) {
-              if (!lines1[i].equals(lines2[i])) {
-                sb.append(lines1[i]);
-                sb.append("\n");
-                sb.append(lines2[i]);
-                sb.append("\n");
-                return;
-              }
-            }
-          }
-        });
-    return sb.toString();
-  }
-
-  private byte[] getDexBytes(Path libDexFile) throws IOException {
-    return Files.readAllBytes(libDexFile.resolve("classes.dex"));
-  }
-
-  private Path buildDesugaredLibraryToBytes(AndroidApiLevel minApiLevel) throws IOException {
-    Path lib1 = buildDesugaredLibrary(minApiLevel);
-    Path unzipped1 = temp.newFolder().toPath();
-    ZipUtils.unzip(lib1.toString(), unzipped1.toFile());
-    return unzipped1;
+    Path libDexFile1 = buildDesugaredLibrary(minApiLevel);
+    Path libDexFile2 = buildDesugaredLibrary(minApiLevel);
+    uploadJarsToCloudStorageIfTestFails(TestBase::filesAreEqual, libDexFile1, libDexFile2);
+    assertProgramsEqual(libDexFile1, libDexFile2);
+    assertTrue(filesAreEqual(libDexFile1, libDexFile2));
   }
 }
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 56f687c..fb5922a 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
@@ -69,14 +69,7 @@
 
   public void setDesugaredLibrarySpecificationForTesting(
       InternalOptions options, DesugaredLibrarySpecification specification) {
-    try {
-      options.setDesugaredLibrarySpecificationForTesting(
-          specification,
-          ToolHelper.getDesugarJDKLibs(),
-          ToolHelper.getAndroidJar(AndroidApiLevel.R));
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
+    options.setDesugaredLibrarySpecification(specification);
   }
 
   // For conversions tests, we need DexRuntimes where classes to convert are present (DexRuntimes
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryWarningTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryWarningTest.java
index 0c61a06..1a7981a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryWarningTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryWarningTest.java
@@ -4,6 +4,9 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.core.StringContains.containsString;
+
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.L8Command;
 import com.android.tools.r8.OutputMode;
@@ -65,6 +68,12 @@
           Arrays.asList(FUNCTION_KEEP.split(System.lineSeparator())), Origin.unknown());
     }
     ToolHelper.runL8(l8Builder.build(), options -> {});
-    diagnosticsHandler.assertNoMessages();
+    if (isJDK11DesugaredLibrary()) {
+      diagnosticsHandler.assertNoErrors();
+      diagnosticsHandler.assertAllWarningsMatch(
+          diagnosticMessage(containsString("Specification conversion")));
+    } else {
+      diagnosticsHandler.assertNoMessages();
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetAndBackportTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetAndBackportTest.java
index 27762ea..50f2ce7 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetAndBackportTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetAndBackportTest.java
@@ -7,7 +7,6 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyRewritingFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyTopLevelFlags;
@@ -15,7 +14,6 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import java.io.IOException;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -55,15 +53,9 @@
             .putRewritePrefix("java.time.", "j$.time.")
             .putBackportCoreLibraryMember("java.lang.DesugarMath", "java.lang.Math")
             .build();
-    try {
-      options.setDesugaredLibrarySpecificationForTesting(
-          new LegacyDesugaredLibrarySpecification(
-              LegacyTopLevelFlags.testing(), rewritingFlags, true),
-          ToolHelper.getDesugarJDKLibs(),
-          ToolHelper.getAndroidJar(AndroidApiLevel.R));
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
+    options.setDesugaredLibrarySpecification(
+        new LegacyDesugaredLibrarySpecification(
+            LegacyTopLevelFlags.testing(), rewritingFlags, true));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SpliteratorConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SpliteratorConversionTest.java
new file mode 100644
index 0000000..2e0d110
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SpliteratorConversionTest.java
@@ -0,0 +1,126 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Spliterator;
+import org.junit.BeforeClass;
+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 SpliteratorConversionTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+  private static final String EXPECTED_RESULT = StringUtils.lines("2", "true", "2", "true");
+
+  private static Path CUSTOM_LIB;
+
+  @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+  }
+
+  public SpliteratorConversionTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @BeforeClass
+  public static void compileCustomLib() throws Exception {
+    CUSTOM_LIB =
+        testForD8(getStaticTemp())
+            .addProgramClasses(CustomLibClass.class)
+            .setMinApi(MIN_SUPPORTED)
+            .compile()
+            .writeToZip();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addLibraryFiles(getLibraryFile())
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(Executor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(getLibraryFile())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Executor.class)
+        .addProgramClasses(Executor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  static class Executor {
+
+    public static void main(String[] args) {
+      ArrayList<String> strings = new ArrayList<>();
+      strings.add("A");
+      strings.add("B");
+      CustomLibClass.printSpliterator(strings.spliterator());
+
+      Spliterator<String> split = CustomLibClass.get();
+      System.out.println(split.getExactSizeIfKnown());
+      System.out.println(split.hasCharacteristics(Spliterator.SIZED));
+    }
+  }
+
+  // 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 {
+
+    public static void printSpliterator(Spliterator<String> split) {
+      System.out.println(split.getExactSizeIfKnown());
+      System.out.println(split.hasCharacteristics(Spliterator.SIZED));
+    }
+
+    public static Spliterator<String> get() {
+      ArrayList<String> strings = new ArrayList<>();
+      strings.add("A");
+      strings.add("B");
+      return strings.spliterator();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InputStreamTransferToTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InputStreamTransferToTest.java
new file mode 100644
index 0000000..77f16d2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InputStreamTransferToTest.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary.jdk11;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InputStreamTransferToTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  private static final Path INPUT_JAR =
+      Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR + "transferto.jar");
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines("Hello World!", "Hello World!", "Hello World!", "$Hello World!");
+  private static final String MAIN_CLASS = "transferto.TestClass";
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public InputStreamTransferToTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    Assume.assumeTrue(isJDK11DesugaredLibrary());
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(getLibraryFile())
+        .addProgramFiles(INPUT_JAR)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Assume.assumeTrue(isJDK11DesugaredLibrary());
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(getLibraryFile())
+        .addProgramFiles(INPUT_JAR)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(MAIN_CLASS)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
index 01eb3b8..592dab4 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
@@ -158,15 +158,18 @@
                   .toArray(new String[0]));
       printTime("R8/JVM external", start);
       assertEquals(javaProcessResult.toString(), 0, javaProcessResult.exitCode);
-      assertTrue(
+      uploadJarsToCloudStorageIfTestFails(
+          TestBase::filesAreEqual, outputThroughCf, outputThroughCfExternal);
+      assertProgramsEqual(outputThroughCf, outputThroughCfExternal);
+      String message =
           "The output of R8/JVM in-process and R8/JVM external differ."
               + " Make sure you have an up-to-date compilation of "
               + r8jar
               + ". If not, that could very likely cause the in-process run (eg, via intellij) to"
               + " differ from the external run which uses "
               + r8jar
-              + ". If up-to-date, the likely cause of this error is that R8 is non-deterministic.",
-          TestBase.filesAreEqual(outputThroughCf, outputThroughCfExternal));
+              + ". If up-to-date, the likely cause of this error is that R8 is non-deterministic.";
+      assertTrue(message, filesAreEqual(outputThroughCf, outputThroughCfExternal));
     }
 
     // Finally compile R8 on the ART runtime using the already compiled DEX version of R8.
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java b/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
index de2364e..c9d943e 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
@@ -64,9 +64,13 @@
         compileWithR8(
             builder ->
                 builder.addOptionsModification(
-                    options ->
-                        options.testing.processingContextsConsumer =
-                            id -> assertNull(idsRoundOne.put(id, id))));
+                    options -> {
+                      options
+                          .getOpenClosedInterfacesOptions()
+                          .suppressArrayAssignmentsToJavaLangSerializable();
+                      options.testing.processingContextsConsumer =
+                          id -> assertNull(idsRoundOne.put(id, id));
+                    }));
 
     compileResult.runDex2Oat(parameters.getRuntime()).assertNoVerificationErrors();
 
@@ -75,12 +79,16 @@
         compileWithR8(
             builder ->
                 builder.addOptionsModification(
-                    options ->
-                        options.testing.processingContextsConsumer =
-                            id -> {
-                              AssertionUtils.assertNotNull(idsRoundOne.get(id));
-                              assertNull(idsRoundTwo.put(id, id));
-                            }));
+                    options -> {
+                      options
+                          .getOpenClosedInterfacesOptions()
+                          .suppressArrayAssignmentsToJavaLangSerializable();
+                      options.testing.processingContextsConsumer =
+                          id -> {
+                            AssertionUtils.assertNotNull(idsRoundOne.get(id));
+                            assertNull(idsRoundTwo.put(id, id));
+                          };
+                    }));
 
     // Verify that the result of the two compilations was the same.
     assertEquals(
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java b/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
index 0622a2a..34b77bd 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
@@ -62,9 +62,13 @@
         compileWithR8(
             builder ->
                 builder.addOptionsModification(
-                    options ->
-                        options.testing.processingContextsConsumer =
-                            id -> assertTrue(idsRoundOne.add(id))));
+                    options -> {
+                      options
+                          .getOpenClosedInterfacesOptions()
+                          .suppressArrayAssignmentsToJavaLangSerializable();
+                      options.testing.processingContextsConsumer =
+                          id -> assertTrue(idsRoundOne.add(id));
+                    }));
 
     compileResult.runDex2Oat(parameters.getRuntime()).assertNoVerificationErrors();
 
@@ -73,9 +77,13 @@
         compileWithR8(
             builder ->
                 builder.addOptionsModification(
-                    options ->
-                        options.testing.processingContextsConsumer =
-                            id -> assertTrue(idsRoundTwo.add(id))));
+                    options -> {
+                      options
+                          .getOpenClosedInterfacesOptions()
+                          .suppressArrayAssignmentsToJavaLangSerializable();
+                      options.testing.processingContextsConsumer =
+                          id -> assertTrue(idsRoundTwo.add(id));
+                    }));
 
     uploadJarsToCloudStorageIfTestFails(
         (ignored1, ignored2) -> {
@@ -94,7 +102,12 @@
     compileWithR8(
             builder ->
                 builder.addOptionsModification(
-                    options -> options.testing.forceJumboStringProcessing = true))
+                    options -> {
+                      options.testing.forceJumboStringProcessing = true;
+                      options
+                          .getOpenClosedInterfacesOptions()
+                          .suppressArrayAssignmentsToJavaLangSerializable();
+                    }))
         .runDex2Oat(parameters.getRuntime())
         .assertNoVerificationErrors();
   }
diff --git a/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java b/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java
index ff19143..f9822e9 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java
@@ -49,6 +49,8 @@
             keepDynamicMethodSignatureRule(),
             keepNewMessageInfoSignatureRule())
         .addDontWarn("android.content.pm.IPackageManager")
+        .addOptionsModification(
+            options -> options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
         .allowUnusedDontWarnPatterns()
         .allowUnusedProguardConfigurationRules()
         .enableProtoShrinking(false)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java
index 1411ce8..1107d60 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java
@@ -8,7 +8,6 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
-import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
@@ -56,7 +55,13 @@
         .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get(...); }")
         .addNoVerticalClassMergingAnnotations()
         .applyIf(
-            !enableVerticalClassMerging, R8TestBuilder::enableNoVerticalClassMergingAnnotations)
+            !enableVerticalClassMerging,
+            testBuilder ->
+                testBuilder
+                    .addOptionsModification(
+                        options ->
+                            options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
+                    .enableNoVerticalClassMergingAnnotations())
         .addVerticallyMergedClassesInspector(
             inspector -> {
               if (enableVerticalClassMerging) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/InstancePutToInterfaceWithObjectMergingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/InstancePutToInterfaceWithObjectMergingTest.java
index 980eebb..d294a77 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/InstancePutToInterfaceWithObjectMergingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/InstancePutToInterfaceWithObjectMergingTest.java
@@ -8,7 +8,6 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
-import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
@@ -56,7 +55,13 @@
         .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get(...); }")
         .addNoVerticalClassMergingAnnotations()
         .applyIf(
-            !enableVerticalClassMerging, R8TestBuilder::enableNoVerticalClassMergingAnnotations)
+            !enableVerticalClassMerging,
+            testBuilder ->
+                testBuilder
+                    .addOptionsModification(
+                        options ->
+                            options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
+                    .enableNoVerticalClassMergingAnnotations())
         .addVerticallyMergedClassesInspector(
             inspector -> {
               if (enableVerticalClassMerging) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ReturnObjectAsInterfaceMergingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ReturnObjectAsInterfaceMergingTest.java
index 48b234b..87d60b4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ReturnObjectAsInterfaceMergingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ReturnObjectAsInterfaceMergingTest.java
@@ -8,7 +8,6 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
-import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
@@ -56,7 +55,13 @@
         .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get(...); }")
         .addNoVerticalClassMergingAnnotations()
         .applyIf(
-            !enableVerticalClassMerging, R8TestBuilder::enableNoVerticalClassMergingAnnotations)
+            !enableVerticalClassMerging,
+            testBuilder ->
+                testBuilder
+                    .addOptionsModification(
+                        options ->
+                            options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
+                    .enableNoVerticalClassMergingAnnotations())
         .addVerticallyMergedClassesInspector(
             inspector -> {
               if (enableVerticalClassMerging) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/StaticPutToInterfaceWithObjectMergingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/StaticPutToInterfaceWithObjectMergingTest.java
index bb948cbe..072b819 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/StaticPutToInterfaceWithObjectMergingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/StaticPutToInterfaceWithObjectMergingTest.java
@@ -8,7 +8,6 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
-import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
@@ -56,7 +55,13 @@
         .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get(...); }")
         .addNoVerticalClassMergingAnnotations()
         .applyIf(
-            !enableVerticalClassMerging, R8TestBuilder::enableNoVerticalClassMergingAnnotations)
+            !enableVerticalClassMerging,
+            testBuilder ->
+                testBuilder
+                    .addOptionsModification(
+                        options ->
+                            options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
+                    .enableNoVerticalClassMergingAnnotations())
         .addVerticallyMergedClassesInspector(
             inspector -> {
               if (enableVerticalClassMerging) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/StaticInvokeWithMultipleObjectsForInterfaceTypesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/StaticInvokeWithMultipleObjectsForInterfaceTypesTest.java
index 0c9a135..4dc19d3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/StaticInvokeWithMultipleObjectsForInterfaceTypesTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/StaticInvokeWithMultipleObjectsForInterfaceTypesTest.java
@@ -65,7 +65,13 @@
         .addNoVerticalClassMergingAnnotations()
         .applyIf(!enableInlining, R8TestBuilder::enableInliningAnnotations)
         .applyIf(
-            !enableVerticalClassMerging, R8TestBuilder::enableNoVerticalClassMergingAnnotations)
+            !enableVerticalClassMerging,
+            testBuilder ->
+                testBuilder
+                    .addOptionsModification(
+                        options ->
+                            options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
+                    .enableNoVerticalClassMergingAnnotations())
         .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceCheckCastTest.java b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceCheckCastTest.java
index 01f306a..c756f8c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceCheckCastTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceCheckCastTest.java
@@ -41,7 +41,7 @@
         .addProgramClasses(getProgramClasses())
         .addProgramClassFileData(getTransformedMainClass())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines(false));
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   @Test
@@ -52,7 +52,7 @@
         .addProgramClassFileData(getTransformedMainClass())
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines(false));
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   @Test
@@ -61,12 +61,14 @@
         .addProgramClasses(getProgramClasses())
         .addProgramClassFileData(getTransformedMainClass())
         .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
         .enableInliningAnnotations()
         // TODO(b/214496607): I should not be merged into A in the first place, since I is open.
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines(true));
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   private List<Class<?>> getProgramClasses() {
@@ -100,11 +102,7 @@
         .transform();
   }
 
-  private List<String> getExpectedOutputLines(boolean isR8) {
-    if (isR8) {
-      // TODO(b/214496607): R8 should not optimize the check-cast instruction since I is open.
-      return ImmutableList.of("OK", "OK");
-    }
+  private List<String> getExpectedOutputLines() {
     if (parameters.isDexRuntime()
         && parameters
             .getDexRuntimeVersion()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInliningTest.java
index 98db3cc..4eaa6a4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInliningTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -39,7 +40,7 @@
         .addProgramClasses(getProgramClasses())
         .addProgramClassFileData(getTransformedMainClass())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines(false));
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   @Test
@@ -50,7 +51,7 @@
         .addProgramClassFileData(getTransformedMainClass())
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines(false));
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   @Test
@@ -59,11 +60,18 @@
         .addProgramClasses(getProgramClasses())
         .addProgramClassFileData(getTransformedMainClass())
         .addKeepClassAndMembersRules(Main.class)
+        .addOptionsModification(
+            options -> options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
+        // TODO(b/214496607): A and B should strictly speaking not be merged since A implements the
+        //  open interface I, and there is an invoke-interface instruction in the program to I.m(),
+        //  which should succeed if the receiver is an A, but fail with an ICCE if the receiver is a
+        //  B.
+        .enableNoHorizontalClassMergingAnnotations()
         // TODO(b/214496607): I should not be merged into A in the first place, since I is open.
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines(true));
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   private List<Class<?>> getProgramClasses() {
@@ -90,11 +98,7 @@
         .transform();
   }
 
-  private List<String> getExpectedOutputLines(boolean isR8) {
-    if (isR8) {
-      // TODO(b/214496607): R8 should not inline the invoke instruction since I is open.
-      return ImmutableList.of("A", "A");
-    }
+  private List<String> getExpectedOutputLines() {
     return ImmutableList.of("A", "ICCE");
   }
 
@@ -124,6 +128,7 @@
     void m();
   }
 
+  @NoHorizontalClassMerging
   static class A implements I {
 
     @Override
@@ -132,6 +137,7 @@
     }
   }
 
+  @NoHorizontalClassMerging
   static class B {
 
     public void m() {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInstanceofTest.java b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInstanceofTest.java
index d73550c..dac3848 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInstanceofTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInstanceofTest.java
@@ -41,7 +41,7 @@
         .addProgramClasses(getProgramClasses())
         .addProgramClassFileData(getTransformedMainClass())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines(false));
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   @Test
@@ -52,7 +52,7 @@
         .addProgramClassFileData(getTransformedMainClass())
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines(false));
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   @Test
@@ -61,12 +61,14 @@
         .addProgramClasses(getProgramClasses())
         .addProgramClassFileData(getTransformedMainClass())
         .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
         .enableInliningAnnotations()
         // TODO(b/214496607): I should not be merged into A in the first place, since I is open.
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines(true));
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   private List<Class<?>> getProgramClasses() {
@@ -93,11 +95,7 @@
         .transform();
   }
 
-  private List<String> getExpectedOutputLines(boolean isR8) {
-    if (isR8) {
-      // TODO(b/214496607): R8 should not optimize the instanceof instruction since I is open.
-      return ImmutableList.of("true", "true");
-    }
+  private List<String> getExpectedOutputLines() {
     if (parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isEqualTo(Version.V7_0_0)) {
       return ImmutableList.of("true", "true");
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceAlwaysNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceAlwaysNullTest.java
new file mode 100644
index 0000000..816da5a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceAlwaysNullTest.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.interfaces;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class OpenUninstantiatedInterfaceAlwaysNullTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForRuntime(parameters)
+        .addProgramClasses(getProgramClasses())
+        .addProgramClassFileData(getTransformedMainClass())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClasses(getProgramClasses())
+        .addProgramClassFileData(getTransformedMainClass())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(getProgramClasses())
+        .addProgramClassFileData(getTransformedMainClass())
+        .addKeepClassAndMembersRules(Main.class)
+        .addOptionsModification(
+            options -> options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
+        // TODO(b/214496607): I should not be merged into A in the first place, since I is open.
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
+  }
+
+  private List<Class<?>> getProgramClasses() {
+    return ImmutableList.of(I.class, A.class, B.class);
+  }
+
+  private byte[] getTransformedMainClass() throws IOException {
+    return transformer(Main.class)
+        .transformTypeInsnInMethod(
+            "getB",
+            (opcode, type, visitor) -> {
+              assertEquals(opcode, Opcodes.NEW);
+              assertEquals(type, binaryName(A.class));
+              visitor.visitTypeInsn(opcode, binaryName(B.class));
+            })
+        .transformMethodInsnInMethod(
+            "getB",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              assertEquals(opcode, Opcodes.INVOKESPECIAL);
+              assertEquals(owner, binaryName(A.class));
+              assertEquals(name, "<init>");
+              visitor.visitMethodInsn(opcode, binaryName(B.class), name, descriptor, isInterface);
+            })
+        .transform();
+  }
+
+  private List<String> getExpectedOutputLines() {
+    return ImmutableList.of("false");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      I b = getB();
+      System.out.println(b == null);
+    }
+
+    static I getB() {
+      return new A(); // transformed into new B().
+    }
+  }
+
+  @NoVerticalClassMerging
+  interface I {}
+
+  static class A implements I {}
+
+  static class B {}
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceInstanceofTest.java b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceInstanceofTest.java
index 7740192..606871f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceInstanceofTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceInstanceofTest.java
@@ -40,7 +40,7 @@
         .addProgramClasses(getProgramClasses())
         .addProgramClassFileData(getTransformedMainClass())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines(false));
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   @Test
@@ -51,7 +51,7 @@
         .addProgramClassFileData(getTransformedMainClass())
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines(false));
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   @Test
@@ -60,10 +60,12 @@
         .addProgramClasses(getProgramClasses())
         .addProgramClassFileData(getTransformedMainClass())
         .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines(true));
+        .assertSuccessWithOutputLines(getExpectedOutputLines());
   }
 
   private List<Class<?>> getProgramClasses() {
@@ -90,11 +92,7 @@
         .transform();
   }
 
-  private List<String> getExpectedOutputLines(boolean isR8) {
-    if (isR8) {
-      // TODO(b/214496607): R8 should not optimize the instanceof instruction since I is open.
-      return ImmutableList.of("true");
-    }
+  private List<String> getExpectedOutputLines() {
     if (parameters.isDexRuntime()) {
       if (parameters.getDexRuntimeVersion().isEqualTo(Version.V7_0_0)
           || parameters.getDexRuntimeVersion().isEqualTo(Version.V13_MASTER)) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/lambda/LambdaMethodInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/lambda/LambdaMethodInliningTest.java
index a63addb..a3d1e6b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/lambda/LambdaMethodInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/lambda/LambdaMethodInliningTest.java
@@ -65,7 +65,7 @@
     assertTrue(
         testClassMethodSubject
             .streamInstructions()
-            .anyMatch(
+            .noneMatch(
                 instruction ->
                     instruction.isInvokeVirtual()
                         && instruction.getMethod().toSourceString().contains("println")));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java
index 4ca57c1..82755bc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java
@@ -33,7 +33,7 @@
   }
 
   @Test
-  public void testWithoutR8() throws Exception {
+  public void testRuntime() throws Exception {
     testForRuntime(parameters)
         .addProgramClasses(I.class, J.class, Main.class)
         .addProgramClassFileData(getAimplementsI())
@@ -42,30 +42,15 @@
   }
 
   @Test
-  public void testWithUninstantiatedTypeOptimizationForInterfaces() throws Exception {
+  public void testR8() throws Exception {
     testForR8(parameters.getBackend())
         .addProgramClasses(I.class, J.class, Main.class)
         .addProgramClassFileData(getAimplementsI())
         .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(
-            options -> options.enableUninstantiatedTypeOptimizationForInterfaces = true)
-        .compile()
-        .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatThrows(NullPointerException.class);
-  }
-
-  @Test
-  public void testWithoutUninstantiatedTypeOptimizationForInterfaces() throws Exception {
-    testForR8(parameters.getBackend())
-        .addProgramClasses(I.class, J.class, Main.class)
-        .addProgramClassFileData(getAimplementsI())
-        .addKeepMainRule(Main.class)
-        .enableInliningAnnotations()
-        .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(
-            options -> options.enableUninstantiatedTypeOptimizationForInterfaces = false)
         .compile()
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("In A.f()");
diff --git a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
index 488271b..c14b357 100644
--- a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
@@ -73,8 +73,9 @@
                       .count();
               long paramNullCheckCount =
                   countCall(testMethod, "Intrinsics", "checkParameterIsNotNull");
-              // One after Iterator#hasNext, and another in the filter predicate: sinceYear != null.
-              assertEquals(2, ifzCount);
+              // TODO(b/214496607): Should be one after Iterator#hasNext, and another in the filter
+              //  predicate: sinceYear != null.
+              assertEquals(testParameters.isCfRuntime() ? 5 : 2, ifzCount);
               assertEquals(0, paramNullCheckCount);
             });
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
index 4726fe4..1446e76 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
@@ -84,10 +84,6 @@
   }
 
   private String getExpectedOutput() {
-    String icceOrNot =
-        enableInliningAnnotations || !parameters.canUseDefaultAndStaticInterfaceMethods()
-            ? "ICCE"
-            : "InterfaceWithDefault";
     return StringUtils.lines(
         "SubSubClassOne",
         "SubSubClassOne",
@@ -97,7 +93,7 @@
         "com.android.tools.r8.resolution.singletarget.one.AbstractSubClass",
         "InterfaceWithDefault",
         "InterfaceWithDefault",
-        icceOrNot,
+        "ICCE",
         "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo",
         "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo",
         "AbstractTopClass",
@@ -109,7 +105,7 @@
         "InterfaceWithDefault",
         "InterfaceWithDefault",
         "InterfaceWithDefault",
-        icceOrNot,
+        "ICCE",
         "InterfaceWithDefault",
         "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo",
         "InterfaceWithDefault",
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index 7d18aee..8c72e58 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -196,7 +196,7 @@
         appInfo.definitionForProgramType(reference.holder).getProgramDefaultInitializer();
     Assert.assertNotNull(appInfo.resolveMethodOnClass(reference).getSingleTarget());
     DexEncodedMethod singleVirtualTarget =
-        appInfo.lookupSingleVirtualTarget(reference, context, false);
+        appInfo.lookupSingleVirtualTarget(appView, reference, context, false);
     if (singleTargetHolderOrNull == null) {
       Assert.assertNull(singleVirtualTarget);
     } else {
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
index 846c994..ca541ea 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.AsmTestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -89,22 +90,23 @@
 
   public static byte[] DUMP = BDump.dump();
 
+  private static AppView<AppInfoWithLiveness> appView;
   private static AppInfoWithLiveness appInfo;
 
   @BeforeClass
   public static void computeAppInfo() throws Exception {
-    appInfo =
+    appView =
         computeAppViewWithLiveness(
-                buildClasses(CLASSES)
-                    .addClassProgramData(DUMP)
-                    .addLibraryFile(getMostRecentAndroidJar())
-                    .build(),
-                Main.class)
-            .appInfo();
+            buildClasses(CLASSES)
+                .addClassProgramData(DUMP)
+                .addLibraryFile(getMostRecentAndroidJar())
+                .build(),
+            Main.class);
+    appInfo = appView.appInfo();
   }
 
   private static DexMethod buildMethod(Class clazz, String name) {
-    return buildNullaryVoidMethod(clazz, name, appInfo.dexItemFactory());
+    return buildNullaryVoidMethod(clazz, name, appView.dexItemFactory());
   }
 
   @Parameters(name = "{0}")
@@ -131,7 +133,7 @@
     assertEquals(methodOnBReference, resolved.getReference());
     assertFalse(resolutionResult.isVirtualTarget());
     DexEncodedMethod singleVirtualTarget =
-        appInfo.lookupSingleVirtualTarget(methodOnBReference, methodOnB, false);
+        appInfo.lookupSingleVirtualTarget(appView, methodOnBReference, methodOnB, false);
     Assert.assertNull(singleVirtualTarget);
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
index ca29588..ac0cdef 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -137,22 +138,23 @@
 
   public static List<byte[]> DUMPS = ImmutableList.of(BaseDump.dump(), ADump.dump());
 
+  private static AppView<AppInfoWithLiveness> appView;
   private static AppInfoWithLiveness appInfo;
 
   @BeforeClass
   public static void computeAppInfo() throws Exception {
-    appInfo =
+    appView =
         computeAppViewWithLiveness(
-                buildClasses(CLASSES)
-                    .addClassProgramData(DUMPS)
-                    .addLibraryFile(getMostRecentAndroidJar())
-                    .build(),
-                Main.class)
-            .appInfo();
+            buildClasses(CLASSES)
+                .addClassProgramData(DUMPS)
+                .addLibraryFile(getMostRecentAndroidJar())
+                .build(),
+            Main.class);
+    appInfo = appView.appInfo();
   }
 
   private static DexMethod buildMethod(Class clazz, String name) {
-    return buildNullaryVoidMethod(clazz, name, appInfo.dexItemFactory());
+    return buildNullaryVoidMethod(clazz, name, appView.dexItemFactory());
   }
 
   @Parameters(name = "{0}")
@@ -171,14 +173,15 @@
 
   @Test
   public void lookupSingleTarget() {
-    DexProgramClass bClass = appInfo.definitionForProgramType(methodOnB.holder);
+    DexProgramClass bClass = appView.definitionForProgramType(methodOnB.holder);
     MethodResolutionResult resolutionResult =
         appInfo.resolveMethodOnClass(methodOnB, methodOnB.holder);
     DexEncodedMethod resolved = resolutionResult.getSingleTarget();
     assertEquals(methodOnA, resolved.getReference());
     assertFalse(resolutionResult.isVirtualTarget());
     DexEncodedMethod singleVirtualTarget =
-        appInfo.lookupSingleVirtualTarget(methodOnB, bClass.getProgramDefaultInitializer(), false);
+        appInfo.lookupSingleVirtualTarget(
+            appView, methodOnB, bClass.getProgramDefaultInitializer(), false);
     Assert.assertNull(singleVirtualTarget);
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java b/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java
index a8bbdf4..7664322 100644
--- a/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java
@@ -22,7 +22,8 @@
 import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
-import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.DynamicTypeWithLowerBound;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
 import java.util.Set;
@@ -61,10 +62,16 @@
     ProgramMethod mainMethod =
         appInfo.definitionForProgramType(typeMain).lookupProgramMethod(mainMethodReference);
     DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    ClassTypeElement latticeB =
-        ClassTypeElement.create(typeB, Nullability.definitelyNotNull(), appView);
+    TypeElement latticeA = typeA.toTypeElement(appView);
+    ClassTypeElement latticeB = typeB.toTypeElement(appView).asClassType();
     DexEncodedMethod singleTarget =
-        appInfo.lookupSingleVirtualTarget(fooA, mainMethod, false, t -> false, typeA, latticeB);
+        appInfo.lookupSingleVirtualTarget(
+            appView,
+            fooA,
+            mainMethod,
+            false,
+            t -> false,
+            DynamicTypeWithLowerBound.create(appView, latticeA, latticeB));
     assertNotNull(singleTarget);
     DexMethod fooB = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory());
     assertEquals(fooB, singleTarget.getReference());
@@ -88,10 +95,16 @@
     ProgramMethod mainMethod =
         appInfo.definitionForProgramType(typeMain).lookupProgramMethod(mainMethodReference);
     DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    ClassTypeElement latticeB =
-        ClassTypeElement.create(typeB, Nullability.definitelyNotNull(), appView);
+    TypeElement latticeA = typeA.toTypeElement(appView);
+    ClassTypeElement latticeB = typeB.toTypeElement(appView).asClassType();
     DexEncodedMethod singleTarget =
-        appInfo.lookupSingleVirtualTarget(fooA, mainMethod, false, t -> false, typeA, latticeB);
+        appInfo.lookupSingleVirtualTarget(
+            appView,
+            fooA,
+            mainMethod,
+            false,
+            t -> false,
+            DynamicTypeWithLowerBound.create(appView, latticeA, latticeB));
     assertNotNull(singleTarget);
     DexMethod fooB = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory());
     assertEquals(fooB, singleTarget.getReference());
@@ -138,10 +151,16 @@
               assert false;
             });
     assertEquals(expected, actual);
-    ClassTypeElement latticeC =
-        ClassTypeElement.create(typeC, Nullability.definitelyNotNull(), appView);
+    TypeElement latticeA = typeA.toTypeElement(appView);
+    ClassTypeElement latticeC = typeC.toTypeElement(appView).asClassType();
     DexEncodedMethod singleTarget =
-        appInfo.lookupSingleVirtualTarget(fooA, mainMethod, false, t -> false, typeA, latticeC);
+        appInfo.lookupSingleVirtualTarget(
+            appView,
+            fooA,
+            mainMethod,
+            false,
+            t -> false,
+            DynamicTypeWithLowerBound.create(appView, latticeA, latticeC));
     assertNull(singleTarget);
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/SuccessAndInvalidLookupTest.java b/src/test/java/com/android/tools/r8/resolution/singletarget/SuccessAndInvalidLookupTest.java
index 85bd2ca..ca2dee8 100644
--- a/src/test/java/com/android/tools/r8/resolution/singletarget/SuccessAndInvalidLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/singletarget/SuccessAndInvalidLookupTest.java
@@ -17,7 +17,11 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound;
+import com.android.tools.r8.optimize.interfaces.collection.NonEmptyOpenClosedInterfacesCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Collections;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -51,13 +55,17 @@
     ProgramMethod mainMethod =
         appInfo.definitionForProgramType(typeMain).lookupProgramMethod(mainMethodReference);
     DexType typeA = buildType(A.class, appInfo.dexItemFactory());
+    DynamicType dynamicTypeA =
+        DynamicTypeWithUpperBound.create(appView, typeA.toTypeElement(appView));
     DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
     DexEncodedMethod singleTarget =
-        appInfo.lookupSingleVirtualTarget(fooA, mainMethod, false, t -> false, typeA, null);
+        appInfo.lookupSingleVirtualTarget(
+            appView, fooA, mainMethod, false, t -> false, dynamicTypeA);
     assertNotNull(singleTarget);
     assertEquals(fooA, singleTarget.getReference());
     DexEncodedMethod invalidSingleTarget =
-        appInfo.lookupSingleVirtualTarget(fooA, mainMethod, true, t -> false, typeA, null);
+        appInfo.lookupSingleVirtualTarget(
+            appView, fooA, mainMethod, true, t -> false, dynamicTypeA);
     assertNull(invalidSingleTarget);
   }
 
@@ -70,6 +78,8 @@
                 .build(),
             factory ->
                 buildConfigForRules(factory, buildKeepRuleForClassAndMethods(Main.class, factory)));
+    appView.setOpenClosedInterfacesCollection(
+        new NonEmptyOpenClosedInterfacesCollection(Collections.emptySet()));
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexType typeMain = buildType(Main.class, appInfo.dexItemFactory());
     DexMethod mainMethodReference =
@@ -77,14 +87,18 @@
     ProgramMethod mainMethod =
         appInfo.definitionForProgramType(typeMain).lookupProgramMethod(mainMethodReference);
     DexType typeA = buildType(I.class, appInfo.dexItemFactory());
+    DynamicType dynamicTypeA =
+        DynamicTypeWithUpperBound.create(appView, typeA.toTypeElement(appView));
     DexMethod fooI = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
     DexEncodedMethod singleTarget =
-        appInfo.lookupSingleVirtualTarget(fooI, mainMethod, true, t -> false, typeA, null);
+        appInfo.lookupSingleVirtualTarget(
+            appView, fooI, mainMethod, true, t -> false, dynamicTypeA);
     assertNotNull(singleTarget);
     assertEquals(fooA, singleTarget.getReference());
     DexEncodedMethod invalidSingleTarget =
-        appInfo.lookupSingleVirtualTarget(fooI, mainMethod, false, t -> false, typeA, null);
+        appInfo.lookupSingleVirtualTarget(
+            appView, fooI, mainMethod, false, t -> false, dynamicTypeA);
     assertNull(invalidSingleTarget);
   }
 
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
index fb48ff4..84f5522 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
@@ -32,10 +32,7 @@
           RetraceApiUnknownJsonTest.ApiTest.class,
           RetraceApiRewriteFrameInlineNpeTest.ApiTest.class,
           RetraceApiAmbiguousOriginalRangeTest.ApiTest.class,
-          RetraceApiOutsideLineRangeTest.ApiTest.class);
-
-  public static List<Class<? extends RetraceApiBinaryTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
-      ImmutableList.of(
+          RetraceApiOutsideLineRangeTest.ApiTest.class,
           RetraceApiRewriteFrameInlineNpeResidualTest.ApiTest.class,
           RetraceApiOutlineNoInlineTest.ApiTest.class,
           RetraceApiOutlineInlineTest.ApiTest.class,
@@ -43,6 +40,9 @@
           RetraceApiInlineInOutlineTest.ApiTest.class,
           RetraceApiSingleFrameTest.ApiTest.class);
 
+  public static List<Class<? extends RetraceApiBinaryTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
+      ImmutableList.of();
+
   private final TemporaryFolder temp;
 
   public RetraceApiTestCollection(TemporaryFolder temp) {
diff --git a/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java b/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java
index e59bfb9..a9491f4 100644
--- a/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java
@@ -56,6 +56,14 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(JavaScriptScriptEngineTest.class)
         .addKeepMainRule(TestClass.class)
+        .applyIf(
+            parameters.isDexRuntime(),
+            testBuilder ->
+                testBuilder.addOptionsModification(
+                    options ->
+                        options
+                            .getOpenClosedInterfacesOptions()
+                            .suppressAllOpenInterfacesDueToMissingClasses()))
         .setMinApi(parameters.getApiLevel())
         .apply(
             b -> {
diff --git a/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java b/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java
index dd55c22..7bb21a3 100644
--- a/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java
@@ -56,6 +56,14 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(ScriptEngineTest.class)
         .addKeepMainRule(TestClass.class)
+        .applyIf(
+            parameters.isDexRuntime(),
+            testBuilder ->
+                testBuilder.addOptionsModification(
+                    options ->
+                        options
+                            .getOpenClosedInterfacesOptions()
+                            .suppressAllOpenInterfacesDueToMissingClasses()))
         .setMinApi(parameters.getApiLevel())
         .addDataEntryResources(
             DataEntryResource.fromBytes(
diff --git a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
index f22ae69..9d3e55f 100644
--- a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRunResult;
-import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
@@ -40,8 +39,7 @@
     D8,
     JAVAC,
     PROGUARD,
-    R8,
-    R8_ENABLE_UNININSTANTATED_TYPE_OPTIMIZATION_FOR_INTERFACES
+    R8
   }
 
   private enum Mode {
@@ -49,7 +47,7 @@
 
       @Override
       public String getExpectedOutput(
-          Compiler compiler, TestRuntime runtime, boolean useInterface) {
+          Compiler compiler, TestParameters parameters, boolean useInterface) {
         return StringUtils.joinLines("Hello!", "Goodbye!", "");
       }
 
@@ -62,21 +60,37 @@
 
       @Override
       public String getExpectedOutput(
-          Compiler compiler, TestRuntime runtime, boolean useInterface) {
+          Compiler compiler, TestParameters parameters, boolean useInterface) {
         if (!useInterface) {
           return StringUtils.joinLines("Hello!", "");
         }
 
         switch (compiler) {
-          case D8:
+          case JAVAC:
+            return StringUtils.joinLines("Hello!", "Goodbye!", "");
+
           case DX:
-            switch (runtime.asDex().getVm().getVersion()) {
+          case D8:
+          case R8:
+            if (parameters.isCfRuntime()) {
+              assert compiler == Compiler.R8;
+              return StringUtils.joinLines("Hello!", "Goodbye!", "");
+            }
+            switch (parameters.getDexRuntimeVersion()) {
               case V4_0_4:
               case V4_4_4:
               case V10_0_0:
               case V12_0_0:
                 return StringUtils.joinLines("Hello!", "Goodbye!", "");
 
+              case V5_1_1:
+              case V6_0_1:
+              case V8_1_0:
+              case V9_0_0:
+              case DEFAULT:
+                return StringUtils.joinLines(
+                    "Hello!", "Unexpected outcome of checkcast", "Goodbye!", "");
+
               case V7_0_0:
               case V13_MASTER:
                 return StringUtils.joinLines(
@@ -87,25 +101,13 @@
                     "");
 
               default:
-                // Fallthrough.
+                throw new Unreachable();
             }
 
-          case R8:
           case PROGUARD:
             return StringUtils.joinLines(
                 "Hello!", "Unexpected outcome of checkcast", "Goodbye!", "");
 
-          case R8_ENABLE_UNININSTANTATED_TYPE_OPTIMIZATION_FOR_INTERFACES:
-            return StringUtils.joinLines(
-                "Hello!",
-                "Unexpected outcome of getstatic",
-                "Unexpected outcome of checkcast",
-                "Goodbye!",
-                "");
-
-          case JAVAC:
-            return StringUtils.joinLines("Hello!", "Goodbye!", "");
-
           default:
             throw new Unreachable();
         }
@@ -120,14 +122,13 @@
 
       @Override
       public String getExpectedOutput(
-          Compiler compiler, TestRuntime runtime, boolean useInterface) {
+          Compiler compiler, TestParameters parameters, boolean useInterface) {
         if (useInterface) {
           return StringUtils.joinLines("Hello!", "In verifiable method!", "Goodbye!", "");
         }
 
         switch (compiler) {
           case R8:
-          case R8_ENABLE_UNININSTANTATED_TYPE_OPTIMIZATION_FOR_INTERFACES:
           case PROGUARD:
             // The unverifiable method has been removed as a result of tree shaking, so the code
             // does not fail with a verification error when trying to load class UnverifiableClass.
@@ -147,7 +148,7 @@
     };
 
     public abstract String getExpectedOutput(
-        Compiler compiler, TestRuntime runtime, boolean useInterface);
+        Compiler compiler, TestParameters parameters, boolean useInterface);
 
     public abstract String instruction();
   }
@@ -301,7 +302,10 @@
             .addOptionsModification(
                 options -> {
                   if (mode == Mode.INVOKE_UNVERIFIABLE_METHOD) {
+                    options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces();
                     options.testing.allowTypeErrors = true;
+                  } else if (mode == Mode.INVOKE_VERIFIABLE_METHOD_ON_UNVERIFIABLE_CLASS) {
+                    options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces();
                   }
                 })
             .allowDiagnosticWarningMessages(allowDiagnosticWarningMessages)
@@ -316,36 +320,6 @@
                                 + " type check and will be assumed to be unreachable.")))
             .run(parameters.getRuntime(), mainClass.name);
     checkTestRunResult(r8Result, Compiler.R8);
-
-    R8TestRunResult r8ResultWithUninstantiatedTypeOptimizationForInterfaces =
-        testForR8(parameters.getBackend())
-            .addProgramFiles(inputJar)
-            .addKeepMainRule(mainClass.name)
-            .addKeepRules(
-                "-keep class TestClass { public static I g; }",
-                "-neverinline class TestClass { public static void m(); }")
-            .enableProguardTestOptions()
-            .addOptionsModification(
-                options -> {
-                  if (mode == Mode.INVOKE_UNVERIFIABLE_METHOD) {
-                    options.testing.allowTypeErrors = true;
-                  }
-                  options.enableUninstantiatedTypeOptimizationForInterfaces = true;
-                })
-            .allowDiagnosticWarningMessages(allowDiagnosticWarningMessages)
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .applyIf(
-                allowDiagnosticWarningMessages,
-                result ->
-                    result.assertAllWarningMessagesMatch(
-                        equalTo(
-                            "The method `void UnverifiableClass.unverifiableMethod()` does not"
-                                + " type check and will be assumed to be unreachable.")))
-            .run(parameters.getRuntime(), mainClass.name);
-    checkTestRunResult(
-        r8ResultWithUninstantiatedTypeOptimizationForInterfaces,
-        Compiler.R8_ENABLE_UNININSTANTATED_TYPE_OPTIMIZATION_FOR_INTERFACES);
   }
 
   private void checkTestRunResult(TestRunResult<?> result, Compiler compiler) {
@@ -359,7 +333,6 @@
           result.assertSuccessWithOutput(getExpectedOutput(compiler));
         } else {
           if (compiler == Compiler.R8
-              || compiler == Compiler.R8_ENABLE_UNININSTANTATED_TYPE_OPTIMIZATION_FOR_INTERFACES
               || compiler == Compiler.PROGUARD) {
             result.assertSuccessWithOutput(getExpectedOutput(compiler));
           } else {
@@ -374,8 +347,7 @@
         if (useInterface) {
           result.assertSuccessWithOutput(getExpectedOutput(compiler));
         } else {
-          if (compiler == Compiler.R8
-              || compiler == Compiler.R8_ENABLE_UNININSTANTATED_TYPE_OPTIMIZATION_FOR_INTERFACES) {
+          if (compiler == Compiler.R8) {
             result
                 .assertFailureWithOutput(getExpectedOutput(compiler))
                 .assertFailureWithErrorThatMatches(
@@ -396,7 +368,7 @@
   }
 
   private String getExpectedOutput(Compiler compiler) {
-    return mode.getExpectedOutput(compiler, parameters.getRuntime(), useInterface);
+    return mode.getExpectedOutput(compiler, parameters, useInterface);
   }
 
   private Matcher<String> getMatcherForExpectedError() {
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index f53c113..f85290d 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -212,6 +212,11 @@
             .addKeepMainRule(R8.class)
             .addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
             .addKeepRules("-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
+            .addOptionsModification(
+                options ->
+                    options
+                        .getOpenClosedInterfacesOptions()
+                        .suppressZipFileAssignmentsToJavaLangAutoCloseable())
             .addDontWarnGoogle()
             .addDontWarnJavaxNullableAnnotation()
             .apply(this::configureHorizontalClassMerging)
@@ -231,6 +236,11 @@
                     + " *** *(...); }")
             .addDontWarnGoogle()
             .addDontWarnJavaxNullableAnnotation()
+            .addOptionsModification(
+                options ->
+                    options
+                        .getOpenClosedInterfacesOptions()
+                        .suppressZipFileAssignmentsToJavaLangAutoCloseable())
             .apply(this::configureHorizontalClassMerging)
             .compile()
             .graphInspector();
@@ -249,6 +259,11 @@
                     + " *** *(...); }")
             .addDontWarnGoogle()
             .addDontWarnJavaxNullableAnnotation()
+            .addOptionsModification(
+                options ->
+                    options
+                        .getOpenClosedInterfacesOptions()
+                        .suppressZipFileAssignmentsToJavaLangAutoCloseable())
             .apply(this::configureHorizontalClassMerging)
             .compile()
             .graphInspector();
@@ -269,6 +284,11 @@
                     + " <2> <3>(...); }")
             .addDontWarnGoogle()
             .addDontWarnJavaxNullableAnnotation()
+            .addOptionsModification(
+                options ->
+                    options
+                        .getOpenClosedInterfacesOptions()
+                        .suppressZipFileAssignmentsToJavaLangAutoCloseable())
             .apply(this::configureHorizontalClassMerging)
             .compile()
             .graphInspector();
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index a296b9b..6c05cb7 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -183,7 +183,7 @@
   public void resetAllowTestOptions() {
     handler = new KeepingDiagnosticHandler();
     reporter = new Reporter(handler);
-    parser = new ProguardConfigurationParser(new DexItemFactory(), reporter, true);
+    parser = new ProguardConfigurationParser(new DexItemFactory(), reporter, null, true);
   }
 
   @Test
@@ -992,7 +992,7 @@
   @Test
   public void parseKeepdirectories() {
     ProguardConfigurationParser parser =
-        new ProguardConfigurationParser(new DexItemFactory(), reporter, false);
+        new ProguardConfigurationParser(new DexItemFactory(), reporter, null, false);
     parser.parse(Paths.get(KEEPDIRECTORIES));
     verifyParserEndsCleanly();
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/RemoveCallToStaticInitTest.java b/src/test/java/com/android/tools/r8/shaking/RemoveCallToStaticInitTest.java
index 0d1fb3d..27c98f6 100644
--- a/src/test/java/com/android/tools/r8/shaking/RemoveCallToStaticInitTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/RemoveCallToStaticInitTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.shaking;
 
 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.NeverInline;
@@ -51,14 +50,12 @@
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .run(parameters.getRuntime(), Main.class)
-        // TODO(b/220667525): R8 should emit EXPECTED
-        .assertSuccessWithOutputLines(R8_EXPECTED)
+        .assertSuccessWithOutputLines(EXPECTED)
         .inspect(
             inspector -> {
               ClassSubject clazz = inspector.clazz(B.class);
               assertThat(clazz, isPresent());
-              // TODO(b/220667525): Should not remove bridge due to class init.
-              assertThat(clazz.uniqueMethodWithName("foo"), not(isPresent()));
+              assertThat(clazz.uniqueMethodWithName("foo"), isPresent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
index 94bdbf1..82d8fff 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
@@ -47,6 +47,11 @@
         .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
         .addKeepRuleFiles(MAIN_KEEP)
         .addKeepRules(WHY_ARE_YOU_KEEPING_ALL)
+        .addOptionsModification(
+            options ->
+                options
+                    .getOpenClosedInterfacesOptions()
+                    .suppressZipFileAssignmentsToJavaLangAutoCloseable())
         .collectStdout()
         .compile()
         .assertStdoutThatMatches(containsString("referenced in keep rule"))
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
index 6f868fc..1464e82 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
@@ -5,7 +5,6 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
-import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestBase;
@@ -58,8 +57,11 @@
             .inspector();
     ClassSubject itf = inspector.clazz(M_I);
     assertThat(itf, isPresent());
+    // TODO(b/214496607): This could be removed as a result of devirtualization, but this only
+    //  happens if the call site is reprocessed, since we don't have knowledge of open/closed
+    //  interfaces until the second optimization pass.
     MethodSubject mtd = itf.uniqueMethodWithName("onEnterForeground");
-    assertThat(mtd, not(isPresent()));
+    assertThat(mtd, isPresent());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
index 047a109..c850ae8 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
@@ -139,8 +139,8 @@
   private void noInterfaceKept(CodeInspector inspector) {
     // Indirectly assert that method is inlined into x, y and z and that redundant field loads
     // remove invokes.
-    assertEquals(0, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
-    assertEquals(0, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
+    assertEquals(3, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
+    assertEquals(3, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
     assertEquals(0, countInstructionInZ(inspector, InstructionSubject::isInvokeVirtual));
   }
 
@@ -149,7 +149,10 @@
     runTest(
         ImmutableList.of(),
         this::noInterfaceKept,
-        "TestClass 1\nTestClass 1\nTestClass 1\nEXCEPTION\n");
+        "TestClass 1\nTestClass 1\nTestClass 1\nProxy\nProxy\nProxy\n"
+            + "TestClass 2\nTestClass 2\nTestClass 2\nProxy\nProxy\nProxy\n"
+            + "TestClass 3\nTestClass 3\nTestClass 3\n"
+            + "TestClass 4\nTestClass 4\nTestClass 4\nSUCCESS\n");
   }
 
   private void baseInterfaceKept(CodeInspector inspector) {
@@ -157,7 +160,7 @@
     assertEquals(3, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
     // Indirectly assert that method is inlined into y and z and that redundant field loads
     // remove invokes.
-    assertEquals(0, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
+    assertEquals(3, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
     assertEquals(0, countInstructionInZ(inspector, InstructionSubject::isInvokeVirtual));
     assertEquals(0, countInstructionInZSubClass(inspector, InstructionSubject::isInvokeVirtual));
   }
@@ -171,7 +174,9 @@
             "}"),
         this::baseInterfaceKept,
         "TestClass 1\nTestClass 1\nTestClass 1\nProxy\nProxy\nProxy\n"
-            + "TestClass 2\nTestClass 2\nTestClass 2\nEXCEPTION\n");
+            + "TestClass 2\nTestClass 2\nTestClass 2\nProxy\nProxy\nProxy\n"
+            + "TestClass 3\nTestClass 3\nTestClass 3\n"
+            + "TestClass 4\nTestClass 4\nTestClass 4\nSUCCESS\n");
   }
 
   private void subInterfaceKept(CodeInspector inspector) {
diff --git a/third_party/retrace/binary_compatibility.tar.gz.sha1 b/third_party/retrace/binary_compatibility.tar.gz.sha1
index ee23c0c..26b5e24 100644
--- a/third_party/retrace/binary_compatibility.tar.gz.sha1
+++ b/third_party/retrace/binary_compatibility.tar.gz.sha1
@@ -1 +1 @@
-40f612b228f0520a11a2e2f8745400163c70c82f
\ No newline at end of file
+3238b42ace7a8e81abf0336c069645c5b97dd470
\ No newline at end of file
diff --git a/tools/archive_desugar_jdk_libs.py b/tools/archive_desugar_jdk_libs.py
index 3a9cfd6..cc9bee4 100755
--- a/tools/archive_desugar_jdk_libs.py
+++ b/tools/archive_desugar_jdk_libs.py
@@ -106,7 +106,7 @@
         bazel,
         '--bazelrc=/dev/null',
         'build',
-        '--sandbox_debug',
+        '--spawn_strategy=local',
         '--verbose_failures',
         'maven_release' + ('_jdk11' if variant == 'jdk11' else '')]
     utils.PrintCmd(cmd)