Merge commit '685a8c81f5387eb165566f7ec48a79241b3d35d0' into dev-release
diff --git a/.gitignore b/.gitignore
index 80c9662..07f90a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -262,6 +262,8 @@
 tools/*/art-9.0.0.tar.gz
 tools/*/art-10.0.0
 tools/*/art-10.0.0.tar.gz
+tools/*/host/art-12.0.0-beta4
+tools/*/host/art-12.0.0-beta4.tar.gz
 tools/*/art.tar.gz
 tools/*/dalvik
 tools/*/dalvik-4.0.4
diff --git a/build.gradle b/build.gradle
index 4a8dd81..8b0525f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -361,6 +361,7 @@
                 "linux/art-8.1.0",
                 "linux/art-9.0.0",
                 "linux/art-10.0.0",
+                "linux/host/art-12.0.0-beta4",
                 "linux/dalvik",
                 "linux/dalvik-4.0.4",
                 "${osString}/dx",
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg
index 9fe678c..85170a7 100644
--- a/infra/config/global/cr-buildbucket.cfg
+++ b/infra/config/global/cr-buildbucket.cfg
@@ -390,6 +390,24 @@
       }
     }
     builders {
+      name: "linux-android-12.0.0"
+      mixins: "linux"
+      mixins: "normal"
+      recipe {
+        properties: "tool:r8"
+        properties: "dex_vm:12.0.0"
+      }
+    }
+    builders {
+      name: "linux-android-12.0.0_release"
+      mixins: "linux"
+      mixins: "normal"
+      recipe {
+        properties: "tool:r8"
+        properties: "dex_vm:12.0.0"
+      }
+    }
+    builders {
       name: "linux-internal"
       mixins: "linux"
       mixins: "internal"
diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg
index b881778..c105138 100644
--- a/infra/config/global/generated/cr-buildbucket.cfg
+++ b/infra/config/global/generated/cr-buildbucket.cfg
@@ -347,6 +347,50 @@
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
     }
     builders {
+      name: "linux-android=12.0.0"
+      swarming_host: "chrome-swarming.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "normal:true"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "pool:luci.r8.ci"
+      recipe {
+        name: "rex"
+        cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
+        cipd_version: "refs/heads/master"
+        properties_j: "builder_group:\"internal.client.r8\""
+        properties_j: "test_options:[\"--dex_vm=12.0.0\",\"--all_tests\",\"--tool=r8\",\"--no_internal\",\"--one_line_per_test\",\"--archive_failures\"]"
+      }
+      priority: 26
+      execution_timeout_secs: 21600
+      expiration_secs: 126000
+      build_numbers: YES
+      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    }
+    builders {
+      name: "linux-android=12.0.0_release"
+      swarming_host: "chrome-swarming.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "normal:true"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "pool:luci.r8.ci"
+      recipe {
+        name: "rex"
+        cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
+        cipd_version: "refs/heads/master"
+        properties_j: "builder_group:\"internal.client.r8\""
+        properties_j: "test_options:[\"--dex_vm=12.0.0\",\"--all_tests\",\"--tool=r8\",\"--no_internal\",\"--one_line_per_test\",\"--archive_failures\"]"
+      }
+      priority: 26
+      execution_timeout_secs: 21600
+      expiration_secs: 126000
+      build_numbers: YES
+      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    }
+    builders {
       name: "linux-android=8.1.0"
       swarming_host: "chrome-swarming.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
diff --git a/infra/config/global/generated/luci-milo.cfg b/infra/config/global/generated/luci-milo.cfg
index 58474a2..7ffcae6 100644
--- a/infra/config/global/generated/luci-milo.cfg
+++ b/infra/config/global/generated/luci-milo.cfg
@@ -186,6 +186,16 @@
     short_name: "android=10.0.0_release"
   }
   builders {
+    name: "buildbucket/luci.r8.ci/linux-android=12.0.0"
+    category: "R8"
+    short_name: "android=12.0.0"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-android=12.0.0_release"
+    category: "R8 release"
+    short_name: "android=12.0.0_release"
+  }
+  builders {
     name: "buildbucket/luci.r8.ci/windows"
     category: "R8"
     short_name: "windows"
diff --git a/infra/config/global/generated/luci-notify.cfg b/infra/config/global/generated/luci-notify.cfg
index c441677..25f83b5 100644
--- a/infra/config/global/generated/luci-notify.cfg
+++ b/infra/config/global/generated/luci-notify.cfg
@@ -180,6 +180,30 @@
   }
   builders {
     bucket: "ci"
+    name: "linux-android=12.0.0"
+    repository: "https://r8.googlesource.com/r8"
+  }
+}
+notifiers {
+  notifications {
+    on_failure: true
+    on_new_failure: true
+    notify_blamelist {}
+  }
+  builders {
+    bucket: "ci"
+    name: "linux-android=12.0.0_release"
+    repository: "https://r8.googlesource.com/r8"
+  }
+}
+notifiers {
+  notifications {
+    on_failure: true
+    on_new_failure: true
+    notify_blamelist {}
+  }
+  builders {
+    bucket: "ci"
     name: "linux-android=8.1.0"
     repository: "https://r8.googlesource.com/r8"
   }
diff --git a/infra/config/global/generated/luci-scheduler.cfg b/infra/config/global/generated/luci-scheduler.cfg
index b4ee1f0..e915f53 100644
--- a/infra/config/global/generated/luci-scheduler.cfg
+++ b/infra/config/global/generated/luci-scheduler.cfg
@@ -155,6 +155,24 @@
   }
 }
 job {
+  id: "linux-android=12.0.0"
+  acl_sets: "ci"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-android=12.0.0"
+  }
+}
+job {
+  id: "linux-android=12.0.0_release"
+  acl_sets: "ci"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-android=12.0.0_release"
+  }
+}
+job {
   id: "linux-android=8.1.0"
   acl_sets: "ci"
   buildbucket {
@@ -372,6 +390,7 @@
   triggers: "linux-android-6.0.1_release"
   triggers: "linux-android-7.0.0_release"
   triggers: "linux-android=10.0.0_release"
+  triggers: "linux-android=12.0.0_release"
   triggers: "linux-android=8.1.0_release"
   triggers: "linux-android=9.0.0_release"
   triggers: "linux-d8_jctf_release"
@@ -399,6 +418,7 @@
   triggers: "linux-android-6.0.1"
   triggers: "linux-android-7.0.0"
   triggers: "linux-android=10.0.0"
+  triggers: "linux-android=12.0.0"
   triggers: "linux-android=8.1.0"
   triggers: "linux-android=9.0.0"
   triggers: "linux-d8_jctf"
diff --git a/infra/config/global/luci-milo.cfg b/infra/config/global/luci-milo.cfg
index 8218353..3c2ad37 100644
--- a/infra/config/global/luci-milo.cfg
+++ b/infra/config/global/luci-milo.cfg
@@ -76,6 +76,11 @@
     short_name: "10.0.0"
   }
   builders {
+    name: "buildbucket/luci.r8.ci/linux-android-12.0.0"
+    category: "R8"
+    short_name: "12.0.0"
+  }
+  builders {
     name: "buildbucket/luci.r8.ci/linux-internal"
     category: "R8"
     short_name: "internal"
@@ -186,6 +191,11 @@
     short_name: "10.0.0"
   }
   builders {
+    name: "buildbucket/luci.r8.ci/linux-android-12.0.0_release"
+    category: "R8 release"
+    short_name: "12.0.0"
+  }
+  builders {
     name: "buildbucket/luci.r8.ci/linux-internal_release"
     category: "R8 release"
     short_name: "internal"
diff --git a/infra/config/global/luci-notify.cfg b/infra/config/global/luci-notify.cfg
index 92b76f3..1e0eb27 100644
--- a/infra/config/global/luci-notify.cfg
+++ b/infra/config/global/luci-notify.cfg
@@ -124,6 +124,16 @@
     repository: "https://r8.googlesource.com/r8"
   }
   builders {
+    name: "linux-android-12.0.0"
+    bucket: "ci"
+    repository: "https://r8.googlesource.com/r8"
+  }
+  builders {
+    name: "linux-android-12.0.0_release"
+    bucket: "ci"
+    repository: "https://r8.googlesource.com/r8"
+  }
+  builders {
     name: "linux-internal"
     bucket: "ci"
     repository: "https://r8.googlesource.com/r8"
diff --git a/infra/config/global/luci-scheduler.cfg b/infra/config/global/luci-scheduler.cfg
index 4bd0b89..f585e8d 100644
--- a/infra/config/global/luci-scheduler.cfg
+++ b/infra/config/global/luci-scheduler.cfg
@@ -39,6 +39,7 @@
   triggers: "linux-android-8.1.0"
   triggers: "linux-android-9.0.0"
   triggers: "linux-android-10.0.0"
+  triggers: "linux-android-12.0.0"
   triggers: "linux-run-on-app-dump"
   triggers: "linux-internal"
   triggers: "linux-jctf"
@@ -91,6 +92,7 @@
   triggers: "linux-android-8.1.0_release"
   triggers: "linux-android-9.0.0_release"
   triggers: "linux-android-10.0.0_release"
+  triggers: "linux-android-12.0.0_release"
   triggers: "linux-internal_release"
   triggers: "linux-jctf_release"
   triggers: "r8cf-linux-jctf_release"
@@ -529,6 +531,34 @@
 }
 
 job {
+  id: "linux-android-12.0.0"
+  acl_sets: "default"
+  triggering_policy: {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 6
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-android-12.0.0"
+  }
+}
+
+job {
+  id: "linux-android-12.0.0_release"
+  acl_sets: "default"
+  triggering_policy: {
+    max_batch_size: 1
+    max_concurrent_invocations: 3
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-android-12.0.0_release"
+  }
+}
+
+job {
   id: "linux-internal"
   acl_sets: "default"
   buildbucket {
diff --git a/infra/config/global/main.star b/infra/config/global/main.star
index e78b09a..beda63e 100755
--- a/infra/config/global/main.star
+++ b/infra/config/global/main.star
@@ -237,6 +237,8 @@
     ["--dex_vm=9.0.0", "--all_tests"])
 r8_tester_with_default("linux-android=10.0.0",
     ["--dex_vm=10.0.0", "--all_tests"])
+r8_tester_with_default("linux-android=12.0.0",
+    ["--dex_vm=12.0.0", "--all_tests"])
 
 r8_tester_with_default("windows", ["--all_tests"],
     dimensions=get_dimensions(windows=True))
diff --git a/scripts/update-host-art.sh b/scripts/update-host-art.sh
index 76572e0..58165cd 100755
--- a/scripts/update-host-art.sh
+++ b/scripts/update-host-art.sh
@@ -109,11 +109,15 @@
 # dalvikvm32 or dalvikvm64).
 cp $ANDROID_HOST_BUILD/bin/dalvikvm $DEST/bin
 cp $ANDROID_HOST_BUILD/bin/dex2oat $DEST/bin
+if [ -e $ANDROID_HOST_BUILD/bin/dex2oat64 ]
+  then cp $ANDROID_HOST_BUILD/bin/dex2oat64 $DEST/bin
+fi
 if [ -e $ANDROID_HOST_BUILD/bin/patchoat ]
   # File patchoat does not exist on Q anymore.
   then cp $ANDROID_HOST_BUILD/bin/patchoat $DEST/bin
 fi
 
+
 # Required framework files.
 mkdir -p $DEST/framework
 cp -R $ANDROID_HOST_BUILD/framework/* $DEST/framework
@@ -127,15 +131,32 @@
 # Image files required for dex2oat of actual android apps. We need an actual android product
 # image containing framework classes to verify the code against.
 mkdir -p $DEST/product/$ANDROID_PRODUCT/system/framework
-cp -r $ANDROID_TARGET_BUILD/product/$ANDROID_PRODUCT/system/framework/* $DEST/product/$ANDROID_PRODUCT/system/framework
+cp -rL $ANDROID_TARGET_BUILD/product/$ANDROID_PRODUCT/system/framework/* $DEST/product/$ANDROID_PRODUCT/system/framework
 
 # Required auxillary files.
-if [ -e $ANDROID_HOST_BUILD/usr/icu ]; then
-  mkdir -p $DEST/usr/icu
-  cp -r $ANDROID_HOST_BUILD/usr/icu/* $DEST/usr/icu
+if [ -e $ANDROID_HOST_BUILD/apex ]; then
+  mkdir -p $DEST/apex
+  cp -rL $ANDROID_HOST_BUILD/apex/* $DEST/apex
+  if [ -e $ANDROID_HOST_BUILD/com.android.i18n ]; then
+    mkdir -p $DEST/com.android.i18n
+    cp -r $ANDROID_HOST_BUILD/com.android.i18n/* $DEST/com.android.i18n
+  fi
+  if [ -e $ANDROID_HOST_BUILD/com.android.tzdata ]; then
+    mkdir -p $DEST/com.android.tzdata
+    cp -r $ANDROID_HOST_BUILD/com.android.tzdata/* $DEST/com.android.tzdata
+  fi
+  if [ -e $ANDROID_HOST_BUILD/usr ]; then
+    mkdir -p $DEST/usr
+    cp -r $ANDROID_HOST_BUILD/usr/* $DEST/usr
+  fi
 else
-  mkdir -p $DEST/com.android.runtime/etc/icu/
-  cp -r $ANDROID_HOST_BUILD/com.android.runtime/etc/icu/* $DEST/com.android.runtime/etc/icu/
+  if [ -e $ANDROID_HOST_BUILD/usr/icu ]; then
+    mkdir -p $DEST/usr/icu
+    cp -r $ANDROID_HOST_BUILD/usr/icu/* $DEST/usr/icu
+  else
+    mkdir -p $DEST/com.android.runtime/etc/icu/
+    cp -r $ANDROID_HOST_BUILD/com.android.runtime/etc/icu/* $DEST/com.android.runtime/etc/icu/
+  fi
 fi
 
 # Update links for vdex files for Android P and later.
@@ -166,3 +187,4 @@
 
 echo "Now run"
 echo "(cd $DEST_ROOT; upload_to_google_storage.py -a --bucket r8-deps $ART_DIR)"
+echo "NOTE; If $ART_DIR has several directory elements adjust accordingly."
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index af8aa34..e7efbfa 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -872,6 +872,7 @@
       internal.enableClassStaticizer = false;
       internal.outline.enabled = false;
       internal.enableEnumUnboxing = false;
+      internal.callSiteOptimizationOptions().disableOptimization();
     }
 
     if (!internal.isShrinking()) {
diff --git a/src/main/java/com/android/tools/r8/cf/CfVerifierTool.java b/src/main/java/com/android/tools/r8/cf/CfVerifierTool.java
index 7bf11e0..524b6bc 100644
--- a/src/main/java/com/android/tools/r8/cf/CfVerifierTool.java
+++ b/src/main/java/com/android/tools/r8/cf/CfVerifierTool.java
@@ -25,6 +25,7 @@
       builder.addProgramFile(Paths.get(arg));
     }
     InternalOptions options = new InternalOptions();
+    options.testing.verifyInputs = true;
     DexApplication dexApplication =
         new ApplicationReader(builder.build(), options, Timing.empty()).read();
     AppView<AppInfo> appView = AppView.createForD8(AppInfo.createInitialAppInfo(dexApplication));
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 31d40bb..b231b6e 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -142,6 +142,11 @@
     if (shouldDump) {
       dumpApplication();
     }
+
+    if (options.testing.verifyInputs) {
+      inputApp.validateInputs();
+    }
+
     timing.begin("DexApplication.read");
     final LazyLoadedDexApplication.Builder builder =
         DexApplication.builder(options, timing, resolver);
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index e6db3e1..30a1c04 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -688,15 +688,21 @@
   }
 
   public MethodResolutionResult resolveMethodOnClass(DexMethod method, DexClass clazz) {
+    return resolveMethodOnClass(clazz, method.getProto(), method.getName());
+  }
+
+  public MethodResolutionResult resolveMethodOnClass(
+      DexClass clazz, DexProto methodProto, DexString methodName) {
     assert checkIfObsolete();
     assert !clazz.isInterface();
     // Step 2:
-    MethodResolutionResult result = resolveMethodOnClassStep2(clazz, method, clazz);
+    MethodResolutionResult result =
+        resolveMethodOnClassStep2(clazz, methodProto, methodName, clazz);
     if (result != null) {
       return result;
     }
     // Finally Step 3:
-    return resolveMethodStep3(clazz, method);
+    return resolveMethodStep3(clazz, methodProto, methodName);
   }
 
   /**
@@ -705,16 +711,19 @@
    * 5.4.3.3 of the JVM Spec</a>.
    */
   private MethodResolutionResult resolveMethodOnClassStep2(
-      DexClass clazz, DexMethod method, DexClass initialResolutionHolder) {
+      DexClass clazz,
+      DexProto methodProto,
+      DexString methodName,
+      DexClass initialResolutionHolder) {
     // Pt. 1: Signature polymorphic method check.
     // See also <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9">
     // Section 2.9 of the JVM Spec</a>.
-    DexEncodedMethod result = clazz.lookupSignaturePolymorphicMethod(method.name, dexItemFactory());
+    DexEncodedMethod result = clazz.lookupSignaturePolymorphicMethod(methodName, dexItemFactory());
     if (result != null) {
       return new SingleResolutionResult(initialResolutionHolder, clazz, result);
     }
     // Pt 2: Find a method that matches the descriptor.
-    result = clazz.lookupMethod(method);
+    result = clazz.lookupMethod(methodProto, methodName);
     if (result != null) {
       // If the resolved method is private, then it can only be accessed if the symbolic reference
       // that initiated the resolution was the type at which the method resolved on. If that is not
@@ -730,7 +739,8 @@
     if (clazz.superType != null) {
       DexClass superClass = definitionFor(clazz.superType);
       if (superClass != null) {
-        return resolveMethodOnClassStep2(superClass, method, initialResolutionHolder);
+        return resolveMethodOnClassStep2(
+            superClass, methodProto, methodName, initialResolutionHolder);
       }
     }
     return null;
@@ -742,35 +752,45 @@
    * 5.4.3.3 of the JVM Spec</a>. As this is the same for interfaces and classes, we share one
    * implementation.
    */
-  private MethodResolutionResult resolveMethodStep3(DexClass clazz, DexMethod method) {
+  private MethodResolutionResult resolveMethodStep3(
+      DexClass clazz, DexProto methodProto, DexString methodName) {
     MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
-    resolveMethodStep3Helper(method, clazz, builder);
+    resolveMethodStep3Helper(methodProto, methodName, clazz, builder);
     return builder.resolve(clazz);
   }
 
   // Non-private lookup (ie, not resolution) to find interface targets.
   DexClassAndMethod lookupMaximallySpecificTarget(DexClass clazz, DexMethod method) {
     MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
-    resolveMethodStep3Helper(method, clazz, builder);
+    resolveMethodStep3Helper(method.getProto(), method.getName(), clazz, builder);
     return builder.lookup();
   }
 
   // Non-private lookup (ie, not resolution) to find interface targets.
   DexClassAndMethod lookupMaximallySpecificTarget(LambdaDescriptor lambda, DexMethod method) {
     MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
-    resolveMethodStep3Helper(method, dexItemFactory().objectType, lambda.interfaces, builder);
+    resolveMethodStep3Helper(
+        method.getProto(),
+        method.getName(),
+        dexItemFactory().objectType,
+        lambda.interfaces,
+        builder);
     return builder.lookup();
   }
 
   /** Helper method that builds the set of maximally specific methods. */
   private void resolveMethodStep3Helper(
-      DexMethod method, DexClass clazz, MaximallySpecificMethodsBuilder builder) {
+      DexProto methodProto,
+      DexString methodName,
+      DexClass clazz,
+      MaximallySpecificMethodsBuilder builder) {
     resolveMethodStep3Helper(
-        method, clazz.superType, Arrays.asList(clazz.interfaces.values), builder);
+        methodProto, methodName, clazz.superType, Arrays.asList(clazz.interfaces.values), builder);
   }
 
   private void resolveMethodStep3Helper(
-      DexMethod method,
+      DexProto methodProto,
+      DexString methodName,
       DexType superType,
       List<DexType> interfaces,
       MaximallySpecificMethodsBuilder builder) {
@@ -781,20 +801,20 @@
         continue;
       }
       assert definition.isInterface();
-      DexEncodedMethod result = definition.lookupMethod(method);
+      DexEncodedMethod result = definition.lookupMethod(methodProto, methodName);
       if (isMaximallySpecificCandidate(result)) {
         // The candidate is added and doing so will prohibit shadowed methods from being in the set.
         builder.addCandidate(definition, result, this);
       } else {
         // Look at the super-interfaces of this class and keep searching.
-        resolveMethodStep3Helper(method, definition, builder);
+        resolveMethodStep3Helper(methodProto, methodName, definition, builder);
       }
     }
     // Now look at indirect super interfaces.
     if (superType != null) {
       DexClass superClass = definitionFor(superType);
       if (superClass != null) {
-        resolveMethodStep3Helper(method, superClass, builder);
+        resolveMethodStep3Helper(methodProto, methodName, superClass, builder);
       }
     }
   }
@@ -860,7 +880,7 @@
     }
     // Step 3: Look for maximally-specific superinterface methods or any interface definition.
     //         This is the same for classes and interfaces.
-    return resolveMethodStep3(definition, desc);
+    return resolveMethodStep3(definition, desc.getProto(), desc.getName());
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 0213b0e..0a7c0e5 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -741,15 +741,20 @@
       firstLabel = new CfLabel();
       newInstructions.add(firstLabel);
     }
-    newInstructions.add(new CfPosition(firstLabel, callerPosition));
+    boolean seenPosition = false;
     for (CfInstruction instruction : instructions) {
       if (instruction.isPosition()) {
+        seenPosition = true;
         CfPosition oldPosition = instruction.asPosition();
         newInstructions.add(
             new CfPosition(
                 oldPosition.getLabel(),
                 oldPosition.getPosition().withOutermostCallerPosition(callerPosition)));
       } else {
+        if (!instruction.isLabel() && !seenPosition) {
+          newInstructions.add(new CfPosition(firstLabel, callerPosition));
+          seenPosition = true;
+        }
         newInstructions.add(instruction);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCallSite.java b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
index 9cb4704..3aa03da 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCallSite.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
@@ -102,6 +102,14 @@
     return application.getCallSite(name, desc, bootstrapMethod, bootstrapArgs);
   }
 
+  public List<DexValue> getBootstrapArgs() {
+    return bootstrapArgs;
+  }
+
+  public DexProto getMethodProto() {
+    return methodProto;
+  }
+
   @Override
   public DexCallSite self() {
     return this;
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index e4d00aa..d3a63f6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -588,6 +588,10 @@
     return methodCollection.getMethod(method);
   }
 
+  public DexEncodedMethod lookupMethod(DexProto methodProto, DexString methodName) {
+    return methodCollection.getMethod(methodProto, methodName);
+  }
+
   /** Find method in this class matching {@param method}. */
   public DexEncodedMethod lookupMethod(Predicate<DexEncodedMethod> predicate) {
     return methodCollection.getMethod(predicate);
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 6daff01..2b8a186 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo;
-import static com.android.tools.r8.utils.AndroidApiLevelUtils.MIN_API_LEVEL;
 
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
@@ -17,7 +16,6 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
 import com.android.tools.r8.ir.optimize.info.DefaultFieldOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.DefaultFieldOptimizationWithMinApiInfo;
 import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
@@ -45,7 +43,7 @@
   /** Generic signature information if the attribute is present in the input */
   private FieldTypeSignature genericSignature;
 
-  private FieldOptimizationInfo optimizationInfo;
+  private FieldOptimizationInfo optimizationInfo = DefaultFieldOptimizationInfo.getInstance();
   private KotlinFieldLevelInfo kotlinMemberInfo = getNoKotlinInfo();
 
   private static void specify(StructuralSpecification<DexEncodedField, ?> spec) {
@@ -98,20 +96,11 @@
       boolean deprecated,
       boolean d8R8Synthesized,
       AndroidApiLevel apiLevel) {
-    super(field, annotations, d8R8Synthesized);
+    super(field, annotations, d8R8Synthesized, apiLevel);
     this.accessFlags = accessFlags;
     this.staticValue = staticValue;
     this.deprecated = deprecated;
     this.genericSignature = genericSignature;
-    if (apiLevel == AndroidApiLevel.UNKNOWN) {
-      optimizationInfo = DefaultFieldOptimizationInfo.getInstance();
-    } else if (apiLevel == MIN_API_LEVEL) {
-      optimizationInfo = DefaultFieldOptimizationWithMinApiInfo.getInstance();
-    } else {
-      MutableFieldOptimizationInfo optimizationInfo = new MutableFieldOptimizationInfo();
-      this.optimizationInfo = optimizationInfo;
-      optimizationInfo.setApiReferenceLevelForDefinition(apiLevel);
-    }
     assert genericSignature != null;
     assert GenericSignatureUtils.verifyNoDuplicateGenericDefinitions(genericSignature, annotations);
   }
@@ -148,8 +137,8 @@
   }
 
   @Override
-  public AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApiLevel) {
-    return optimizationInfo.getApiReferenceLevelForDefinition(minApiLevel);
+  public AndroidApiLevel getApiLevel() {
+    return getApiLevelForDefinition();
   }
 
   public synchronized MutableFieldOptimizationInfo getMutableOptimizationInfo() {
@@ -162,10 +151,6 @@
     optimizationInfo = info;
   }
 
-  public void setMinApiOptimizationInfo(DefaultFieldOptimizationWithMinApiInfo info) {
-    optimizationInfo = info;
-  }
-
   @Override
   public KotlinFieldLevelInfo getKotlinInfo() {
     return kotlinMemberInfo;
@@ -400,6 +385,7 @@
     private FieldAccessFlags accessFlags;
     private FieldTypeSignature genericSignature;
     private DexValue staticValue;
+    private AndroidApiLevel apiLevel;
     private FieldOptimizationInfo optimizationInfo;
     private boolean deprecated;
     private boolean d8R8Synthesized;
@@ -413,6 +399,7 @@
       genericSignature = from.getGenericSignature();
       annotations = from.annotations();
       staticValue = from.staticValue;
+      apiLevel = from.getApiLevel();
       optimizationInfo =
           from.optimizationInfo.isMutableOptimizationInfo()
               ? from.optimizationInfo.asMutableFieldOptimizationInfo().mutableCopy()
@@ -468,7 +455,7 @@
               staticValue,
               deprecated,
               d8R8Synthesized,
-              AndroidApiLevel.UNKNOWN);
+              apiLevel);
       dexEncodedField.optimizationInfo = optimizationInfo;
       buildConsumer.accept(dexEncodedField);
       return dexEncodedField;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
index c1ce2f5..da3c5ff 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -20,12 +20,20 @@
   // set.
   private final boolean d8R8Synthesized;
 
+  /** apiLevelForDefinition describes the api level needed for knowing all types */
+  private AndroidApiLevel apiLevelForDefinition;
+
   private final R reference;
 
-  public DexEncodedMember(R reference, DexAnnotationSet annotations, boolean d8R8Synthesized) {
+  public DexEncodedMember(
+      R reference,
+      DexAnnotationSet annotations,
+      boolean d8R8Synthesized,
+      AndroidApiLevel apiLevelForDefinition) {
     super(annotations);
     this.reference = reference;
     this.d8R8Synthesized = d8R8Synthesized;
+    this.apiLevelForDefinition = apiLevelForDefinition;
   }
 
   public abstract KotlinMemberLevelInfo getKotlinInfo();
@@ -85,7 +93,15 @@
 
   public abstract MemberOptimizationInfo<?> getOptimizationInfo();
 
-  public abstract AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApiLevel);
+  public abstract AndroidApiLevel getApiLevel();
+
+  public AndroidApiLevel getApiLevelForDefinition() {
+    return apiLevelForDefinition;
+  }
+
+  public void setApiLevelForDefinition(AndroidApiLevel apiLevelForDefinition) {
+    this.apiLevelForDefinition = apiLevelForDefinition;
+  }
 
   @Override
   public final boolean equals(Object other) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index cb44ff9..c2b204e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -11,7 +11,6 @@
 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo;
-import static com.android.tools.r8.utils.AndroidApiLevelUtils.MIN_API_LEVEL;
 import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
 
 import com.android.tools.r8.cf.CfVersion;
@@ -57,7 +56,6 @@
 import com.android.tools.r8.ir.optimize.NestUtils;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationWithMinApiInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MutableMethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
@@ -158,9 +156,12 @@
   // TODO(b/128967328): towards finer-grained inlining constraints,
   //   we need to maintain a set of states with (potentially different) contexts.
   private CompilationState compilationState = CompilationState.NOT_PROCESSED;
-  private MethodOptimizationInfo optimizationInfo = DefaultMethodOptimizationInfo.DEFAULT_INSTANCE;
+  private MethodOptimizationInfo optimizationInfo = DefaultMethodOptimizationInfo.getInstance();
   private CallSiteOptimizationInfo callSiteOptimizationInfo = CallSiteOptimizationInfo.bottom();
   private CfVersion classFileVersion;
+  /** The apiLevelForCode describes the api level needed for knowing all references in the code */
+  private AndroidApiLevel apiLevelForCode;
+
   private KotlinMethodLevelInfo kotlinMemberInfo = getNoKotlinInfo();
   /** Generic signature information if the attribute is present in the input */
   private MethodTypeSignature genericSignature;
@@ -316,28 +317,18 @@
       AndroidApiLevel apiLevelForDefinition,
       AndroidApiLevel apiLevelForCode,
       boolean deprecated) {
-    super(method, annotations, d8R8Synthesized);
+    super(method, annotations, d8R8Synthesized, apiLevelForDefinition);
     this.accessFlags = accessFlags;
     this.deprecated = deprecated;
     this.genericSignature = genericSignature;
     this.parameterAnnotationsList = parameterAnnotationsList;
     this.code = code;
     this.classFileVersion = classFileVersion;
-    if (apiLevelForDefinition == AndroidApiLevel.UNKNOWN
-        && apiLevelForCode == AndroidApiLevel.UNKNOWN) {
-      optimizationInfo = DefaultMethodOptimizationInfo.getInstance();
-    } else if (apiLevelForDefinition == MIN_API_LEVEL && apiLevelForCode == MIN_API_LEVEL) {
-      optimizationInfo = DefaultMethodOptimizationWithMinApiInfo.getInstance();
-    } else {
-      MutableMethodOptimizationInfo optimizationInfo =
-          DefaultMethodOptimizationInfo.getInstance().toMutableOptimizationInfo();
-      optimizationInfo.setApiReferenceLevelForDefinition(apiLevelForDefinition);
-      optimizationInfo.setApiReferenceLevelForCode(apiLevelForCode);
-      this.optimizationInfo = optimizationInfo;
-    }
+    this.apiLevelForCode = apiLevelForCode;
     assert accessFlags != null;
     assert code == null || !shouldNotHaveCode();
     assert parameterAnnotationsList != null;
+    assert apiLevelForCode != null && apiLevelForDefinition != null;
   }
 
   public static DexEncodedMethod toMethodDefinitionOrNull(DexClassAndMethod method) {
@@ -1117,11 +1108,17 @@
   }
 
   public DexEncodedMethod toMethodThatLogsError(AppView<?> appView) {
-    if (appView.options().isGeneratingDex()) {
-      return toMethodThatLogsErrorDexCode(appView.dexItemFactory());
-    } else {
-      return toMethodThatLogsErrorCfCode(appView.dexItemFactory());
-    }
+    Builder builder =
+        builder(this)
+            .setCode(
+                appView.options().isGeneratingClassFiles()
+                    ? toCfCodeThatLogsError(appView.dexItemFactory())
+                    : toDexCodeThatLogsError(appView.dexItemFactory()))
+            .setIsLibraryMethodOverrideIf(
+                belongsToVirtualPool() && !isLibraryMethodOverride().isUnknown(),
+                isLibraryMethodOverride());
+    setObsolete();
+    return builder.build();
   }
 
   public static void setDebugInfoWithFakeThisParameter(Code code, int arity, AppView<?> appView) {
@@ -1136,8 +1133,8 @@
       cfCode.addFakeThisParameter(appView.dexItemFactory());
     }
   }
-  
-  private DexEncodedMethod toMethodThatLogsErrorDexCode(DexItemFactory itemFactory) {
+
+  private DexCode toDexCodeThatLogsError(DexItemFactory itemFactory) {
     checkIfObsolete();
     Signature signature = MethodSignature.fromDexMethod(getReference());
     DexString message =
@@ -1158,23 +1155,18 @@
             exceptionType,
             itemFactory.createProto(itemFactory.voidType, itemFactory.stringType),
             itemFactory.constructorMethodName);
-    DexCode code =
-        generateCodeFromTemplate(
-            2,
-            2,
-            new ConstString(0, tag),
-            new ConstString(1, message),
-            new InvokeStatic(2, logMethod, 0, 1, 0, 0, 0),
-            new NewInstance(0, exceptionType),
-            new InvokeDirect(2, exceptionInitMethod, 0, 1, 0, 0, 0),
-            new Throw(0));
-    Builder builder = builder(this);
-    builder.setCode(code);
-    setObsolete();
-    return builder.build();
+    return generateCodeFromTemplate(
+        2,
+        2,
+        new ConstString(0, tag),
+        new ConstString(1, message),
+        new InvokeStatic(2, logMethod, 0, 1, 0, 0, 0),
+        new NewInstance(0, exceptionType),
+        new InvokeDirect(2, exceptionInitMethod, 0, 1, 0, 0, 0),
+        new Throw(0));
   }
 
-  private DexEncodedMethod toMethodThatLogsErrorCfCode(DexItemFactory itemFactory) {
+  private CfCode toCfCodeThatLogsError(DexItemFactory itemFactory) {
     checkIfObsolete();
     Signature signature = MethodSignature.fromDexMethod(getReference());
     DexString message =
@@ -1219,18 +1211,13 @@
         .add(new CfConstString(message))
         .add(new CfInvoke(Opcodes.INVOKESPECIAL, exceptionInitMethod, false))
         .add(new CfThrow());
-    CfCode code =
-        new CfCode(
-            getReference().holder,
-            3,
-            locals,
-            instructionBuilder.build(),
-            Collections.emptyList(),
-            Collections.emptyList());
-    Builder builder = builder(this);
-    builder.setCode(code);
-    setObsolete();
-    return builder.build();
+    return new CfCode(
+        getReference().holder,
+        3,
+        locals,
+        instructionBuilder.build(),
+        Collections.emptyList(),
+        Collections.emptyList());
   }
 
   public DexEncodedMethod toTypeSubstitutedMethod(DexMethod method) {
@@ -1328,7 +1315,10 @@
                                                 .getHolderType()
                                                 .isInterface(definitions)))
                             .build())
-                    .modifyAccessFlags(MethodAccessFlags::setBridge))
+                    .modifyAccessFlags(MethodAccessFlags::setBridge)
+                    .setIsLibraryMethodOverrideIf(
+                        !isStatic() && !isLibraryMethodOverride().isUnknown(),
+                        isLibraryMethodOverride()))
         .build();
   }
 
@@ -1431,18 +1421,19 @@
     return optimizationInfo;
   }
 
-  public AndroidApiLevel getApiReferenceLevelForDefinition(AndroidApiLevel minApiLevel) {
-    return optimizationInfo.getApiReferenceLevelForDefinition(minApiLevel);
+  public AndroidApiLevel getApiLevelForCode() {
+    return apiLevelForCode;
   }
 
-  public AndroidApiLevel getApiReferenceLevelForCode(AndroidApiLevel minApiLevel) {
-    return optimizationInfo.getApiReferenceLevelForCode(minApiLevel);
+  public void setApiLevelForCode(AndroidApiLevel apiLevel) {
+    assert apiLevel != null;
+    this.apiLevelForCode = apiLevel;
   }
 
   @Override
-  public AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApiLevel) {
-    return getApiReferenceLevelForDefinition(minApiLevel)
-        .max(getApiReferenceLevelForCode(minApiLevel));
+  public AndroidApiLevel getApiLevel() {
+    return (isAbstract() ? AndroidApiLevel.B : getApiLevelForCode())
+        .max(getApiLevelForDefinition());
   }
 
   public synchronized MutableMethodOptimizationInfo getMutableOptimizationInfo() {
@@ -1457,11 +1448,6 @@
     optimizationInfo = info;
   }
 
-  public void setMinApiOptimizationInfo(DefaultMethodOptimizationWithMinApiInfo info) {
-    checkIfObsolete();
-    optimizationInfo = info;
-  }
-
   public synchronized void abandonCallSiteOptimizationInfo() {
     checkIfObsolete();
     callSiteOptimizationInfo = CallSiteOptimizationInfo.abandoned();
@@ -1525,6 +1511,8 @@
     private MethodOptimizationInfo optimizationInfo = DefaultMethodOptimizationInfo.getInstance();
     private KotlinMethodLevelInfo kotlinInfo = getNoKotlinInfo();
     private CfVersion classFileVersion = null;
+    private AndroidApiLevel apiLevelForDefinition = null;
+    private AndroidApiLevel apiLevelForCode = null;
     private boolean d8R8Synthesized = false;
 
     private Consumer<DexEncodedMethod> buildConsumer = ConsumerUtils.emptyConsumer();
@@ -1542,6 +1530,8 @@
       genericSignature = from.getGenericSignature();
       annotations = from.annotations();
       code = from.getCode();
+      apiLevelForDefinition = from.getApiLevelForDefinition();
+      apiLevelForCode = from.getApiLevelForCode();
       optimizationInfo =
           from.getOptimizationInfo().isMutableOptimizationInfo()
               ? from.getOptimizationInfo().asMutableMethodOptimizationInfo().mutableCopy()
@@ -1732,6 +1722,8 @@
       assert parameterAnnotations != null;
       assert parameterAnnotations.isEmpty()
           || parameterAnnotations.size() == method.proto.parameters.size();
+      assert apiLevelForDefinition != null;
+      assert apiLevelForCode != null;
       DexEncodedMethod result =
           new DexEncodedMethod(
               method,
@@ -1742,8 +1734,8 @@
               code,
               d8R8Synthesized,
               classFileVersion,
-              AndroidApiLevel.UNKNOWN,
-              AndroidApiLevel.UNKNOWN);
+              apiLevelForDefinition,
+              apiLevelForCode);
       result.setKotlinMemberInfo(kotlinInfo);
       result.compilationState = compilationState;
       result.optimizationInfo = optimizationInfo;
@@ -1758,5 +1750,15 @@
       this.genericSignature = methodSignature;
       return this;
     }
+
+    public Builder setApiLevelForDefinition(AndroidApiLevel apiLevelForDefinition) {
+      this.apiLevelForDefinition = apiLevelForDefinition;
+      return this;
+    }
+
+    public Builder setApiLevelForCode(AndroidApiLevel apiLevelForCode) {
+      this.apiLevelForCode = apiLevelForCode;
+      return this;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 5a109e7..c5cb0ff 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -228,7 +228,15 @@
 
   @Override
   public boolean match(DexMethod method) {
-    return method.name == name && method.proto == proto;
+    return match(method.getProto(), method.getName());
+  }
+
+  public boolean match(DexMethodSignature method) {
+    return match(method.getProto(), method.getName());
+  }
+
+  public boolean match(DexProto methodProto, DexString methodName) {
+    return proto == methodProto && name == methodName;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodSignature.java b/src/main/java/com/android/tools/r8/graph/DexMethodSignature.java
index 8b2fcc4..74d464f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethodSignature.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethodSignature.java
@@ -4,9 +4,13 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.utils.structural.StructuralItem;
+import com.android.tools.r8.utils.structural.StructuralMapping;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
 import java.util.Objects;
 
-public class DexMethodSignature {
+public class DexMethodSignature implements StructuralItem<DexMethodSignature> {
+
   private final DexProto proto;
   private final DexString name;
 
@@ -21,14 +25,31 @@
     this.name = name;
   }
 
-  public DexProto getProto() {
-    return proto;
+  public int getArity() {
+    return proto.getArity();
   }
 
   public DexString getName() {
     return name;
   }
 
+  public DexProto getProto() {
+    return proto;
+  }
+
+  public DexType getReturnType() {
+    return proto.returnType;
+  }
+
+  @Override
+  public StructuralMapping<DexMethodSignature> getStructuralMapping() {
+    return DexMethodSignature::specify;
+  }
+
+  private static void specify(StructuralSpecification<DexMethodSignature, ?> spec) {
+    spec.withItem(DexMethodSignature::getName).withItem(DexMethodSignature::getProto);
+  }
+
   public DexMethodSignature withName(DexString name) {
     return new DexMethodSignature(proto, name);
   }
@@ -58,12 +79,9 @@
     return Objects.hash(proto, name);
   }
 
-  public DexType getReturnType() {
-    return proto.returnType;
-  }
-
-  public int getArity() {
-    return proto.getArity();
+  @Override
+  public DexMethodSignature self() {
+    return this;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 8a3f39a..e297bd4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.graph;
 
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo;
-import static com.android.tools.r8.utils.AndroidApiLevelUtils.getApiLevelIfEnabled;
+import static com.android.tools.r8.utils.AndroidApiLevel.minApiLevelIfEnabledOrUnknown;
 import static com.google.common.base.Predicates.alwaysTrue;
 
 import com.android.tools.r8.ProgramResource;
@@ -827,7 +827,7 @@
       AppView<?> appView,
       BiFunction<DexReference, AndroidApiLevel, AndroidApiLevel> apiLevelLookup) {
     // The api level of a class is the max level of it's members, super class and interfaces.
-    AndroidApiLevel computedApiLevel = getApiLevelIfEnabled(appView, Function.identity());
+    AndroidApiLevel computedApiLevel = minApiLevelIfEnabledOrUnknown(appView);
     for (DexType superType : allImmediateSupertypes()) {
       computedApiLevel = apiLevelLookup.apply(superType, computedApiLevel);
       if (computedApiLevel == AndroidApiLevel.UNKNOWN) {
@@ -838,9 +838,9 @@
   }
 
   public AndroidApiLevel getMembersApiReferenceLevel(AppView<?> appView) {
-    AndroidApiLevel memberLevel = getApiLevelIfEnabled(appView, Function.identity());
+    AndroidApiLevel memberLevel = minApiLevelIfEnabledOrUnknown(appView);
     for (DexEncodedMember<?, ?> member : members()) {
-      memberLevel = memberLevel.max(getApiLevelIfEnabled(appView, member::getApiReferenceLevel));
+      memberLevel = memberLevel.max(member.getApiLevel());
       if (memberLevel == AndroidApiLevel.UNKNOWN) {
         return AndroidApiLevel.UNKNOWN;
       }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
index 62b62b9..03627d7 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
@@ -66,6 +66,10 @@
     return false;
   }
 
+  public boolean isFailedResolution() {
+    return false;
+  }
+
   public DexClass getInitialResolutionHolder() {
     return null;
   }
@@ -163,6 +167,11 @@
     public boolean isFailedOrUnknownResolution() {
       return true;
     }
+
+    @Override
+    public boolean isFailedResolution() {
+      return true;
+    }
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index 8da7519..37dea58 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -681,7 +681,11 @@
           continue;
         }
         DexMethod originalMethod = getOriginalMethodSignature(method.getReference());
-        assert originalMethods.contains(originalMethod);
+        assert originalMethods.contains(originalMethod)
+            : "Method could not be mapped back: "
+                + method.toSourceString()
+                + ", originalMethod: "
+                + originalMethod.toSourceString();
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java b/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
index ac08e3f..fa003ea 100644
--- a/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
@@ -63,6 +63,24 @@
         });
   }
 
+  public void forEachImmediateProgramSuperClass(
+      DexProgramClass clazz, Consumer<? super DexProgramClass> consumer) {
+    forEachImmediateProgramSuperClassMatching(clazz, alwaysTrue(), consumer);
+  }
+
+  public void forEachImmediateProgramSuperClassMatching(
+      DexProgramClass clazz,
+      Predicate<? super DexProgramClass> predicate,
+      Consumer<? super DexProgramClass> consumer) {
+    clazz.forEachImmediateSupertype(
+        supertype -> {
+          DexProgramClass superclass = asProgramClassOrNull(appView.definitionFor(supertype));
+          if (superclass != null && predicate.test(superclass)) {
+            consumer.accept(superclass);
+          }
+        });
+  }
+
   public void forEachImmediateSubClass(
       DexProgramClass clazz, Consumer<? super DexProgramClass> consumer) {
     forEachImmediateSubClassMatching(clazz, alwaysTrue(), consumer);
diff --git a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
index 7e67c3b..6930912 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
@@ -249,9 +249,21 @@
   }
 
   @Override
-  DexEncodedMethod getMethod(DexMethod method) {
-    DexEncodedMethod result = getDirectMethod(method);
-    return result == null ? getVirtualMethod(method) : result;
+  DexEncodedMethod getMethod(DexProto methodProto, DexString methodName) {
+    DexEncodedMethod directMethod = internalGetMethod(methodProto, methodName, directMethods);
+    return directMethod == null
+        ? internalGetMethod(methodProto, methodName, virtualMethods)
+        : directMethod;
+  }
+
+  private static DexEncodedMethod internalGetMethod(
+      DexProto methodProto, DexString methodName, DexEncodedMethod[] methods) {
+    for (DexEncodedMethod method : methods) {
+      if (method.getReference().match(methodProto, methodName)) {
+        return method;
+      }
+    }
+    return null;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index 3509b8c..3dc2b58 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -160,7 +160,11 @@
   }
 
   public DexEncodedMethod getMethod(DexMethod method) {
-    return backing.getMethod(method);
+    return backing.getMethod(method.getProto(), method.getName());
+  }
+
+  public DexEncodedMethod getMethod(DexProto methodProto, DexString methodName) {
+    return backing.getMethod(methodProto, methodName);
   }
 
   public DexEncodedMethod getMethod(Predicate<DexEncodedMethod> predicate) {
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
index 301352a..90fbf1f 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
@@ -68,7 +68,7 @@
 
   // Lookup methods.
 
-  abstract DexEncodedMethod getMethod(DexMethod method);
+  abstract DexEncodedMethod getMethod(DexProto methodProto, DexString methodName);
 
   abstract DexEncodedMethod getDirectMethod(DexMethod method);
 
diff --git a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
index a7d3c45..8f705d4 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
@@ -5,67 +5,59 @@
 
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.IteratorUtils;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.TraversalContinuation;
-import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.Lists;
-import it.unimi.dsi.fastutil.objects.Object2ReferenceLinkedOpenHashMap;
-import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
-import it.unimi.dsi.fastutil.objects.Object2ReferenceRBTreeMap;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.function.Function;
 import java.util.function.Predicate;
 
 public class MethodMapBacking extends MethodCollectionBacking {
 
-  private Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> methodMap;
+  private Map<DexMethodSignature, DexEncodedMethod> methodMap;
 
   public MethodMapBacking() {
     this(createMap());
   }
 
-  private MethodMapBacking(Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> methodMap) {
+  private MethodMapBacking(Map<DexMethodSignature, DexEncodedMethod> methodMap) {
     this.methodMap = methodMap;
   }
 
   public static MethodMapBacking createSorted() {
-    Comparator<Wrapper<DexMethod>> comparator = Comparator.comparing(Wrapper::get);
-    return new MethodMapBacking(new Object2ReferenceRBTreeMap<>(comparator));
+    return new MethodMapBacking(new TreeMap<>());
   }
 
-  private static Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> createMap() {
+  private static Map<DexMethodSignature, DexEncodedMethod> createMap() {
     // Maintain a linked map so the output order remains a deterministic function of the input.
-    return new Object2ReferenceLinkedOpenHashMap<>();
+    return new HashMap<>();
   }
 
-  private static Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> createMap(int capacity) {
+  private static Map<DexMethodSignature, DexEncodedMethod> createMap(int capacity) {
     // Maintain a linked map so the output order remains a deterministic function of the input.
-    return new Object2ReferenceLinkedOpenHashMap<>(capacity);
+    return new HashMap<>(capacity);
   }
 
-  private Wrapper<DexMethod> wrap(DexMethod method) {
-    return MethodSignatureEquivalence.get().wrap(method);
-  }
-
-  private void replace(Wrapper<DexMethod> existingKey, DexEncodedMethod method) {
-    if (existingKey.get().match(method)) {
+  private void replace(DexMethodSignature existingKey, DexEncodedMethod method) {
+    if (method.getReference().match(existingKey)) {
       methodMap.put(existingKey, method);
     } else {
       methodMap.remove(existingKey);
-      methodMap.put(wrap(method.getReference()), method);
+      methodMap.put(method.getSignature(), method);
     }
   }
 
   @Override
   boolean verify() {
     methodMap.forEach(
-        (key, method) -> {
-          assert key.get().match(method);
+        (signature, method) -> {
+          assert method.getReference().match(signature);
         });
     return true;
   }
@@ -97,7 +89,7 @@
 
   @Override
   TraversalContinuation traverse(Function<DexEncodedMethod, TraversalContinuation> fn) {
-    for (Entry<Wrapper<DexMethod>, DexEncodedMethod> entry : methodMap.object2ReferenceEntrySet()) {
+    for (Entry<DexMethodSignature, DexEncodedMethod> entry : methodMap.entrySet()) {
       TraversalContinuation result = fn.apply(entry.getValue());
       if (result.shouldBreak()) {
         return result;
@@ -122,8 +114,8 @@
   }
 
   @Override
-  DexEncodedMethod getMethod(DexMethod method) {
-    return methodMap.get(wrap(method));
+  DexEncodedMethod getMethod(DexProto methodProto, DexString methodName) {
+    return methodMap.get(new DexMethodSignature(methodProto, methodName));
   }
 
   private DexEncodedMethod getMethod(Predicate<DexEncodedMethod> predicate) {
@@ -141,7 +133,7 @@
 
   @Override
   DexEncodedMethod getDirectMethod(DexMethod method) {
-    DexEncodedMethod definition = getMethod(method);
+    DexEncodedMethod definition = getMethod(method.getProto(), method.getName());
     return definition != null && belongsToDirectPool(definition) ? definition : null;
   }
 
@@ -153,7 +145,7 @@
 
   @Override
   DexEncodedMethod getVirtualMethod(DexMethod method) {
-    DexEncodedMethod definition = getMethod(method);
+    DexEncodedMethod definition = getMethod(method.getProto(), method.getName());
     return definition != null && belongsToVirtualPool(definition) ? definition : null;
   }
 
@@ -165,7 +157,7 @@
 
   @Override
   void addMethod(DexEncodedMethod method) {
-    Wrapper<DexMethod> key = wrap(method.getReference());
+    DexMethodSignature key = method.getSignature();
     DexEncodedMethod old = methodMap.put(key, method);
     assert old == null;
   }
@@ -208,12 +200,12 @@
 
   @Override
   DexEncodedMethod removeMethod(DexMethod method) {
-    return methodMap.remove(wrap(method));
+    return methodMap.remove(method.getSignature());
   }
 
   @Override
   void removeMethods(Set<DexEncodedMethod> methods) {
-    methods.forEach(method -> methodMap.remove(wrap(method.getReference())));
+    methods.forEach(method -> methodMap.remove(method.getSignature()));
   }
 
   @Override
@@ -224,17 +216,16 @@
     if (methods == null) {
       methods = DexEncodedMethod.EMPTY_ARRAY;
     }
-    Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> newMap =
-        createMap(size() + methods.length);
+    Map<DexMethodSignature, DexEncodedMethod> newMap = createMap(size() + methods.length);
     forEachMethod(
         method -> {
           if (belongsToVirtualPool(method)) {
-            newMap.put(wrap(method.getReference()), method);
+            newMap.put(method.getSignature(), method);
           }
         });
     for (DexEncodedMethod method : methods) {
       assert belongsToDirectPool(method);
-      newMap.put(wrap(method.getReference()), method);
+      newMap.put(method.getSignature(), method);
     }
     methodMap = newMap;
   }
@@ -247,17 +238,16 @@
     if (methods == null) {
       methods = DexEncodedMethod.EMPTY_ARRAY;
     }
-    Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> newMap =
-        createMap(size() + methods.length);
+    Map<DexMethodSignature, DexEncodedMethod> newMap = createMap(size() + methods.length);
     forEachMethod(
         method -> {
           if (belongsToDirectPool(method)) {
-            newMap.put(wrap(method.getReference()), method);
+            newMap.put(method.getSignature(), method);
           }
         });
     for (DexEncodedMethod method : methods) {
       assert belongsToVirtualPool(method);
-      newMap.put(wrap(method.getReference()), method);
+      newMap.put(method.getSignature(), method);
     }
     methodMap = newMap;
   }
@@ -325,7 +315,7 @@
       DexMethod method,
       Function<DexEncodedMethod, DexEncodedMethod> replacement,
       Predicate<DexEncodedMethod> predicate) {
-    Wrapper<DexMethod> key = wrap(method);
+    DexMethodSignature key = method.getSignature();
     DexEncodedMethod existing = methodMap.get(key);
     if (existing == null || !predicate.test(existing)) {
       return null;
@@ -339,7 +329,7 @@
   @Override
   DexEncodedMethod replaceDirectMethodWithVirtualMethod(
       DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
-    Wrapper<DexMethod> key = wrap(method);
+    DexMethodSignature key = method.getSignature();
     DexEncodedMethod existing = methodMap.get(key);
     if (existing == null || belongsToVirtualPool(existing)) {
       return null;
@@ -359,7 +349,7 @@
   private boolean verifyVirtualizedMethods(Set<DexEncodedMethod> methods) {
     for (DexEncodedMethod method : methods) {
       assert belongsToVirtualPool(method);
-      assert methodMap.get(wrap(method.getReference())) == method;
+      assert methodMap.get(method.getSignature()) == method;
     }
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
index aadd446..f1e8ae1 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -80,6 +80,10 @@
     return false;
   }
 
+  public boolean isClassNotFoundResult() {
+    return false;
+  }
+
   /** Returns non-null if isFailedResolution() is true, otherwise null. */
   public FailedResolutionResult asFailedResolution() {
     return null;
@@ -94,6 +98,10 @@
     return null;
   }
 
+  public ProgramMethod getResolvedProgramMethod() {
+    return null;
+  }
+
   @Override
   public DexClassAndMethod getResolutionPair() {
     return null;
@@ -188,6 +196,7 @@
       return resolvedMethod;
     }
 
+    @Override
     public ProgramMethod getResolvedProgramMethod() {
       return resolvedHolder.isProgramClass()
           ? new ProgramMethod(resolvedHolder.asProgramClass(), resolvedMethod)
@@ -846,6 +855,11 @@
     private ClassNotFoundResult() {
       // Intentionally left empty.
     }
+
+    @Override
+    public boolean isClassNotFoundResult() {
+      return true;
+    }
   }
 
   public abstract static class FailedResolutionWithCausingMethods extends FailedResolutionResult {
diff --git a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
index d09c3f5..f3670f3 100644
--- a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
+++ b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
@@ -208,7 +208,10 @@
       // inner class attributes.
       boolean innerClassAttributeChanged =
           newInnerClassType != innerClassType || newOuterClassType != outerClassType;
-      if (innerClassAttributeChanged && innerClassType != null && outerClassType != null) {
+      if (innerClassAttributeChanged
+          && innerClassType != null
+          && outerClassType != null
+          && innerClassAttribute.getInnerName() != null) {
         String innerClassName =
             DescriptorUtils.getInnerClassName(
                 newOuterClassType.toDescriptorString(), newInnerClassType.toDescriptorString());
@@ -217,10 +220,8 @@
         } else {
           // If run without treeshaking and the outer type is missing we are not pruning the
           // relationship.
-          // TODO(b/196503304): Enable the below asserts when resolved.
-          assert !appView.options().isTreeShakingEnabled() || true;
-          // assert appView.appInfo().definitionForWithoutExistenceAssert(newOuterClassType) ==
-          // null;
+          assert !appView.options().isTreeShakingEnabled();
+          assert appView.appInfo().definitionForWithoutExistenceAssert(newOuterClassType) == null;
         }
       }
       newInnerClassAttributes.add(
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java
index b4615df..9da5368 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java
@@ -6,18 +6,11 @@
 
 import com.android.tools.r8.androidapi.AndroidApiReferenceLevelCache;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMember;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexMember;
+import com.android.tools.r8.graph.DexClassAndMember;
 import com.android.tools.r8.graph.LookupTarget;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.optimize.info.DefaultFieldOptimizationWithMinApiInfo;
-import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationWithMinApiInfo;
-import com.android.tools.r8.ir.optimize.info.MemberOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.shaking.DefaultEnqueuerUseRegistry;
 import com.android.tools.r8.utils.AndroidApiLevel;
 
@@ -35,14 +28,12 @@
 
   @Override
   public void processNewlyLiveField(ProgramField field, ProgramDefinition context) {
-    setApiLevelForMemberDefinition(
-        field.getDefinition(), computeApiLevelForReferencedTypes(field.getReference()));
+    computeApiLevelForDefinition(field);
   }
 
   @Override
   public void processNewlyLiveMethod(ProgramMethod method, ProgramDefinition context) {
-    setApiLevelForMemberDefinition(
-        method.getDefinition(), computeApiLevelForReferencedTypes(method.getReference()));
+    computeApiLevelForDefinition(method);
   }
 
   @Override
@@ -55,71 +46,35 @@
           .tracedMethodApiLevelCallback
           .accept(method.getMethodReference(), registry.getMaxApiReferenceLevel());
     }
-    setApiLevelForMemberDefinition(
-        method.getDefinition(), computeApiLevelForReferencedTypes(method.getReference()));
-    setApiLevelForCode(method.getDefinition(), registry.getMaxApiReferenceLevel());
+    computeApiLevelForDefinition(method);
+    method.getDefinition().setApiLevelForCode(registry.getMaxApiReferenceLevel());
   }
 
   @Override
   public void notifyMarkMethodAsTargeted(ProgramMethod method) {
-    setApiLevelForMemberDefinition(method.getDefinition(), minApiLevel);
+    computeApiLevelForDefinition(method);
   }
 
   @Override
   public void notifyMarkFieldAsReachable(ProgramField field) {
-    setApiLevelForMemberDefinition(field.getDefinition(), minApiLevel);
+    computeApiLevelForDefinition(field);
   }
 
   @Override
   public void notifyMarkVirtualDispatchTargetAsLive(LookupTarget target) {
     target.accept(
-        dexClassAndMethod -> {
-          setApiLevelForMemberDefinition(dexClassAndMethod.getDefinition(), minApiLevel);
-        },
+        this::computeApiLevelForDefinition,
         lookupLambdaTarget -> {
           // The implementation method will be assigned an api level when visited.
         });
   }
 
-  private void setApiLevelForMemberDefinition(
-      DexEncodedMember<?, ?> member, AndroidApiLevel apiLevel) {
-    // To not have mutable update information for all members that all has min api level we
-    // swap the default optimization info for one with that marks the api level to be min api.
-    MemberOptimizationInfo<?> optimizationInfo = member.getOptimizationInfo();
-    if (!optimizationInfo.isMutableOptimizationInfo() && apiLevel == minApiLevel) {
-      member.accept(
-          field -> {
-            field.setMinApiOptimizationInfo(DefaultFieldOptimizationWithMinApiInfo.getInstance());
-          },
-          method -> {
-            method.setMinApiOptimizationInfo(DefaultMethodOptimizationWithMinApiInfo.getInstance());
-          });
-    } else {
-      AndroidApiLevel maxApiLevel =
-          optimizationInfo.hasApiReferenceLevelForDefinition()
-              ? apiLevel.max(optimizationInfo.getApiReferenceLevelForDefinition(minApiLevel))
-              : apiLevel;
-      member.accept(
-          field -> {
-            field.getMutableOptimizationInfo().setApiReferenceLevelForDefinition(maxApiLevel);
-          },
-          method -> {
-            method.getMutableOptimizationInfo().setApiReferenceLevelForDefinition(maxApiLevel);
-          });
-    }
-  }
-
-  private void setApiLevelForCode(DexEncodedMethod method, AndroidApiLevel apiLevel) {
-    MethodOptimizationInfo optimizationInfo = method.getOptimizationInfo();
-    assert optimizationInfo != DefaultMethodOptimizationInfo.getInstance();
-    if (!optimizationInfo.isMutableOptimizationInfo() && apiLevel == minApiLevel) {
-      assert optimizationInfo == DefaultMethodOptimizationWithMinApiInfo.getInstance();
-      return;
-    }
-    method.getMutableOptimizationInfo().setApiReferenceLevelForCode(apiLevel);
-  }
-
-  private AndroidApiLevel computeApiLevelForReferencedTypes(DexMember<?, ?> member) {
-    return member.computeApiLevelForReferencedTypes(appView, referenceLevelCache::lookupMax);
+  private void computeApiLevelForDefinition(DexClassAndMember<?, ?> member) {
+    member
+        .getDefinition()
+        .setApiLevelForDefinition(
+            member
+                .getReference()
+                .computeApiLevelForReferencedTypes(appView, referenceLevelCache::lookupMax));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index a772278..43a2c8a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
-import static com.android.tools.r8.utils.AndroidApiLevelUtils.getApiLevelIfEnabledForNewMember;
+import static com.android.tools.r8.utils.AndroidApiLevel.minApiLevelIfEnabledOrUnknown;
 import static com.google.common.base.Predicates.not;
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -45,7 +45,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Function;
 
 /**
  * The class merger is responsible for moving methods from the sources in {@link ClassMerger#group}
@@ -141,8 +140,8 @@
             classInitializerMerger.getCode(syntheticMethodReference),
             DexEncodedMethod.D8_R8_SYNTHESIZED,
             classInitializerMerger.getCfVersion(),
-            getApiLevelIfEnabledForNewMember(appView, ignored -> apiReferenceLevel),
-            getApiLevelIfEnabledForNewMember(appView, ignored -> apiReferenceLevel));
+            apiReferenceLevel,
+            apiReferenceLevel);
     classMethodsBuilder.addDirectMethod(definition);
 
     // In case we didn't synthesize CF code, we register the class initializer for conversion to dex
@@ -230,7 +229,7 @@
             null,
             deprecated,
             d8R8Synthesized,
-            getApiLevelIfEnabledForNewMember(appView, Function.identity()));
+            minApiLevelIfEnabledOrUnknown(appView));
 
     // For the $r8$classId synthesized fields, we try to over-approximate the set of values it may
     // have. For example, for a merge group of size 4, we may compute the set {0, 2, 3}, if the
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 adc89fc..d6ae971 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.horizontalclassmerging;
 
 import static com.android.tools.r8.dex.Constants.TEMPORARY_INSTANCE_INITIALIZER_PREFIX;
-import static com.android.tools.r8.utils.AndroidApiLevelUtils.getApiLevelIfEnabledForNewMember;
 
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.dex.Constants;
@@ -372,10 +371,8 @@
             getNewCode(newMethodReference, syntheticMethodReference, needsClassId, extraNulls),
             true,
             getNewClassFileVersion(),
-            getApiLevelIfEnabledForNewMember(
-                appView, representativeMethod::getApiReferenceLevelForDefinition),
-            getApiLevelIfEnabledForNewMember(
-                appView, representativeMethod::getApiReferenceLevelForCode));
+            representativeMethod.getApiLevelForDefinition(),
+            representativeMethod.getApiLevelForCode());
     classMethodsBuilder.addDirectMethod(newInstanceInitializer);
 
     if (mode.isFinal()) {
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 87a3211..be6a5a2 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
-import static com.android.tools.r8.utils.AndroidApiLevelUtils.getApiLevelIfEnabledForNewMember;
-
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
@@ -289,10 +287,8 @@
             synthesizedCode,
             true,
             classFileVersion,
-            getApiLevelIfEnabledForNewMember(
-                appView, representativeMethod::getApiReferenceLevelForDefinition),
-            getApiLevelIfEnabledForNewMember(
-                appView, representativeMethod::getApiReferenceLevelForCode));
+            representativeMethod.getApiLevelForDefinition(),
+            representativeMethod.getApiLevelForCode());
     if (!representative.getDefinition().isLibraryMethodOverride().isUnknown()) {
       newMethod.setLibraryMethodOverride(representative.getDefinition().isLibraryMethodOverride());
     }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
index cff4480..3251db1 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.horizontalclassmerging.code;
 
-import static com.android.tools.r8.utils.AndroidApiLevelUtils.getApiLevelIfEnabled;
+import static com.android.tools.r8.utils.AndroidApiLevel.minApiLevelIfEnabledOrUnknown;
 import static java.lang.Integer.max;
 
 import com.android.tools.r8.cf.CfVersion;
@@ -104,12 +104,8 @@
     assert !classInitializers.isEmpty();
     return ListUtils.fold(
         classInitializers,
-        appView.options().apiModelingOptions().enableApiCallerIdentification
-            ? appView.options().minApiLevel
-            : AndroidApiLevel.UNKNOWN,
-        (accApiLevel, method) ->
-            accApiLevel.max(
-                getApiLevelIfEnabled(appView, method.getDefinition()::getApiReferenceLevel)));
+        minApiLevelIfEnabledOrUnknown(appView),
+        (accApiLevel, method) -> accApiLevel.max(method.getDefinition().getApiLevel()));
   }
 
   public static class Builder {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
index 083c27f..9e83183 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
@@ -140,6 +140,16 @@
   }
 
   @Override
+  public ClassTypeElement joinNullability(Nullability nullability) {
+    return getOrCreateVariant(nullability().join(nullability));
+  }
+
+  @Override
+  public ClassTypeElement meetNullability(Nullability nullability) {
+    return getOrCreateVariant(nullability().meet(nullability));
+  }
+
+  @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
     builder.append(nullability);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java
index 12d1c4c..5db6f54 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Objects;
 
 /**
  * Represents the runtime type of a reference value. This type may be more precise than the value's
@@ -27,18 +28,40 @@
     this.dynamicUpperBoundType = dynamicUpperBoundType;
   }
 
-  public static DynamicType create(Value value, AppView<AppInfoWithLiveness> appView) {
-    assert value.getType().isReferenceType();
-    TypeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
-    ClassTypeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
+  public static DynamicType create(
+      AppView<AppInfoWithLiveness> appView,
+      TypeElement dynamicUpperBoundType,
+      ClassTypeElement dynamicLowerBoundType) {
+    if (dynamicUpperBoundType.isBottom()) {
+      return bottom();
+    }
+    if (dynamicUpperBoundType.isTop()) {
+      return unknown();
+    }
     if (dynamicLowerBoundType != null) {
       assert dynamicUpperBoundType.isClassType();
+      assert dynamicUpperBoundType.nullability() == dynamicLowerBoundType.nullability();
+      if (dynamicUpperBoundType.equals(dynamicLowerBoundType)) {
+        return createExact(dynamicLowerBoundType);
+      }
       return DynamicTypeWithLowerBound.create(
           appView, dynamicUpperBoundType.asClassType(), dynamicLowerBoundType);
     }
     return new DynamicType(dynamicUpperBoundType);
   }
 
+  public static DynamicType createExact(ClassTypeElement exactDynamicType) {
+    return new ExactDynamicType(exactDynamicType);
+  }
+
+  public static DynamicType create(Value value, AppView<AppInfoWithLiveness> appView) {
+    assert value.getType().isReferenceType();
+    TypeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
+    ClassTypeElement dynamicLowerBoundType =
+        value.getDynamicLowerBoundType(appView, dynamicUpperBoundType.nullability());
+    return create(appView, dynamicUpperBoundType, dynamicLowerBoundType);
+  }
+
   public static DynamicType bottom() {
     return BOTTOM;
   }
@@ -59,21 +82,61 @@
     return null;
   }
 
+  public boolean isBottom() {
+    return getDynamicUpperBoundType().isBottom();
+  }
+
   public boolean isTrivial(TypeElement staticType) {
-    return staticType == getDynamicUpperBoundType() || isUnknown();
+    return staticType.equals(getDynamicUpperBoundType()) || isUnknown();
   }
 
   public boolean isUnknown() {
     return getDynamicUpperBoundType().isTop();
   }
 
+  public DynamicType join(AppView<AppInfoWithLiveness> appView, DynamicType dynamicType) {
+    if (isBottom()) {
+      return dynamicType;
+    }
+    if (dynamicType.isBottom() || equals(dynamicType)) {
+      return this;
+    }
+    if (isUnknown() || dynamicType.isUnknown()) {
+      return unknown();
+    }
+    TypeElement upperBoundType =
+        getDynamicUpperBoundType().join(dynamicType.getDynamicUpperBoundType(), appView);
+    ClassTypeElement lowerBoundType = meetDynamicLowerBound(appView, dynamicType);
+    if (upperBoundType.equals(getDynamicUpperBoundType())
+        && Objects.equals(lowerBoundType, getDynamicLowerBoundType())) {
+      return this;
+    }
+    return create(appView, upperBoundType, lowerBoundType);
+  }
+
+  private ClassTypeElement meetDynamicLowerBound(
+      AppView<AppInfoWithLiveness> appView, DynamicType dynamicType) {
+    if (!hasDynamicLowerBoundType() || !dynamicType.hasDynamicLowerBoundType()) {
+      return null;
+    }
+    ClassTypeElement lowerBoundType = getDynamicLowerBoundType();
+    ClassTypeElement otherLowerBoundType = dynamicType.getDynamicLowerBoundType();
+    if (lowerBoundType.lessThanOrEqualUpToNullability(otherLowerBoundType, appView)) {
+      return lowerBoundType.joinNullability(otherLowerBoundType.nullability());
+    }
+    if (otherLowerBoundType.lessThanOrEqualUpToNullability(lowerBoundType, appView)) {
+      return otherLowerBoundType.joinNullability(lowerBoundType.nullability());
+    }
+    return null;
+  }
+
   @Override
   public boolean equals(Object other) {
     if (other == null || getClass() != other.getClass()) {
       return false;
     }
-    DynamicType assumption = (DynamicType) other;
-    return dynamicUpperBoundType == assumption.dynamicUpperBoundType;
+    DynamicType dynamicType = (DynamicType) other;
+    return dynamicUpperBoundType.equals(dynamicType.dynamicUpperBoundType);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java
index ae61ee3..400f99e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java
@@ -15,16 +15,21 @@
   DynamicTypeWithLowerBound(
       ClassTypeElement dynamicUpperBoundType, ClassTypeElement dynamicLowerBoundType) {
     super(dynamicUpperBoundType);
+    assert !dynamicUpperBoundType.equals(dynamicLowerBoundType);
     this.dynamicLowerBoundType = dynamicLowerBoundType;
   }
 
-  public static DynamicTypeWithLowerBound create(
+  static DynamicTypeWithLowerBound create(
       AppView<AppInfoWithLiveness> appView,
       ClassTypeElement dynamicUpperBoundType,
       ClassTypeElement dynamicLowerBoundType) {
+    assert dynamicUpperBoundType != null;
+    assert dynamicLowerBoundType != null;
+    assert dynamicUpperBoundType.nullability() == dynamicLowerBoundType.nullability();
     assert appView
         .appInfo()
-        .isSubtype(dynamicLowerBoundType.getClassType(), dynamicUpperBoundType.getClassType());
+        .isStrictSubtypeOf(
+            dynamicLowerBoundType.getClassType(), dynamicUpperBoundType.getClassType());
     return new DynamicTypeWithLowerBound(dynamicUpperBoundType, dynamicLowerBoundType);
   }
 
@@ -48,9 +53,9 @@
     if (other == null || getClass() != other.getClass()) {
       return false;
     }
-    DynamicTypeWithLowerBound assumption = (DynamicTypeWithLowerBound) other;
-    return getDynamicUpperBoundType() == assumption.getDynamicUpperBoundType()
-        && getDynamicLowerBoundType() == assumption.getDynamicLowerBoundType();
+    DynamicTypeWithLowerBound dynamicType = (DynamicTypeWithLowerBound) other;
+    return getDynamicUpperBoundType().equals(dynamicType.getDynamicUpperBoundType())
+        && getDynamicLowerBoundType().equals(dynamicType.getDynamicLowerBoundType());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java
new file mode 100644
index 0000000..a6061d9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.type;
+
+public class ExactDynamicType extends DynamicType {
+
+  ExactDynamicType(ClassTypeElement exactDynamicType) {
+    super(exactDynamicType);
+  }
+
+  @Override
+  public boolean hasDynamicLowerBoundType() {
+    return true;
+  }
+
+  @Override
+  public ClassTypeElement getDynamicLowerBoundType() {
+    return getDynamicUpperBoundType().asClassType();
+  }
+
+  @Override
+  public boolean isTrivial(TypeElement staticType) {
+    return false;
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (other == null || getClass() != other.getClass()) {
+      return false;
+    }
+    ExactDynamicType dynamicType = (ExactDynamicType) other;
+    return getDynamicUpperBoundType().equals(dynamicType.getDynamicUpperBoundType());
+  }
+
+  @Override
+  public int hashCode() {
+    return getDynamicLowerBoundType().hashCode();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/Nullability.java b/src/main/java/com/android/tools/r8/ir/analysis/type/Nullability.java
index 26b9030..e45ac9a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/Nullability.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/Nullability.java
@@ -29,6 +29,10 @@
 
   private Nullability() {}
 
+  public boolean isBottom() {
+    return this == BOTTOM;
+  }
+
   public boolean isDefinitelyNull() {
     return this == DEFINITELY_NULL;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java
index c150cfd..3577c72 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java
@@ -78,6 +78,10 @@
     return getOrCreateVariant(nullability.meet(Nullability.definitelyNotNull()));
   }
 
+  public TypeElement asDefinitelyNull() {
+    return getOrCreateVariant(Nullability.definitelyNull());
+  }
+
   public TypeElement asDefinitelyNotNull() {
     return getOrCreateVariant(Nullability.definitelyNotNull());
   }
@@ -86,6 +90,14 @@
     return getOrCreateVariant(Nullability.maybeNull());
   }
 
+  public ReferenceTypeElement joinNullability(Nullability nullability) {
+    return getOrCreateVariant(nullability().join(nullability));
+  }
+
+  public ReferenceTypeElement meetNullability(Nullability nullability) {
+    return getOrCreateVariant(nullability().meet(nullability));
+  }
+
   @Override
   public boolean isReferenceType() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Assume.java b/src/main/java/com/android/tools/r8/ir/code/Assume.java
index 3a51eec..f25c73b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Assume.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Assume.java
@@ -311,6 +311,8 @@
     }
 
     public boolean verifyCorrectnessOfValues(Value dest, Value src, AppView<?> appView) {
+      assert !dynamicUpperBoundType.isBottom();
+      assert !dynamicUpperBoundType.isTop();
       assert dynamicUpperBoundType.lessThanOrEqualUpToNullability(src.getType(), appView);
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index ce8efd1..4df7f91 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 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.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
@@ -1136,13 +1137,18 @@
   }
 
   public ClassTypeElement getDynamicLowerBoundType(AppView<AppInfoWithLiveness> appView) {
+    return getDynamicLowerBoundType(appView, Nullability.maybeNull());
+  }
+
+  public ClassTypeElement getDynamicLowerBoundType(
+      AppView<AppInfoWithLiveness> appView, Nullability maxNullability) {
     // If it is a final or effectively-final class type, then we know the lower bound.
     if (getType().isClassType()) {
       ClassTypeElement classType = getType().asClassType();
       DexType type = classType.getClassType();
       DexClass clazz = appView.definitionFor(type);
       if (clazz != null && clazz.isEffectivelyFinal(appView)) {
-        return classType;
+        return classType.meetNullability(maxNullability);
       }
     }
 
@@ -1151,25 +1157,32 @@
       return null;
     }
 
-    Instruction definition = root.definition;
+    Instruction definition = root.getDefinition();
     if (definition.isNewInstance()) {
-      DexType type = definition.asNewInstance().clazz;
+      DexType type = definition.asNewInstance().getType();
       DexClass clazz = appView.definitionFor(type);
       if (clazz != null && !clazz.isInterface()) {
-        return ClassTypeElement.create(type, definitelyNotNull(), appView);
+        assert !maxNullability.isBottom();
+        return TypeElement.fromDexType(type, definitelyNotNull(), appView).asClassType();
       }
       return null;
     }
 
     // Try to find an alias of the receiver, which is defined by an instruction of the type Assume.
     Value aliasedValue =
-        getSpecificAliasedValue(value -> value.definition.isAssumeWithDynamicTypeAssumption());
+        getSpecificAliasedValue(value -> value.getDefinition().isAssumeWithDynamicTypeAssumption());
     if (aliasedValue != null) {
-      ClassTypeElement lattice =
-          aliasedValue.definition.asAssume().getDynamicTypeAssumption().getDynamicLowerBoundType();
-      return lattice != null && type.isDefinitelyNotNull() && lattice.isNullable()
-          ? lattice.asMeetWithNotNull()
-          : lattice;
+      ClassTypeElement aliasedValueType =
+          aliasedValue
+              .getDefinition()
+              .asAssume()
+              .getDynamicTypeAssumption()
+              .getDynamicLowerBoundType();
+      if (aliasedValueType != null) {
+        aliasedValueType = aliasedValueType.meetNullability(getType().nullability());
+        assert aliasedValueType.nullability().lessThanOrEqual(maxNullability);
+        return aliasedValueType;
+      }
     }
 
     return null;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
index 3129b82..2a30a23 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
@@ -13,7 +13,9 @@
 import com.android.tools.r8.ir.desugar.CfClassDesugaringEventConsumer.D8CfClassDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.D8CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.CfL8ClassSynthesizerEventConsumer;
 import com.android.tools.r8.utils.ThreadUtils;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.List;
@@ -53,6 +55,17 @@
       throws ExecutionException {
     List<DexProgramClass> classes = appView.appInfo().classes();
 
+    if (appView.options().isDesugaredLibraryCompilation()) {
+      CfL8ClassSynthesizerEventConsumer l8ClassSynthesizerEventConsumer =
+          new CfL8ClassSynthesizerEventConsumer();
+      converter.l8ClassSynthesis(executorService, l8ClassSynthesizerEventConsumer);
+      classes =
+          ImmutableList.<DexProgramClass>builder()
+              .addAll(classes)
+              .addAll(l8ClassSynthesizerEventConsumer.getSynthesizedClasses())
+              .build();
+    }
+
     D8CfClassDesugaringEventConsumer classDesugaringEventConsumer =
         CfClassDesugaringEventConsumer.createForD8(methodProcessor);
     converter.desugarClassesForD8(classes, classDesugaringEventConsumer, executorService);
@@ -76,11 +89,6 @@
       D8CfInstructionDesugaringEventConsumer instructionDesugaringEventConsumer =
           CfInstructionDesugaringEventConsumer.createForD8(methodProcessor);
 
-      // TODO(b/191656218): Move upfront the loop and use maybe the class event consumer.
-      if (appView.options().isDesugaredLibraryCompilation()) {
-        converter.ensureWrappersForL8(instructionDesugaringEventConsumer);
-      }
-
       // Process the wave and wait for all IR processing to complete.
       methodProcessor.newWave();
       ThreadUtils.processItems(
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 73ef0f1..8595ecd 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
@@ -49,6 +49,8 @@
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.D8CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.CfL8ClassSynthesizerCollection;
+import com.android.tools.r8.ir.desugar.CfL8ClassSynthesizerEventConsumer;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer.D8CfPostProcessingDesugaringEventConsumer;
@@ -355,13 +357,6 @@
         D8NestBasedAccessDesugaring::clearNestAttributes);
   }
 
-  public void ensureWrappersForL8(
-      D8CfInstructionDesugaringEventConsumer instructionDesugaringEventConsumer) {
-    assert appView.options().isDesugaredLibraryCompilation();
-    instructionDesugaring.withDesugaredLibraryAPIConverter(
-        converter -> converter.ensureWrappersForL8(instructionDesugaringEventConsumer));
-  }
-
   private void staticizeClasses(
       OptimizationFeedback feedback, ExecutorService executorService, GraphLens applied)
       throws ExecutionException {
@@ -414,11 +409,11 @@
     reportNestDesugarDependencies();
     clearNestAttributes();
 
-    application = commitPendingSyntheticItems(appView, application);
+    application = commitPendingSyntheticItemsD8(appView, application);
 
     postProcessingDesugaringForD8(methodProcessor, executor);
 
-    application = commitPendingSyntheticItems(appView, application);
+    application = commitPendingSyntheticItemsD8(appView, application);
 
     // Build a new application with jumbo string info,
     Builder<?> builder = application.builder().setHighestSortingString(highestSortingString);
@@ -437,7 +432,7 @@
             appView.appInfo().getMainDexInfo()));
   }
 
-  private DexApplication commitPendingSyntheticItems(
+  private DexApplication commitPendingSyntheticItemsD8(
       AppView<AppInfo> appView, DexApplication application) {
     if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
       appView.setAppInfo(
@@ -449,6 +444,23 @@
     return application;
   }
 
+  private static void commitPendingSyntheticItemsR8(AppView<AppInfoWithLiveness> appView) {
+    if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
+      appView.setAppInfo(
+          appView
+              .appInfo()
+              .rebuildWithLiveness(appView.getSyntheticItems().commit(appView.appInfo().app())));
+    }
+  }
+
+  public void l8ClassSynthesis(
+      ExecutorService executorService,
+      CfL8ClassSynthesizerEventConsumer l8ClassSynthesizerEventConsumer)
+      throws ExecutionException {
+    new CfL8ClassSynthesizerCollection(appView, instructionDesugaring.getRetargetingInfo())
+        .synthesizeClasses(executorService, l8ClassSynthesizerEventConsumer);
+  }
+
   private void postProcessingDesugaringForD8(
       D8MethodProcessor methodProcessor, ExecutorService executorService)
       throws ExecutionException {
@@ -726,6 +738,9 @@
     // lenses with code rewriting are added.
     appView.clearCodeRewritings();
 
+    // Commit synthetics from the primary optimization pass.
+    commitPendingSyntheticItemsR8(appView);
+
     // Analyze the data collected by the argument propagator, use the analysis result to update
     // the parameter optimization infos, and rewrite the application.
     appView.withArgumentPropagator(
@@ -797,12 +812,8 @@
 
     // Commit synthetics before creating a builder (otherwise the builder will not include the
     // synthetics.)
-    if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
-      appView.setAppInfo(
-          appView
-              .appInfo()
-              .rebuildWithLiveness(appView.getSyntheticItems().commit(appView.appInfo().app())));
-    }
+    commitPendingSyntheticItemsR8(appView);
+
     // Build a new application with jumbo string info.
     Builder<?> builder = appView.appInfo().app().builder();
     builder.setHighestSortingString(highestSortingString);
@@ -1538,7 +1549,7 @@
         printMethod(code, "IR after idempotent function call canonicalization (SSA)", previous);
 
     // Insert code to log arguments if requested.
-    if (options.methodMatchesLogArgumentsFilter(method)) {
+    if (options.methodMatchesLogArgumentsFilter(method) && !method.isProcessed()) {
       codeRewriter.logArgumentTypes(method, code);
       assert code.isConsistentSSA();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index 0b29d90..6eda99c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
@@ -15,8 +14,8 @@
 import com.android.tools.r8.ir.conversion.ClassConverterResult;
 import com.android.tools.r8.ir.conversion.D8MethodProcessor;
 import com.android.tools.r8.ir.desugar.backports.BackportedMethodDesugaringEventConsumer;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverterEventConsumer;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterInstructionEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterInstructionEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPIConverterEventConsumer;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialBridgeInfo;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodDesugaringEventConsumer;
@@ -70,11 +69,6 @@
     return new CfInstructionDesugaringEventConsumer() {
 
       @Override
-      public void acceptWrapperProgramClass(DexProgramClass clazz) {
-        assert false;
-      }
-
-      @Override
       public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
         assert false;
       }
@@ -85,21 +79,11 @@
       }
 
       @Override
-      public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
-        assert false;
-      }
-
-      @Override
       public void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz) {
         assert false;
       }
 
       @Override
-      public void acceptInterfaceInjection(DexProgramClass clazz, DexClass newInterface) {
-        assert false;
-      }
-
-      @Override
       public void acceptThrowMethod(ProgramMethod method, ProgramMethod context) {
         assert false;
       }
@@ -154,6 +138,11 @@
       public void acceptTwrCloseResourceMethod(ProgramMethod closeMethod, ProgramMethod context) {
         assert false;
       }
+
+      @Override
+      public void acceptCompanionClassClinit(ProgramMethod method) {
+        assert false;
+      }
     };
   }
 
@@ -171,21 +160,11 @@
     }
 
     @Override
-    public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
-      methodProcessor.scheduleDesugaredMethodsForProcessing(clazz.programMethods());
-    }
-
-    @Override
     public void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz) {
       // Intentionally empty.
     }
 
     @Override
-    public void acceptInterfaceInjection(DexProgramClass clazz, DexClass newInterface) {
-      // Intentionally empty.
-    }
-
-    @Override
     public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
       methodProcessor.scheduleMethodForProcessing(backportedMethod, this);
     }
@@ -247,11 +226,6 @@
     }
 
     @Override
-    public void acceptWrapperProgramClass(DexProgramClass clazz) {
-      methodProcessor.scheduleDesugaredMethodsForProcessing(clazz.programMethods());
-    }
-
-    @Override
     public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
       // Intentionally empty.
     }
@@ -261,6 +235,11 @@
       methodProcessor.scheduleDesugaredMethodForProcessing(method);
     }
 
+    @Override
+    public void acceptCompanionClassClinit(ProgramMethod method) {
+      methodProcessor.scheduleDesugaredMethodForProcessing(method);
+    }
+
     public List<ProgramMethod> finalizeDesugaring(
         AppView<?> appView, ClassConverterResult.Builder classConverterResultBuilder) {
       List<ProgramMethod> needsProcessing = new ArrayList<>();
@@ -342,22 +321,16 @@
     }
 
     @Override
-    public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
-      // Called only in Desugared library compilation which is D8.
-      assert false;
-    }
-
-    @Override
-    public void acceptInterfaceInjection(DexProgramClass clazz, DexClass newInterface) {
-      additions.injectInterface(clazz, newInterface);
-    }
-
-    @Override
     public void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz) {
       additions.addLiveClasspathClass(clazz);
     }
 
     @Override
+    public void acceptCompanionClassClinit(ProgramMethod method) {
+      // TODO(b/183998768): Update this once desugaring is moved to the enqueuer.
+    }
+
+    @Override
     public void acceptRecordClass(DexProgramClass recordClass) {
       // This is called each time an instruction or a class is found to require the record class.
       assert false : "TODO(b/179146128): To be implemented";
@@ -380,12 +353,6 @@
     }
 
     @Override
-    public void acceptWrapperProgramClass(DexProgramClass clazz) {
-      // Called only in Desugared library compilation which is D8.
-      assert false;
-    }
-
-    @Override
     public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
       additions.addLiveClasspathClass(clazz);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfL8ClassSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfL8ClassSynthesizer.java
new file mode 100644
index 0000000..2eefa63
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfL8ClassSynthesizer.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar;
+
+public interface CfL8ClassSynthesizer {
+
+  void synthesizeClasses(CfL8ClassSynthesizerEventConsumer eventConsumer);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfL8ClassSynthesizerCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfL8ClassSynthesizerCollection.java
new file mode 100644
index 0000000..9accf26
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfL8ClassSynthesizerCollection.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterL8Synthesizer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
+import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceSynthesizer;
+import com.android.tools.r8.utils.ThreadUtils;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class CfL8ClassSynthesizerCollection {
+
+  private Collection<CfL8ClassSynthesizer> synthesizers = new ArrayList<>();
+
+  public CfL8ClassSynthesizerCollection(AppView<?> appView, RetargetingInfo retargetingInfo) {
+    assert appView.options().isDesugaredLibraryCompilation();
+    EmulatedInterfaceSynthesizer emulatedInterfaceSynthesizer =
+        EmulatedInterfaceSynthesizer.create(appView);
+    if (emulatedInterfaceSynthesizer != null) {
+      synthesizers.add(emulatedInterfaceSynthesizer);
+    }
+    DesugaredLibraryRetargeterL8Synthesizer retargeterL8Synthesizer =
+        DesugaredLibraryRetargeterL8Synthesizer.create(appView, retargetingInfo);
+    if (retargeterL8Synthesizer != null) {
+      synthesizers.add(retargeterL8Synthesizer);
+    }
+    synthesizers.add(new DesugaredLibraryWrapperSynthesizer(appView));
+  }
+
+  public void synthesizeClasses(
+      ExecutorService executorService, CfL8ClassSynthesizerEventConsumer eventConsumer)
+      throws ExecutionException {
+    ThreadUtils.processItems(
+        synthesizers, synthesizer -> synthesizer.synthesizeClasses(eventConsumer), executorService);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfL8ClassSynthesizerEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfL8ClassSynthesizerEventConsumer.java
new file mode 100644
index 0000000..5e41810
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfL8ClassSynthesizerEventConsumer.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterL8SynthesizerEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer;
+import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceSynthesizerEventConsumer;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+public class CfL8ClassSynthesizerEventConsumer
+    implements EmulatedInterfaceSynthesizerEventConsumer,
+        DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer,
+        DesugaredLibraryRetargeterL8SynthesizerEventConsumer {
+
+  private Set<DexProgramClass> synthesizedClasses = Sets.newConcurrentHashSet();
+
+  @Override
+  public void acceptEmulatedInterface(DexProgramClass clazz) {
+    synthesizedClasses.add(clazz);
+  }
+
+  @Override
+  public void acceptWrapperProgramClass(DexProgramClass clazz) {
+    synthesizedClasses.add(clazz);
+  }
+
+  @Override
+  public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
+    synthesizedClasses.add(clazz);
+  }
+
+  public Set<DexProgramClass> getSynthesizedClasses() {
+    return synthesizedClasses;
+  }
+
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
index e539861..2d99720 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
@@ -53,7 +53,8 @@
         InterfaceMethodProcessorFacade interfaceMethodProcessorFacade,
         RetargetingInfo retargetingInfo) {
       ArrayList<CfPostProcessingDesugaring> desugarings = new ArrayList<>();
-      if (!appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
+      if (!appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()
+          && !appView.options().isDesugaredLibraryCompilation()) {
         desugarings.add(new DesugaredLibraryRetargeterPostProcessor(appView, retargetingInfo));
       }
       if (interfaceMethodProcessorFacade != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
index d0b798b..95407f8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
@@ -8,8 +8,8 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.D8MethodProcessor;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverterEventConsumer.DesugaredLibraryAPIConverterPostProcessingEventConsumer;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterInstructionEventConsumer.DesugaredLibraryRetargeterPostProcessingEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterPostProcessingEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPICallbackSynthesizorEventConsumer;
 import com.android.tools.r8.ir.desugar.itf.InterfaceProcessingDesugaringEventConsumer;
 import com.android.tools.r8.shaking.Enqueuer.SyntheticAdditions;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -23,7 +23,7 @@
 public abstract class CfPostProcessingDesugaringEventConsumer
     implements DesugaredLibraryRetargeterPostProcessingEventConsumer,
         InterfaceProcessingDesugaringEventConsumer,
-        DesugaredLibraryAPIConverterPostProcessingEventConsumer {
+        DesugaredLibraryAPICallbackSynthesizorEventConsumer {
 
   public static D8CfPostProcessingDesugaringEventConsumer createForD8(
       D8MethodProcessor methodProcessor) {
@@ -38,6 +38,7 @@
 
   public static class D8CfPostProcessingDesugaringEventConsumer
       extends CfPostProcessingDesugaringEventConsumer {
+
     private final D8MethodProcessor methodProcessor;
     // Methods cannot be processed directly because we cannot add method to classes while
     // concurrently processing other methods.
@@ -48,11 +49,6 @@
     }
 
     @Override
-    public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
-      methodsToReprocess.addAll(clazz.programMethods());
-    }
-
-    @Override
     public void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz) {
       // Intentionally empty.
     }
@@ -73,11 +69,6 @@
     }
 
     @Override
-    public void acceptEmulatedInterfaceMethod(ProgramMethod method) {
-      methodsToReprocess.add(method);
-    }
-
-    @Override
     public void finalizeDesugaring() throws ExecutionException {
       assert methodProcessor.verifyNoPendingMethodProcessing();
       methodProcessor.newWave();
@@ -89,10 +80,16 @@
     public void acceptAPIConversionCallback(ProgramMethod method) {
       methodsToReprocess.add(method);
     }
+
+    @Override
+    public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
+      // Intentionally empty.
+    }
   }
 
   public static class R8PostProcessingDesugaringEventConsumer
       extends CfPostProcessingDesugaringEventConsumer {
+
     private final SyntheticAdditions additions;
 
     R8PostProcessingDesugaringEventConsumer(SyntheticAdditions additions) {
@@ -105,11 +102,6 @@
     }
 
     @Override
-    public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
-      additions.addLiveMethods(clazz.programMethods());
-    }
-
-    @Override
     public void acceptInterfaceInjection(DexProgramClass clazz, DexClass newInterface) {
       additions.injectInterface(clazz, newInterface);
     }
@@ -130,13 +122,13 @@
     }
 
     @Override
-    public void acceptEmulatedInterfaceMethod(ProgramMethod method) {
-      assert false : "TODO(b/183998768): Support Interface processing in R8";
+    public void acceptAPIConversionCallback(ProgramMethod method) {
+      additions.addLiveMethod(method);
     }
 
     @Override
-    public void acceptAPIConversionCallback(ProgramMethod method) {
-      additions.addLiveMethod(method);
+    public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
+      additions.addLiveClasspathClass(clazz);
     }
   }
 }
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 88f451b..61e6828 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
@@ -5,7 +5,6 @@
 package com.android.tools.r8.ir.desugar;
 
 import static com.android.tools.r8.ir.desugar.lambda.ForcefullyMovedLambdaMethodConsumer.emptyForcefullyMovedLambdaMethodConsumer;
-import static com.android.tools.r8.utils.AndroidApiLevelUtils.getApiLevelIfEnabledForNewMember;
 import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
 
 import com.android.tools.r8.dex.Constants;
@@ -40,11 +39,11 @@
 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 com.android.tools.r8.utils.AndroidApiLevel;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
-import java.util.function.Function;
 
 /**
  * Represents lambda class generated for a lambda descriptor in context of lambda instantiation
@@ -109,7 +108,7 @@
     this.lambdaField =
         !stateless ? null : factory.createField(type, type, factory.lambdaInstanceFieldName);
 
-    // Synthesize the program class one all fields are set.
+    // Synthesize the program class once all fields are set.
     synthesizeLambdaClass(builder);
   }
 
@@ -244,7 +243,8 @@
               null,
               deprecated,
               d8R8Synthesized,
-              getApiLevelIfEnabledForNewMember(appView, Function.identity())));
+              // The api level is computed when tracing.
+              AndroidApiLevel.minApiLevelIfEnabledOrUnknown(appView)));
     }
     builder.setInstanceFields(fields);
   }
@@ -270,7 +270,8 @@
                   DexValueNull.NULL,
                   deprecated,
                   d8R8Synthesized,
-                  getApiLevelIfEnabledForNewMember(appView, Function.identity()))));
+                  // The api level is computed when tracing.
+                  AndroidApiLevel.minApiLevelIfEnabledOrUnknown(appView))));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPICallbackSynthesizor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPICallbackSynthesizor.java
index cfcb9b0..3e0143a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPICallbackSynthesizor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPICallbackSynthesizor.java
@@ -19,8 +19,8 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverterEventConsumer.DesugaredLibraryAPIConverterPostProcessingEventConsumer;
-import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPICallbackSynthesizorEventConsumer;
+import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APICallbackWrapperCfCodeProvider;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Sets;
@@ -190,17 +190,16 @@
   private ProgramMethod generateCallbackMethod(
       DexEncodedMethod originalMethod,
       DexProgramClass clazz,
-      DesugaredLibraryAPIConverterPostProcessingEventConsumer eventConsumer) {
+      DesugaredLibraryAPICallbackSynthesizorEventConsumer eventConsumer) {
     DexMethod methodToInstall =
         methodWithVivifiedTypeInSignature(originalMethod.getReference(), clazz.type, appView);
     CfCode cfCode =
-        new APIConverterWrapperCfCodeProvider(
+        new APICallbackWrapperCfCodeProvider(
                 appView,
                 originalMethod.getReference(),
-                null,
                 wrapperSynthesizor,
                 clazz.isInterface(),
-                null)
+                eventConsumer)
             .generateCfCode();
     DexEncodedMethod newMethod =
         wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
@@ -209,11 +208,8 @@
       newMethod.setLibraryMethodOverride(OptionalBool.TRUE);
     }
     ProgramMethod callback = new ProgramMethod(clazz, newMethod);
-    if (eventConsumer != null) {
-      eventConsumer.acceptAPIConversionCallback(callback);
-    } else {
-      assert appView.enableWholeProgramOptimizations();
-    }
+    assert eventConsumer != null;
+    eventConsumer.acceptAPIConversionCallback(callback);
     return callback;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java
index ebab39e..be8ac84 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java
@@ -34,6 +34,7 @@
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConversionCfCodeProvider;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
@@ -241,12 +242,6 @@
     DexProto newProto = appView.dexItemFactory().createProto(newReturnType, newParameters);
     return appView.dexItemFactory().createMethod(holder, newProto, originalMethod.name);
   }
-
-  public void ensureWrappersForL8(CfInstructionDesugaringEventConsumer eventConsumer) {
-    assert appView.options().isDesugaredLibraryCompilation();
-    wrapperSynthesizor.ensureWrappersForL8(eventConsumer);
-  }
-
   public void generateTrackingWarnings() {
     generateTrackDesugaredAPIWarnings(trackedAPIs, "", appView);
   }
@@ -324,7 +319,8 @@
   }
 
   private DexMethod computeReturnConversion(
-      DexMethod invokedMethod, DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
+      DexMethod invokedMethod,
+      DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer) {
     DexType returnType = invokedMethod.proto.returnType;
     if (wrapperSynthesizor.shouldConvert(returnType, invokedMethod)) {
       DexType newReturnType = DesugaredLibraryAPIConverter.vivifiedTypeFor(returnType, appView);
@@ -335,7 +331,8 @@
   }
 
   private DexMethod[] computeParameterConversions(
-      DexMethod invokedMethod, DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
+      DexMethod invokedMethod,
+      DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer) {
     DexMethod[] parameterConversions = new DexMethod[invokedMethod.getArity()];
     DexType[] parameters = invokedMethod.proto.parameters.values;
     for (int i = 0; i < parameters.length; i++) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverterEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverterEventConsumer.java
deleted file mode 100644
index 7af64bb..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverterEventConsumer.java
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.desugar.desugaredlibrary;
-
-import com.android.tools.r8.graph.DexClasspathClass;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.ProgramMethod;
-
-public interface DesugaredLibraryAPIConverterEventConsumer {
-
-  void acceptWrapperProgramClass(DexProgramClass clazz);
-
-  void acceptWrapperClasspathClass(DexClasspathClass clazz);
-
-  void acceptAPIConversion(ProgramMethod method);
-
-  interface DesugaredLibraryAPIConverterPostProcessingEventConsumer {
-
-    void acceptAPIConversionCallback(ProgramMethod method);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeter.java
index a4c9b12..8a59f14 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeter.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterInstructionEventConsumer;
 import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
 import java.util.Collection;
 import java.util.Collections;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterInstructionEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterInstructionEventConsumer.java
deleted file mode 100644
index ae7630d..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterInstructionEventConsumer.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.desugar.desugaredlibrary;
-
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexClasspathClass;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.ProgramMethod;
-
-public interface DesugaredLibraryRetargeterInstructionEventConsumer {
-
-  void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz);
-
-  void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz);
-
-  void acceptInterfaceInjection(DexProgramClass clazz, DexClass newInterface);
-
-  interface DesugaredLibraryRetargeterPostProcessingEventConsumer
-      extends DesugaredLibraryRetargeterInstructionEventConsumer {
-
-    void acceptForwardingMethod(ProgramMethod method);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterL8Synthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterL8Synthesizer.java
new file mode 100644
index 0000000..dad4cd6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterL8Synthesizer.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.desugar.desugaredlibrary;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.ir.desugar.CfL8ClassSynthesizer;
+import com.android.tools.r8.ir.desugar.CfL8ClassSynthesizerEventConsumer;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
+
+public class DesugaredLibraryRetargeterL8Synthesizer implements CfL8ClassSynthesizer {
+
+  private final AppView<?> appView;
+  private final DesugaredLibraryRetargeterSyntheticHelper syntheticHelper;
+  private final DexClassAndMethodSet emulatedDispatchMethods;
+
+  public static DesugaredLibraryRetargeterL8Synthesizer create(
+      AppView<?> appView, RetargetingInfo retargetingInfo) {
+    assert appView.options().isDesugaredLibraryCompilation();
+    if (retargetingInfo == null || retargetingInfo.getEmulatedDispatchMethods().isEmpty()) {
+      assert appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty();
+      return null;
+    }
+    return new DesugaredLibraryRetargeterL8Synthesizer(appView, retargetingInfo);
+  }
+
+  public DesugaredLibraryRetargeterL8Synthesizer(
+      AppView<?> appView, RetargetingInfo retargetingInfo) {
+    this.appView = appView;
+    this.syntheticHelper = new DesugaredLibraryRetargeterSyntheticHelper(appView);
+    emulatedDispatchMethods = retargetingInfo.getEmulatedDispatchMethods();
+  }
+
+  @Override
+  public void synthesizeClasses(CfL8ClassSynthesizerEventConsumer eventConsumer) {
+    assert !emulatedDispatchMethods.isEmpty();
+    for (DexClassAndMethod emulatedDispatchMethod : emulatedDispatchMethods) {
+      syntheticHelper.ensureProgramEmulatedHolderDispatchMethod(
+          emulatedDispatchMethod, eventConsumer);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterPostProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterPostProcessor.java
index 64d3cc6..b6200cb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterPostProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterPostProcessor.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterInstructionEventConsumer.DesugaredLibraryRetargeterPostProcessingEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterPostProcessingEventConsumer;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
 import com.google.common.collect.Maps;
@@ -49,11 +49,8 @@
       CfPostProcessingDesugaringEventConsumer eventConsumer,
       ExecutorService executorService)
       throws ExecutionException {
-    if (appView.options().isDesugaredLibraryCompilation()) {
-      ensureEmulatedDispatchMethodsSynthesized(eventConsumer);
-    } else {
-      ensureInterfacesAndForwardingMethodsSynthesized(programClasses, eventConsumer);
-    }
+    assert !appView.options().isDesugaredLibraryCompilation();
+    ensureInterfacesAndForwardingMethodsSynthesized(programClasses, eventConsumer);
   }
 
   private void ensureInterfacesAndForwardingMethodsSynthesized(
@@ -150,17 +147,6 @@
     return desugaringForwardingMethod;
   }
 
-  private void ensureEmulatedDispatchMethodsSynthesized(
-      DesugaredLibraryRetargeterInstructionEventConsumer eventConsumer) {
-    assert appView.options().isDesugaredLibraryCompilation();
-    if (emulatedDispatchMethods.isEmpty()) {
-      return;
-    }
-    for (DexClassAndMethod emulatedDispatchMethod : emulatedDispatchMethods) {
-      syntheticHelper.ensureEmulatedHolderDispatchMethod(emulatedDispatchMethod, eventConsumer);
-    }
-  }
-
   private void reportInvalidLibrarySupertype(
       DexLibraryClass libraryClass, DexClassAndMethodSet retarget) {
     DexClass dexClass = appView.definitionFor(libraryClass.superType);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSynthesizerEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSynthesizerEventConsumer.java
new file mode 100644
index 0000000..fab6f10
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSynthesizerEventConsumer.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface DesugaredLibraryRetargeterSynthesizerEventConsumer {
+
+  interface DesugaredLibraryRetargeterL8SynthesizerEventConsumer {
+    void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz);
+  }
+
+  interface DesugaredLibraryRetargeterInstructionEventConsumer {
+    void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz);
+  }
+
+  interface DesugaredLibraryRetargeterPostProcessingEventConsumer
+      extends DesugaredLibraryRetargeterInstructionEventConsumer {
+    void acceptInterfaceInjection(DexProgramClass clazz, DexClass newInterface);
+
+    void acceptForwardingMethod(ProgramMethod method);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSyntheticHelper.java
index 02eb59b..9569c61 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSyntheticHelper.java
@@ -11,6 +11,8 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterInstructionEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterL8SynthesizerEventConsumer;
 import com.android.tools.r8.ir.synthetic.EmulateInterfaceSyntheticCfCodeProvider;
 import com.android.tools.r8.synthesis.SyntheticClassBuilder;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
@@ -28,91 +30,99 @@
   public DexClass ensureEmulatedHolderDispatchMethod(
       DexClassAndMethod emulatedDispatchMethod,
       DesugaredLibraryRetargeterInstructionEventConsumer eventConsumer) {
-    assert eventConsumer != null || appView.enableWholeProgramOptimizations();
+    assert eventConsumer != null;
+    if (appView.options().isDesugaredLibraryCompilation()) {
+      return appView
+          .getSyntheticItems()
+          .getExistingFixedClass(
+              SyntheticKind.RETARGET_CLASS, emulatedDispatchMethod.getHolder(), appView);
+    }
     DexClass interfaceClass =
         ensureEmulatedInterfaceDispatchMethod(emulatedDispatchMethod, eventConsumer);
     DexMethod itfMethod =
         interfaceClass.lookupMethod(emulatedDispatchMethod.getReference()).getReference();
-    DexClass holderDispatch;
-    if (appView.options().isDesugaredLibraryCompilation()) {
-      holderDispatch =
-          appView
-              .getSyntheticItems()
-              .ensureFixedClass(
-                  SyntheticKind.RETARGET_CLASS,
-                  emulatedDispatchMethod.getHolder(),
-                  appView,
-                  classBuilder ->
-                      buildHolderDispatchMethod(classBuilder, emulatedDispatchMethod, itfMethod),
-                  clazz -> {
-                    if (eventConsumer != null) {
-                      eventConsumer.acceptDesugaredLibraryRetargeterDispatchProgramClass(clazz);
-                    }
-                  });
-    } else {
-      ClasspathOrLibraryClass context =
-          emulatedDispatchMethod.getHolder().asClasspathOrLibraryClass();
-      assert context != null;
-      holderDispatch =
-          appView
-              .getSyntheticItems()
-              .ensureFixedClasspathClass(
-                  SyntheticKind.RETARGET_CLASS,
-                  context,
-                  appView,
-                  classBuilder ->
-                      buildHolderDispatchMethod(classBuilder, emulatedDispatchMethod, itfMethod),
-                  clazz -> {
-                    if (eventConsumer != null) {
-                      eventConsumer.acceptDesugaredLibraryRetargeterDispatchClasspathClass(clazz);
-                    }
-                  });
-    }
-    rewriteType(holderDispatch.type);
-    return holderDispatch;
+    ClasspathOrLibraryClass context =
+        emulatedDispatchMethod.getHolder().asClasspathOrLibraryClass();
+    assert context != null;
+    return appView
+        .getSyntheticItems()
+        .ensureFixedClasspathClass(
+            SyntheticKind.RETARGET_CLASS,
+            context,
+            appView,
+            classBuilder ->
+                buildHolderDispatchMethod(classBuilder, emulatedDispatchMethod, itfMethod),
+            clazz -> {
+              eventConsumer.acceptDesugaredLibraryRetargeterDispatchClasspathClass(clazz);
+              rewriteType(clazz.type);
+            });
+  }
+
+  public void ensureProgramEmulatedHolderDispatchMethod(
+      DexClassAndMethod emulatedDispatchMethod,
+      DesugaredLibraryRetargeterL8SynthesizerEventConsumer eventConsumer) {
+    assert eventConsumer != null;
+    assert appView.options().isDesugaredLibraryCompilation();
+    DexClass interfaceClass =
+        ensureEmulatedInterfaceDispatchMethod(emulatedDispatchMethod, eventConsumer);
+    DexMethod itfMethod =
+        interfaceClass.lookupMethod(emulatedDispatchMethod.getReference()).getReference();
+    appView
+        .getSyntheticItems()
+        .ensureFixedClass(
+            SyntheticKind.RETARGET_CLASS,
+            emulatedDispatchMethod.getHolder(),
+            appView,
+            classBuilder ->
+                buildHolderDispatchMethod(classBuilder, emulatedDispatchMethod, itfMethod),
+            clazz -> {
+              eventConsumer.acceptDesugaredLibraryRetargeterDispatchProgramClass(clazz);
+              rewriteType(clazz.type);
+            });
   }
 
   public DexClass ensureEmulatedInterfaceDispatchMethod(
       DexClassAndMethod emulatedDispatchMethod,
       DesugaredLibraryRetargeterInstructionEventConsumer eventConsumer) {
-    assert eventConsumer != null || appView.enableWholeProgramOptimizations();
-    DexClass interfaceDispatch;
+    assert eventConsumer != null;
     if (appView.options().isDesugaredLibraryCompilation()) {
-      interfaceDispatch =
-          appView
-              .getSyntheticItems()
-              .ensureFixedClass(
-                  SyntheticKind.RETARGET_INTERFACE,
-                  emulatedDispatchMethod.getHolder(),
-                  appView,
-                  classBuilder ->
-                      buildInterfaceDispatchMethod(classBuilder, emulatedDispatchMethod),
-                  clazz -> {
-                    if (eventConsumer != null) {
-                      eventConsumer.acceptDesugaredLibraryRetargeterDispatchProgramClass(clazz);
-                    }
-                  });
-    } else {
-      ClasspathOrLibraryClass context =
-          emulatedDispatchMethod.getHolder().asClasspathOrLibraryClass();
-      assert context != null;
-      interfaceDispatch =
-          appView
-              .getSyntheticItems()
-              .ensureFixedClasspathClass(
-                  SyntheticKind.RETARGET_INTERFACE,
-                  context,
-                  appView,
-                  classBuilder ->
-                      buildInterfaceDispatchMethod(classBuilder, emulatedDispatchMethod),
-                  clazz -> {
-                    if (eventConsumer != null) {
-                      eventConsumer.acceptDesugaredLibraryRetargeterDispatchClasspathClass(clazz);
-                    }
-                  });
+      return appView
+          .getSyntheticItems()
+          .getExistingFixedClass(
+              SyntheticKind.RETARGET_INTERFACE, emulatedDispatchMethod.getHolder(), appView);
     }
-    rewriteType(interfaceDispatch.type);
-    return interfaceDispatch;
+    ClasspathOrLibraryClass context =
+        emulatedDispatchMethod.getHolder().asClasspathOrLibraryClass();
+    assert context != null;
+    return appView
+        .getSyntheticItems()
+        .ensureFixedClasspathClass(
+            SyntheticKind.RETARGET_INTERFACE,
+            context,
+            appView,
+            classBuilder -> buildInterfaceDispatchMethod(classBuilder, emulatedDispatchMethod),
+            clazz -> {
+              eventConsumer.acceptDesugaredLibraryRetargeterDispatchClasspathClass(clazz);
+              rewriteType(clazz.type);
+            });
+  }
+
+  public DexClass ensureEmulatedInterfaceDispatchMethod(
+      DexClassAndMethod emulatedDispatchMethod,
+      DesugaredLibraryRetargeterL8SynthesizerEventConsumer eventConsumer) {
+    assert appView.options().isDesugaredLibraryCompilation();
+    assert eventConsumer != null;
+    return appView
+        .getSyntheticItems()
+        .ensureFixedClass(
+            SyntheticKind.RETARGET_INTERFACE,
+            emulatedDispatchMethod.getHolder(),
+            appView,
+            classBuilder -> buildInterfaceDispatchMethod(classBuilder, emulatedDispatchMethod),
+            clazz -> {
+              eventConsumer.acceptDesugaredLibraryRetargeterDispatchProgramClass(clazz);
+              rewriteType(clazz.type);
+            });
   }
 
   private void buildInterfaceDispatchMethod(
@@ -149,14 +159,16 @@
               .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
               .setCode(
                   methodSig ->
-                      new EmulateInterfaceSyntheticCfCodeProvider(
-                              methodSig.getHolderType(),
-                              emulatedDispatchMethod.getHolderType(),
-                              desugarMethod,
-                              itfMethod,
-                              Collections.emptyList(),
-                              appView)
-                          .generateCfCode());
+                      appView.options().isDesugaredLibraryCompilation()
+                          ? new EmulateInterfaceSyntheticCfCodeProvider(
+                                  methodSig.getHolderType(),
+                                  emulatedDispatchMethod.getHolderType(),
+                                  desugarMethod,
+                                  itfMethod,
+                                  Collections.emptyList(),
+                                  appView)
+                              .generateCfCode()
+                          : null);
         });
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java
index 2605672..3e21feb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -19,13 +18,17 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfL8ClassSynthesizer;
+import com.android.tools.r8.ir.desugar.CfL8ClassSynthesizerEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterConstructorCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterThrowRuntimeExceptionCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterVivifiedWrapperCfCodeProvider;
@@ -39,12 +42,12 @@
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Consumer;
 import java.util.function.Function;
 
 // I am responsible for the generation of wrappers used to call library APIs when desugaring
@@ -90,14 +93,14 @@
 //     return new j$....BiFunction$-WRP(wrappedValue);
 //     }
 //   }
-public class DesugaredLibraryWrapperSynthesizer {
+public class DesugaredLibraryWrapperSynthesizer implements CfL8ClassSynthesizer {
 
   private final AppView<?> appView;
   private final DexItemFactory factory;
   private final ConcurrentHashMap<DexType, List<DexEncodedMethod>> allImplementedMethodsCache =
       new ConcurrentHashMap<>();
 
-  DesugaredLibraryWrapperSynthesizer(AppView<?> appView) {
+  public DesugaredLibraryWrapperSynthesizer(AppView<?> appView) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
   }
@@ -122,20 +125,49 @@
       DexType type,
       DexType srcType,
       DexType destType,
-      DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
+      DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer) {
+    DexMethod customConversion = getCustomConversion(type, srcType, destType);
+    if (customConversion != null) {
+      return customConversion;
+    }
+    assert canGenerateWrapper(type) : type;
+    DexClass clazz = getValidClassToWrap(type);
+    WrapperConversions wrapperConversions = ensureWrappers(clazz, eventConsumer);
+    DexMethod conversion =
+        type == srcType
+            ? wrapperConversions.getConversion()
+            : wrapperConversions.getVivifiedConversion();
+    assert srcType == conversion.getArgumentType(0, true);
+    assert destType == conversion.getReturnType();
+    return conversion;
+  }
+
+  public DexMethod getExistingProgramConversionMethod(
+      DexType type, DexType srcType, DexType destType) {
+    DexMethod customConversion = getCustomConversion(type, srcType, destType);
+    if (customConversion != null) {
+      return customConversion;
+    }
+    WrapperConversions wrapperConversions =
+        getExistingProgramWrapperConversions(getValidClassToWrap(type));
+    DexMethod conversion =
+        type == srcType
+            ? wrapperConversions.getConversion()
+            : wrapperConversions.getVivifiedConversion();
+    assert srcType == conversion.getArgumentType(0, true);
+    return conversion;
+  }
+
+  private DexMethod getCustomConversion(DexType type, DexType srcType, DexType destType) {
     // ConversionType holds the methods "rewrittenType convert(type)" and the other way around.
     // But everything is going to be rewritten, so we need to use vivifiedType and type".
     DexType conversionHolder =
         appView.options().desugaredLibraryConfiguration.getCustomConversions().get(type);
-    if (conversionHolder == null) {
-      conversionHolder =
-          type == srcType
-              ? ensureTypeWrapper(type, eventConsumer)
-              : ensureVivifiedTypeWrapper(type, eventConsumer);
+    if (conversionHolder != null) {
+      return factory.createMethod(
+          conversionHolder, factory.createProto(destType, srcType), factory.convertMethodName);
     }
-    assert conversionHolder != null;
-    return factory.createMethod(
-        conversionHolder, factory.createProto(destType, srcType), factory.convertMethodName);
+    return null;
   }
 
   private boolean canConvert(DexType type) {
@@ -165,16 +197,6 @@
     return appView.options().desugaredLibraryConfiguration.getWrapperConversions().contains(type);
   }
 
-  private DexType ensureTypeWrapper(
-      DexType type, DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
-    return ensureWrappers(type, eventConsumer).getWrapper().type;
-  }
-
-  private DexType ensureVivifiedTypeWrapper(
-      DexType type, DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
-    return ensureWrappers(type, eventConsumer).getVivifiedWrapper().type;
-  }
-
   private DexClass getValidClassToWrap(DexType type) {
     DexClass dexClass = appView.definitionFor(type);
     // The dexClass should be a library class, so it cannot be null.
@@ -188,102 +210,78 @@
     return DesugaredLibraryAPIConverter.vivifiedTypeFor(type, appView);
   }
 
-  static class Wrappers {
-    private final DexClass wrapper;
-    private final DexClass vivifiedWrapper;
+  static class WrapperConversions {
 
-    Wrappers(DexClass wrapper, DexClass vivifiedWrapper) {
-      this.wrapper = wrapper;
-      this.vivifiedWrapper = vivifiedWrapper;
+    private final DexMethod conversion;
+    private final DexMethod vivifiedConversion;
+
+    WrapperConversions(DexMethod conversion, DexMethod vivifiedConversion) {
+      this.conversion = conversion;
+      this.vivifiedConversion = vivifiedConversion;
     }
 
-    public DexClass getWrapper() {
-      return wrapper;
+    public DexMethod getConversion() {
+      return conversion;
     }
 
-    public DexClass getVivifiedWrapper() {
-      return vivifiedWrapper;
+    public DexMethod getVivifiedConversion() {
+      return vivifiedConversion;
     }
   }
 
-  private Wrappers ensureWrappers(
-      DexType type, DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
-    assert canGenerateWrapper(type) : type;
-    DexClass dexClass = getValidClassToWrap(type);
-    return ensureWrappers(dexClass, ignored -> {}, eventConsumer);
-  }
-
-  private Wrappers ensureWrappers(
-      DexClass context,
-      Consumer<DexClasspathClass> creationCallback,
-      DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
-    DexType type = context.type;
-    DexClass wrapper;
-    DexClass vivifiedWrapper;
+  private WrapperConversions ensureWrappers(
+      DexClass context, DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer) {
+    assert eventConsumer != null;
     if (context.isProgramClass()) {
-      assert appView.options().isDesugaredLibraryCompilation();
-      DexProgramClass programContext = context.asProgramClass();
-      wrapper =
-          ensureProgramWrapper(
-              SyntheticKind.WRAPPER,
-              vivifiedTypeFor(type),
-              type,
-              programContext,
-              eventConsumer,
-              wrapperField ->
-                  synthesizeVirtualMethodsForTypeWrapper(
-                      programContext, wrapperField, eventConsumer));
-      vivifiedWrapper =
-          ensureProgramWrapper(
-              SyntheticKind.VIVIFIED_WRAPPER,
-              type,
-              vivifiedTypeFor(type),
-              programContext,
-              eventConsumer,
-              wrapperField ->
-                  synthesizeVirtualMethodsForVivifiedTypeWrapper(
-                      programContext, wrapperField, eventConsumer));
-      DexField wrapperField = getWrapperUniqueField(wrapper);
-      DexField vivifiedWrapperField = getWrapperUniqueField(vivifiedWrapper);
-      ensureProgramConversionMethod(
-          SyntheticKind.WRAPPER, programContext, wrapperField, vivifiedWrapperField);
-      ensureProgramConversionMethod(
-          SyntheticKind.VIVIFIED_WRAPPER, programContext, vivifiedWrapperField, wrapperField);
-    } else {
-      assert context.isNotProgramClass();
-      ClasspathOrLibraryClass classpathOrLibraryContext = context.asClasspathOrLibraryClass();
-      wrapper =
-          ensureClasspathWrapper(
-              SyntheticKind.WRAPPER,
-              vivifiedTypeFor(type),
-              type,
-              classpathOrLibraryContext,
-              creationCallback,
-              eventConsumer,
-              wrapperField ->
-                  synthesizeVirtualMethodsForTypeWrapper(context, wrapperField, eventConsumer));
-      vivifiedWrapper =
-          ensureClasspathWrapper(
-              SyntheticKind.VIVIFIED_WRAPPER,
-              type,
-              vivifiedTypeFor(type),
-              classpathOrLibraryContext,
-              creationCallback,
-              eventConsumer,
-              wrapperField ->
-                  synthesizeVirtualMethodsForVivifiedTypeWrapper(
-                      context, wrapperField, eventConsumer));
-      DexField wrapperField = getWrapperUniqueField(wrapper);
-      DexField vivifiedWrapperField = getWrapperUniqueField(vivifiedWrapper);
-      ensureClasspathConversionMethod(
-          SyntheticKind.WRAPPER, classpathOrLibraryContext, wrapperField, vivifiedWrapperField);
-      ensureClasspathConversionMethod(
-          SyntheticKind.VIVIFIED_WRAPPER,
-          classpathOrLibraryContext,
-          vivifiedWrapperField,
-          wrapperField);
+      return getExistingProgramWrapperConversions(context);
     }
-    return new Wrappers(wrapper, vivifiedWrapper);
+    assert context.isNotProgramClass();
+    ClasspathOrLibraryClass classpathOrLibraryContext = context.asClasspathOrLibraryClass();
+    DexType type = context.type;
+    DexType vivifiedType = vivifiedTypeFor(type);
+    DexClass wrapper =
+        ensureClasspathWrapper(
+            SyntheticKind.WRAPPER,
+            vivifiedType,
+            type,
+            classpathOrLibraryContext,
+            eventConsumer,
+            wrapperField -> synthesizeVirtualMethodsForTypeWrapper(context, wrapperField));
+    DexClass vivifiedWrapper =
+        ensureClasspathWrapper(
+            SyntheticKind.VIVIFIED_WRAPPER,
+            type,
+            vivifiedType,
+            classpathOrLibraryContext,
+            eventConsumer,
+            wrapperField -> synthesizeVirtualMethodsForVivifiedTypeWrapper(context, wrapperField));
+    return new WrapperConversions(
+        getConversion(wrapper, vivifiedType, type),
+        getConversion(vivifiedWrapper, type, vivifiedType));
+  }
+
+  private WrapperConversions getExistingProgramWrapperConversions(DexClass context) {
+    DexClass vivifiedWrapper;
+    DexClass wrapper;
+    assert appView.options().isDesugaredLibraryCompilation();
+    wrapper = getExistingProgramWrapper(context, SyntheticKind.WRAPPER);
+    vivifiedWrapper = getExistingProgramWrapper(context, SyntheticKind.VIVIFIED_WRAPPER);
+    DexField wrapperField = getWrapperUniqueField(wrapper);
+    DexField vivifiedWrapperField = getWrapperUniqueField(vivifiedWrapper);
+    return new WrapperConversions(
+        getConversion(wrapper, vivifiedWrapperField.type, wrapperField.type),
+        getConversion(vivifiedWrapper, wrapperField.type, vivifiedWrapperField.type));
+  }
+
+  private DexProgramClass getExistingProgramWrapper(DexClass context, SyntheticKind kind) {
+    return appView.getSyntheticItems().getExistingFixedClass(kind, context, appView);
+  }
+
+  private DexMethod getConversion(DexClass wrapper, DexType returnType, DexType argType) {
+    DexMethod convertMethod =
+        factory.createMethod(
+            wrapper.type, factory.createProto(returnType, argType), factory.convertMethodName);
+    return wrapper.lookupDirectMethod(convertMethod).getReference();
   }
 
   private DexEncodedField getWrapperUniqueEncodedField(DexClass wrapper) {
@@ -300,8 +298,9 @@
       DexType wrappingType,
       DexType wrappedType,
       DexProgramClass programContext,
-      DesugaredLibraryAPIConverterEventConsumer eventConsumer,
-      Function<DexEncodedField, DexEncodedMethod[]> virtualMethodProvider) {
+      DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer) {
+    assert appView.options().isDesugaredLibraryCompilation();
+    assert eventConsumer != null;
     return appView
         .getSyntheticItems()
         .ensureFixedClass(
@@ -311,13 +310,7 @@
             builder -> buildWrapper(wrappingType, wrappedType, programContext, builder),
             // The creation of virtual methods may require new wrappers, this needs to happen
             // once the wrapper is created to avoid infinite recursion.
-            wrapper -> {
-              if (eventConsumer != null) {
-                eventConsumer.acceptWrapperProgramClass(wrapper);
-              }
-              wrapper.setVirtualMethods(
-                  virtualMethodProvider.apply(getWrapperUniqueEncodedField(wrapper)));
-            });
+            eventConsumer::acceptWrapperProgramClass);
   }
 
   private DexClasspathClass ensureClasspathWrapper(
@@ -325,101 +318,84 @@
       DexType wrappingType,
       DexType wrappedType,
       ClasspathOrLibraryClass classpathOrLibraryContext,
-      Consumer<DexClasspathClass> creationCallback,
-      DesugaredLibraryAPIConverterEventConsumer eventConsumer,
-      Function<DexEncodedField, DexEncodedMethod[]> virtualMethodProvider) {
+      DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
+      Function<DexEncodedField, Collection<DexEncodedMethod>> virtualMethodProvider) {
+    assert eventConsumer != null;
     return appView
         .getSyntheticItems()
         .ensureFixedClasspathClass(
             kind,
             classpathOrLibraryContext,
             appView,
-            builder ->
-                buildWrapper(
-                    wrappingType, wrappedType, classpathOrLibraryContext.asDexClass(), builder),
-            // The creation of virtual methods may require new wrappers, this needs to happen
-            // once the wrapper is created to avoid infinite recursion.
-            wrapper -> {
-              if (eventConsumer != null) {
-                eventConsumer.acceptWrapperClasspathClass(wrapper);
-              }
-              wrapper.setVirtualMethods(
-                  virtualMethodProvider.apply(getWrapperUniqueEncodedField(wrapper)));
-              creationCallback.accept(wrapper);
-            });
+            builder -> {
+              DexEncodedField wrapperField =
+                  buildWrapper(
+                      wrappingType, wrappedType, classpathOrLibraryContext.asDexClass(), builder);
+              builder.addMethod(
+                  methodBuilder ->
+                      buildConversionMethod(
+                          methodBuilder, factory.createProto(wrappingType, wrappedType), null));
+              builder.setVirtualMethods(virtualMethodProvider.apply(wrapperField));
+            },
+            eventConsumer::acceptWrapperClasspathClass);
   }
 
-  private ProgramMethod ensureProgramConversionMethod(
-      SyntheticKind kind,
-      DexProgramClass context,
-      DexField wrapperField,
-      DexField reverseWrapperField) {
-    return appView
+  private void getExistingProgramConversionMethod(
+      SyntheticKind kind, DexProgramClass context, DexClass wrapper, DexClass reverseWrapper) {
+    DexField wrapperField = getWrapperUniqueField(wrapper);
+    DexField reverseWrapperField = getWrapperUniqueField(reverseWrapper);
+    DexProto proto = factory.createProto(reverseWrapperField.type, wrapperField.type);
+    appView
         .getSyntheticItems()
         .ensureFixedClassMethod(
             factory.convertMethodName,
-            factory.createProto(reverseWrapperField.type, wrapperField.type),
-            kind,
-            context,
-            appView,
-            ignored -> {},
-            methodBuilder ->
-                buildConversionMethod(methodBuilder, wrapperField, reverseWrapperField, context));
-  }
-
-  private DexClassAndMethod ensureClasspathConversionMethod(
-      SyntheticKind kind,
-      ClasspathOrLibraryClass context,
-      DexField wrapperField,
-      DexField reverseWrapperField) {
-    return appView
-        .getSyntheticItems()
-        .ensureFixedClasspathClassMethod(
-            factory.convertMethodName,
-            factory.createProto(reverseWrapperField.type, wrapperField.type),
+            proto,
             kind,
             context,
             appView,
             ignored -> {},
             methodBuilder ->
                 buildConversionMethod(
-                    methodBuilder, wrapperField, reverseWrapperField, context.asDexClass()));
+                    methodBuilder,
+                    proto,
+                    computeProgramConversionMethodCode(
+                        wrapperField, reverseWrapperField, context)));
   }
 
   private boolean isInvalidWrapper(DexClass clazz) {
     return Iterables.any(allImplementedMethods(clazz), DexEncodedMethod::isFinal);
   }
 
-  private void buildConversionMethod(
-      SyntheticMethodBuilder methodBuilder,
-      DexField wrapperField,
-      DexField reverseWrapperField,
-      DexClass context) {
-    CfCode cfCode;
+  private CfCode computeProgramConversionMethodCode(
+      DexField wrapperField, DexField reverseWrapperField, DexClass context) {
     if (isInvalidWrapper(context)) {
-      cfCode =
-          new APIConverterThrowRuntimeExceptionCfCodeProvider(
-                  appView,
-                  factory.createString(
-                      "Unsupported conversion for "
-                          + context.type
-                          + ". See compilation time warnings for more details."),
-                  wrapperField.holder)
-              .generateCfCode();
-    } else {
-      cfCode =
-          new APIConverterWrapperConversionCfCodeProvider(
-                  appView, reverseWrapperField, wrapperField)
-              .generateCfCode();
+      return new APIConverterThrowRuntimeExceptionCfCodeProvider(
+              appView,
+              factory.createString(
+                  "Unsupported conversion for "
+                      + context.type
+                      + ". See compilation time warnings for more details."),
+              wrapperField.holder)
+          .generateCfCode();
     }
+    assert context.isProgramClass();
+    return new APIConverterWrapperConversionCfCodeProvider(
+            appView, reverseWrapperField, wrapperField)
+        .generateCfCode();
+  }
+
+  private void buildConversionMethod(
+      SyntheticMethodBuilder methodBuilder, DexProto proto, CfCode cfCode) {
     methodBuilder
+        .setName(factory.convertMethodName)
+        .setProto(proto)
         .setAccessFlags(
             MethodAccessFlags.fromCfAccessFlags(
                 Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC, false))
         .setCode(methodSignature -> cfCode);
   }
 
-  private void buildWrapper(
+  private DexEncodedField buildWrapper(
       DexType wrappingType,
       DexType wrappedType,
       DexClass clazz,
@@ -435,6 +411,7 @@
         .setSuperType(superType)
         .setInstanceFields(Collections.singletonList(wrapperField))
         .addMethod(methodBuilder -> buildWrapperConstructor(wrapperField, methodBuilder));
+    return wrapperField;
   }
 
   private void buildWrapperConstructor(
@@ -451,10 +428,8 @@
                     .generateCfCode());
   }
 
-  private DexEncodedMethod[] synthesizeVirtualMethodsForVivifiedTypeWrapper(
-      DexClass dexClass,
-      DexEncodedField wrapperField,
-      DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
+  private Collection<DexEncodedMethod> synthesizeVirtualMethodsForVivifiedTypeWrapper(
+      DexClass dexClass, DexEncodedField wrapperField) {
     List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
     List<DexEncodedMethod> generatedMethods = new ArrayList<>();
     // Each method should use only types in their signature, but each method the wrapper forwards
@@ -489,16 +464,13 @@
       if (dexEncodedMethod.isFinal()) {
         finalMethods.add(dexEncodedMethod.getReference());
         continue;
-      } else {
+      } else if (dexClass.isProgramClass()) {
         cfCode =
             new APIConverterVivifiedWrapperCfCodeProvider(
-                    appView,
-                    methodToInstall,
-                    wrapperField.getReference(),
-                    this,
-                    isInterface,
-                    eventConsumer)
+                    appView, methodToInstall, wrapperField.getReference(), this, isInterface)
                 .generateCfCode();
+      } else {
+        cfCode = null;
       }
       DexEncodedMethod newDexEncodedMethod =
           newSynthesizedMethod(methodToInstall, dexEncodedMethod, cfCode);
@@ -507,10 +479,8 @@
     return finalizeWrapperMethods(generatedMethods, finalMethods);
   }
 
-  private DexEncodedMethod[] synthesizeVirtualMethodsForTypeWrapper(
-      DexClass dexClass,
-      DexEncodedField wrapperField,
-      DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
+  private Collection<DexEncodedMethod> synthesizeVirtualMethodsForTypeWrapper(
+      DexClass dexClass, DexEncodedField wrapperField) {
     List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
     List<DexEncodedMethod> generatedMethods = new ArrayList<>();
     // Each method should use only vivified types in their signature, but each method the wrapper
@@ -535,16 +505,17 @@
       if (dexEncodedMethod.isFinal()) {
         finalMethods.add(dexEncodedMethod.getReference());
         continue;
-      } else {
+      } else if (dexClass.isProgramClass()) {
         cfCode =
             new APIConverterWrapperCfCodeProvider(
                     appView,
                     dexEncodedMethod.getReference(),
                     wrapperField.getReference(),
                     this,
-                    isInterface,
-                    eventConsumer)
+                    isInterface)
                 .generateCfCode();
+      } else {
+        cfCode = null;
       }
       DexEncodedMethod newDexEncodedMethod =
           newSynthesizedMethod(methodToInstall, dexEncodedMethod, cfCode);
@@ -553,14 +524,14 @@
     return finalizeWrapperMethods(generatedMethods, finalMethods);
   }
 
-  private DexEncodedMethod[] finalizeWrapperMethods(
+  private Collection<DexEncodedMethod> finalizeWrapperMethods(
       List<DexEncodedMethod> generatedMethods, Set<DexMethod> finalMethods) {
     if (finalMethods.isEmpty()) {
-      return generatedMethods.toArray(DexEncodedMethod.EMPTY_ARRAY);
+      return generatedMethods;
     }
     // Wrapper is invalid, no need to add the virtual methods.
     reportFinalMethodsInWrapper(finalMethods);
-    return DexEncodedMethod.EMPTY_ARRAY;
+    return Collections.emptyList();
   }
 
   private void reportFinalMethodsInWrapper(Set<DexMethod> methods) {
@@ -582,9 +553,9 @@
       DexMethod methodToInstall, DexEncodedMethod template, Code code) {
     MethodAccessFlags newFlags = template.accessFlags.copy();
     assert newFlags.isPublic();
-    if (code == null) {
-      newFlags.setAbstract();
-    } else {
+    // It can happen that we wrap an abstract method, in which case the wrapping method is no
+    // longer abstract.
+    if (code != null) {
       newFlags.unsetAbstract();
     }
     // TODO(b/146114533): Fix inlining in synthetic methods and remove unsetBridge.
@@ -657,16 +628,63 @@
         field, fieldAccessFlags, FieldTypeSignature.noSignature(), DexAnnotationSet.empty(), null);
   }
 
-  void ensureWrappersForL8(DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
+  // Program wrappers are harder to deal with than classpath wrapper because generating a method's
+  // code may require other wrappers. To keep it simple (This is L8 specific), we generate first
+  // the wrappers with the conversion methods only, then the virtual methods assuming the
+  // conversion methods are present.
+  @Override
+  public void synthesizeClasses(CfL8ClassSynthesizerEventConsumer eventConsumer) {
     DesugaredLibraryConfiguration conf = appView.options().desugaredLibraryConfiguration;
+    List<DexProgramClass> validClassesToWrap = new ArrayList<>();
     for (DexType type : conf.getWrapperConversions()) {
       assert !conf.getCustomConversions().containsKey(type);
       DexClass validClassToWrap = getValidClassToWrap(type);
       // In broken set-ups we can end up having a json files containing wrappers of non desugared
       // classes. Such wrappers are not required since the class won't be rewritten.
       if (validClassToWrap.isProgramClass()) {
-        ensureWrappers(validClassToWrap, ignored -> {}, eventConsumer);
+        validClassesToWrap.add(validClassToWrap.asProgramClass());
+        ensureProgramWrappersWithoutVirtualMethods(validClassToWrap, eventConsumer);
       }
     }
+    for (DexProgramClass validClassToWrap : validClassesToWrap) {
+      ensureProgramWrappersVirtualMethods(validClassToWrap);
+    }
+  }
+
+  // We generate first the two wrappers with the constructor method and the fields, then we
+  // the two conversion methods which requires the wrappers to know both fields.
+  private void ensureProgramWrappersWithoutVirtualMethods(
+      DexClass context, DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer) {
+    assert eventConsumer != null;
+    assert context.isProgramClass();
+    DexType type = context.type;
+    assert appView.options().isDesugaredLibraryCompilation();
+    DexProgramClass programContext = context.asProgramClass();
+    DexClass wrapper =
+        ensureProgramWrapper(
+            SyntheticKind.WRAPPER, vivifiedTypeFor(type), type, programContext, eventConsumer);
+    DexClass vivifiedWrapper =
+        ensureProgramWrapper(
+            SyntheticKind.VIVIFIED_WRAPPER,
+            type,
+            vivifiedTypeFor(type),
+            programContext,
+            eventConsumer);
+    getExistingProgramConversionMethod(
+        SyntheticKind.WRAPPER, programContext, wrapper, vivifiedWrapper);
+    getExistingProgramConversionMethod(
+        SyntheticKind.VIVIFIED_WRAPPER, programContext, vivifiedWrapper, wrapper);
+  }
+
+  private void ensureProgramWrappersVirtualMethods(DexClass context) {
+    assert context.isProgramClass();
+    DexProgramClass wrapper = getExistingProgramWrapper(context, SyntheticKind.WRAPPER);
+    wrapper.addVirtualMethods(
+        synthesizeVirtualMethodsForTypeWrapper(context, getWrapperUniqueEncodedField(wrapper)));
+    DexProgramClass vivifiedWrapper =
+        getExistingProgramWrapper(context, SyntheticKind.VIVIFIED_WRAPPER);
+    vivifiedWrapper.addVirtualMethods(
+        synthesizeVirtualMethodsForVivifiedTypeWrapper(
+            context, getWrapperUniqueEncodedField(vivifiedWrapper)));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizerEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizerEventConsumer.java
new file mode 100644
index 0000000..212ad66
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizerEventConsumer.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary;
+
+import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface DesugaredLibraryWrapperSynthesizerEventConsumer {
+
+  interface DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer {
+
+    void acceptWrapperProgramClass(DexProgramClass clazz);
+  }
+
+  interface DesugaredLibraryClasspathWrapperSynthesizeEventConsumer {
+
+    void acceptWrapperClasspathClass(DexClasspathClass clazz);
+  }
+
+  interface DesugaredLibraryAPIConverterEventConsumer
+      extends DesugaredLibraryClasspathWrapperSynthesizeEventConsumer {
+
+    void acceptAPIConversion(ProgramMethod method);
+  }
+
+  interface DesugaredLibraryAPICallbackSynthesizorEventConsumer
+      extends DesugaredLibraryClasspathWrapperSynthesizeEventConsumer {
+
+    void acceptAPIConversionCallback(ProgramMethod method);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceSynthesizer.java
similarity index 86%
rename from src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
rename to src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceSynthesizer.java
index c3fded8..5af4f7e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceSynthesizer.java
@@ -12,6 +12,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfL8ClassSynthesizer;
+import com.android.tools.r8.ir.desugar.CfL8ClassSynthesizerEventConsumer;
 import com.android.tools.r8.ir.synthetic.EmulateInterfaceSyntheticCfCodeProvider;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
 import com.android.tools.r8.synthesis.SyntheticNaming;
@@ -28,13 +30,21 @@
 import java.util.Map;
 import java.util.Set;
 
-public final class EmulatedInterfaceProcessor implements InterfaceDesugaringProcessor {
+public final class EmulatedInterfaceSynthesizer implements CfL8ClassSynthesizer {
 
   private final AppView<?> appView;
   private final InterfaceDesugaringSyntheticHelper helper;
   private final Map<DexType, List<DexType>> emulatedInterfacesHierarchy;
 
-  EmulatedInterfaceProcessor(AppView<?> appView) {
+  public static EmulatedInterfaceSynthesizer create(AppView<?> appView) {
+    if (!appView.options().isDesugaredLibraryCompilation()
+        || appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface().isEmpty()) {
+      return null;
+    }
+    return new EmulatedInterfaceSynthesizer(appView);
+  }
+
+  EmulatedInterfaceSynthesizer(AppView<?> appView) {
     this.appView = appView;
     helper = new InterfaceDesugaringSyntheticHelper(appView);
     // Avoid the computation outside L8 since it is not needed.
@@ -83,7 +93,7 @@
   }
 
   DexProgramClass ensureEmulateInterfaceLibrary(
-      DexProgramClass emulatedInterface, InterfaceProcessingDesugaringEventConsumer eventConsumer) {
+      DexProgramClass emulatedInterface, EmulatedInterfaceSynthesizerEventConsumer eventConsumer) {
     assert helper.isEmulatedInterface(emulatedInterface.type);
     DexProgramClass emulateInterfaceClass =
         appView
@@ -101,7 +111,7 @@
                                     synthesizeEmulatedInterfaceMethod(
                                         method, emulatedInterface, methodBuilder))),
                 ignored -> {});
-    emulateInterfaceClass.forEachProgramMethod(eventConsumer::acceptEmulatedInterfaceMethod);
+    eventConsumer.acceptEmulatedInterface(emulateInterfaceClass);
     assert emulateInterfaceClass.getType()
         == InterfaceDesugaringSyntheticHelper.getEmulateLibraryInterfaceClassType(
             emulatedInterface.type, appView.dexItemFactory());
@@ -209,15 +219,20 @@
   }
 
   @Override
-  public void process(
-      DexProgramClass emulatedInterface, InterfaceProcessingDesugaringEventConsumer eventConsumer) {
-    if (!appView.options().isDesugaredLibraryCompilation()
-        || !helper.isEmulatedInterface(emulatedInterface.type)
-        || appView.isAlreadyLibraryDesugared(emulatedInterface)) {
-      return;
-    }
-    if (needsEmulateInterfaceLibrary(emulatedInterface)) {
-      ensureEmulateInterfaceLibrary(emulatedInterface, eventConsumer);
+  public void synthesizeClasses(CfL8ClassSynthesizerEventConsumer eventConsumer) {
+    assert appView.options().isDesugaredLibraryCompilation();
+    for (DexType emulatedInterfaceType : helper.getEmulatedInterfaces()) {
+      DexClass emulatedInterfaceClazz = appView.definitionFor(emulatedInterfaceType);
+      if (emulatedInterfaceClazz == null || !emulatedInterfaceClazz.isProgramClass()) {
+        warnMissingEmulatedInterface(emulatedInterfaceType);
+        continue;
+      }
+      DexProgramClass emulatedInterface = emulatedInterfaceClazz.asProgramClass();
+      assert emulatedInterface != null;
+      if (!appView.isAlreadyLibraryDesugared(emulatedInterface)
+          && needsEmulateInterfaceLibrary(emulatedInterface)) {
+        ensureEmulateInterfaceLibrary(emulatedInterface, eventConsumer);
+      }
     }
   }
 
@@ -225,20 +240,6 @@
     return Iterables.any(emulatedInterface.methods(), DexEncodedMethod::isDefaultMethod);
   }
 
-  @Override
-  public void finalizeProcessing(InterfaceProcessingDesugaringEventConsumer eventConsumer) {
-    warnMissingEmulatedInterfaces();
-  }
-
-  private void warnMissingEmulatedInterfaces() {
-    for (DexType interfaceType : helper.getEmulatedInterfaces()) {
-      DexClass theInterface = appView.definitionFor(interfaceType);
-      if (theInterface == null) {
-        warnMissingEmulatedInterface(interfaceType);
-      }
-    }
-  }
-
   private void warnMissingEmulatedInterface(DexType interfaceType) {
     StringDiagnostic warning =
         new StringDiagnostic(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceSynthesizerEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceSynthesizerEventConsumer.java
new file mode 100644
index 0000000..cb73851
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceSynthesizerEventConsumer.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.itf;
+
+import com.android.tools.r8.graph.DexProgramClass;
+
+public interface EmulatedInterfaceSynthesizerEventConsumer {
+
+  void acceptEmulatedInterface(DexProgramClass clazz);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
index ad5aa5e..65816d4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
@@ -4,29 +4,56 @@
 
 package com.android.tools.r8.ir.desugar.itf;
 
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInitClass;
+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.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.ClasspathOrLibraryClass;
+import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.InvalidCode;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.structural.Ordered;
+import com.google.common.collect.ImmutableList;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
+import org.objectweb.asm.Opcodes;
 
 public class InterfaceDesugaringSyntheticHelper {
 
+  // Any interface method desugared code can be version 1.7 at the most.
+  // Note: we always desugar both default/static (v1.8) and private (v9) when targeting api < N.
+  public static final CfVersion MAX_INTERFACE_DESUGARED_CF_VERSION = CfVersion.V1_7;
+
+  public static CfVersion getInterfaceDesugaredCfVersion(CfVersion existing) {
+    return Ordered.min(existing, MAX_INTERFACE_DESUGARED_CF_VERSION);
+  }
+
   // Use InterfaceDesugaringForTesting for public accesses in tests.
   static final String EMULATE_LIBRARY_CLASS_NAME_SUFFIX = "$-EL";
   static final String COMPANION_CLASS_NAME_SUFFIX = "$-CC";
@@ -167,9 +194,11 @@
     return ensureMethodOfClasspathCompanionClassStub(companionMethodReference, context, appView);
   }
 
-  DexClassAndMethod ensureStaticAsMethodOfCompanionClassStub(DexClassAndMethod method) {
+  DexClassAndMethod ensureStaticAsMethodOfCompanionClassStub(
+      DexClassAndMethod method, Consumer<ProgramMethod> companionClinitConsumer) {
     if (method.isProgramMethod()) {
-      return ensureStaticAsMethodOfProgramCompanionClassStub(method.asProgramMethod());
+      return ensureStaticAsMethodOfProgramCompanionClassStub(
+          method.asProgramMethod(), companionClinitConsumer);
     } else {
       ClasspathOrLibraryClass context = method.getHolder().asClasspathOrLibraryClass();
       DexMethod companionMethodReference = staticAsMethodOfCompanionClass(method);
@@ -207,7 +236,8 @@
                               .getCode()
                               .getCodeAsInlining(syntheticMethod, method.getReference())
                           : InvalidCode.getInstance());
-        });
+        },
+        ignored -> {});
   }
 
   ProgramMethod ensurePrivateAsMethodOfProgramCompanionClassStub(ProgramMethod method) {
@@ -239,11 +269,12 @@
                               .getCode()
                               .getCodeAsInlining(syntheticMethod, method.getReference())
                           : InvalidCode.getInstance());
-        });
+        },
+        ignored -> {});
   }
 
   // Represent a static interface method as a method of companion class.
-  final DexMethod staticAsMethodOfCompanionClass(DexClassAndMethod method) {
+  private DexMethod staticAsMethodOfCompanionClass(DexClassAndMethod method) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     DexType companionClassType = getCompanionClassType(method.getHolderType(), dexItemFactory);
     DexMethod rewritten = method.getReference().withHolder(companionClassType, dexItemFactory);
@@ -298,7 +329,11 @@
                     .setCode(DexEncodedMethod::buildEmptyThrowingCfCode));
   }
 
-  ProgramMethod ensureStaticAsMethodOfProgramCompanionClassStub(ProgramMethod method) {
+  ProgramMethod ensureStaticAsMethodOfProgramCompanionClassStub(
+      ProgramMethod method, Consumer<ProgramMethod> companionClinitConsumer) {
+    if (!method.getDefinition().isClassInitializer() && method.getHolder().hasClassInitializer()) {
+      ensureCompanionClassInitializesInterface(method.getHolder(), companionClinitConsumer);
+    }
     DexMethod companionMethodReference = staticAsMethodOfCompanionClass(method);
     DexEncodedMethod definition = method.getDefinition();
     return InterfaceProcessor.ensureCompanionMethod(
@@ -323,7 +358,107 @@
                               .getCode()
                               .getCodeAsInlining(syntheticMethod, method.getReference())
                           : InvalidCode.getInstance());
-        });
+        },
+        ignored -> {});
+  }
+
+  private void ensureCompanionClassInitializesInterface(
+      DexProgramClass iface, Consumer<ProgramMethod> companionClinitConsumer) {
+    assert hasStaticMethodThatTriggersNonTrivialClassInitializer(iface);
+    InterfaceProcessor.ensureCompanionMethod(
+        iface,
+        appView.dexItemFactory().classConstructorMethodName,
+        appView.dexItemFactory().createProto(appView.dexItemFactory().voidType),
+        appView,
+        methodBuilder -> createCompanionClassInitializer(iface, methodBuilder),
+        companionClinitConsumer);
+  }
+
+  private DexEncodedField ensureStaticClinitFieldToTriggerInterfaceInitialization(
+      DexProgramClass iface) {
+    DexEncodedField clinitField =
+        findExistingStaticClinitFieldToTriggerInterfaceInitialization(iface);
+    if (clinitField == null) {
+      clinitField = createStaticClinitFieldToTriggerInterfaceInitialization(iface);
+      iface.appendStaticField(clinitField);
+    }
+    return clinitField;
+  }
+
+  private boolean hasStaticMethodThatTriggersNonTrivialClassInitializer(DexProgramClass iface) {
+    return iface.hasClassInitializer()
+        && iface
+            .getMethodCollection()
+            .hasDirectMethods(method -> method.isStatic() && !method.isClassInitializer());
+  }
+
+  private DexEncodedField findExistingStaticClinitFieldToTriggerInterfaceInitialization(
+      DexProgramClass iface) {
+    // Don't select a field that has been marked dead, since we'll assert later that these fields
+    // have been dead code eliminated.
+    for (DexEncodedField field :
+        iface.staticFields(field -> !field.isPrivate() && !field.getOptimizationInfo().isDead())) {
+      return field;
+    }
+    return null;
+  }
+
+  private DexEncodedField createStaticClinitFieldToTriggerInterfaceInitialization(
+      DexProgramClass iface) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    DexField clinitFieldReference =
+        dexItemFactory.createFreshFieldNameWithoutHolder(
+            iface.getType(),
+            dexItemFactory.intType,
+            "$desugar$clinit",
+            candidate -> iface.lookupField(candidate) == null);
+    return new DexEncodedField(
+        clinitFieldReference,
+        FieldAccessFlags.builder().setPackagePrivate().setStatic().setSynthetic().build(),
+        FieldTypeSignature.noSignature(),
+        DexAnnotationSet.empty(),
+        DexValueInt.DEFAULT);
+  }
+
+  private void createCompanionClassInitializer(
+      DexProgramClass iface, SyntheticMethodBuilder methodBuilder) {
+    methodBuilder
+        .setAccessFlags(
+            MethodAccessFlags.builder().setConstructor().setPackagePrivate().setStatic().build())
+        .setClassFileVersion(getInterfaceDesugaredCfVersion(iface.getInitialClassFileVersion()))
+        .setCode(
+            method -> {
+              if (appView.canUseInitClass()) {
+                return new CfCode(
+                    method.holder,
+                    1,
+                    0,
+                    ImmutableList.of(
+                        new CfInitClass(iface.getType()),
+                        new CfStackInstruction(Opcode.Pop),
+                        new CfReturnVoid()),
+                    ImmutableList.of(),
+                    ImmutableList.of());
+              }
+              DexEncodedField clinitField =
+                  ensureStaticClinitFieldToTriggerInterfaceInitialization(iface);
+              boolean isWide = clinitField.getType().isWideType();
+              return new CfCode(
+                  method.holder,
+                  isWide ? 2 : 1,
+                  0,
+                  ImmutableList.of(
+                      new CfFieldInstruction(
+                          Opcodes.GETSTATIC,
+                          clinitField.getReference(),
+                          clinitField.getReference()),
+                      isWide
+                          ? new CfStackInstruction(Opcode.Pop2)
+                          : new CfStackInstruction(Opcode.Pop),
+                      new CfReturnVoid()),
+                  ImmutableList.of(),
+                  ImmutableList.of());
+            });
   }
 
   private Predicate<DexType> getShouldIgnoreFromReportsPredicate(AppView<?> appView) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java
index 031e142..bdc8816 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java
@@ -12,5 +12,7 @@
 
   void acceptInvokeStaticInterfaceOutliningMethod(ProgramMethod method, ProgramMethod context);
 
+  void acceptCompanionClassClinit(ProgramMethod method);
+
   // TODO(b/183998768): Add acceptCompanionClass and acceptEmulatedInterface.
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java
index fafc920..f989858 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java
@@ -35,10 +35,6 @@
   private List<InterfaceDesugaringProcessor> instantiateInterfaceDesugaringProcessors(
       AppView<?> appView) {
 
-    // During L8 compilation, emulated interfaces are processed to be renamed, to have
-    // their interfaces fixed-up and to generate the emulated dispatch code.
-    EmulatedInterfaceProcessor emulatedInterfaceProcessor = new EmulatedInterfaceProcessor(appView);
-
     // Process all classes first. Add missing forwarding methods to
     // replace desugared default interface methods.
     ClassProcessor classProcessor = new ClassProcessor(appView);
@@ -50,7 +46,7 @@
     InterfaceProcessor interfaceProcessor = new InterfaceProcessor(appView);
 
     // The processors can be listed in any order.
-    return ImmutableList.of(classProcessor, interfaceProcessor, emulatedInterfaceProcessor);
+    return ImmutableList.of(classProcessor, interfaceProcessor);
   }
 
   /** Runs the interfaceProcessor, the class processor and the emulated interface processor. */
@@ -81,12 +77,6 @@
       sortedSynthesizedMethods.add(method);
     }
 
-    @Override
-    public void acceptEmulatedInterfaceMethod(ProgramMethod method) {
-
-      sortedSynthesizedMethods.add(method);
-    }
-
     public SortedProgramMethodSet getSortedSynthesizedMethods() {
       return sortedSynthesizedMethods;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index 38ad3e4..dfdfb45 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -464,7 +464,8 @@
           context,
           staticOutliningMethodConsumer,
           rewriteInvoke,
-          rewriteToThrow);
+          rewriteToThrow,
+          eventConsumer::acceptCompanionClassClinit);
     }
     assert invoke.isInvokeSpecial();
     if (invoke.isInvokeSuper(context.getHolderType())) {
@@ -665,7 +666,8 @@
             context,
             synthesizedMethods::add,
             rewriteInvoke,
-            rewriteToThrow);
+            rewriteToThrow,
+            synthesizedMethods::add);
       } else {
         assert instruction.isInvokeSuper();
         rewriteInvokeSuper(invoke.getInvokedMethod(), context, rewriteInvoke, rewriteToThrow);
@@ -761,7 +763,8 @@
       ProgramMethod context,
       Consumer<ProgramMethod> staticOutliningMethodConsumer,
       Function<DexMethod, Collection<CfInstruction>> rewriteInvoke,
-      Function<SingleResolutionResult, Collection<CfInstruction>> rewriteToThrow) {
+      Function<SingleResolutionResult, Collection<CfInstruction>> rewriteToThrow,
+      Consumer<ProgramMethod> companionClinitConsumer) {
     if (appView.getSyntheticItems().isPendingSynthetic(invokedMethod.holder)) {
       // We did not create this code yet, but it will not require rewriting.
       return null;
@@ -856,7 +859,8 @@
     assert resolutionResult.getResolvedMethod().isStatic();
     assert invokeNeedsRewriting(invokedMethod, STATIC);
     DexClassAndMethod companionMethod =
-        helper.ensureStaticAsMethodOfCompanionClassStub(resolutionResult.getResolutionPair());
+        helper.ensureStaticAsMethodOfCompanionClassStub(
+            resolutionResult.getResolutionPair(), companionClinitConsumer);
     return rewriteInvoke.apply(companionMethod.getReference());
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessingDesugaringEventConsumer.java
index 2581364..8fc50d1 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessingDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessingDesugaringEventConsumer.java
@@ -10,7 +10,6 @@
 
   void acceptForwardingMethod(ProgramMethod method);
 
+  // TODO(b/183998768): Remove this once interface desugaring is moved to the R8 enqueuer.
   void acceptCompanionClassClinit(ProgramMethod method);
-
-  void acceptEmulatedInterfaceMethod(ProgramMethod method);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
index c917520..1437d48 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -5,37 +5,25 @@
 package com.android.tools.r8.ir.desugar.itf;
 
 
-import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.cf.code.CfReturnVoid;
-import com.android.tools.r8.cf.code.CfStackInstruction;
-import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.InvokeSuper;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexValue.DexValueInt;
-import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
-import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InvalidCode;
-import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodCollection;
 import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -48,7 +36,6 @@
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
 import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap;
 import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
-import com.google.common.collect.ImmutableList;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Deque;
@@ -59,7 +46,6 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
-import org.objectweb.asm.Opcodes;
 
 // Default and static method interface desugaring processor for interfaces.
 //
@@ -101,12 +87,11 @@
 
   private void ensureCompanionClassMethods(
       DexProgramClass iface, InterfaceProcessingDesugaringEventConsumer eventConsumer) {
-    ensureCompanionClassInitializesInterface(iface, eventConsumer);
     // TODO(b/183998768): Once fixed, the methods should be added for processing.
     // D8 and R8 don't need to optimize the methods since they are just moved from interfaces and
     // don't need to be re-processed.
     processVirtualInterfaceMethods(iface);
-    processDirectInterfaceMethods(iface);
+    processDirectInterfaceMethods(iface, eventConsumer);
   }
 
   static ProgramMethod ensureCompanionMethod(
@@ -114,7 +99,8 @@
       DexString methodName,
       DexProto methodProto,
       AppView<?> appView,
-      Consumer<SyntheticMethodBuilder> fn) {
+      Consumer<SyntheticMethodBuilder> methodBuilderCallback,
+      Consumer<ProgramMethod> newMethodCallback) {
     return appView
         .getSyntheticItems()
         .ensureFixedClassMethod(
@@ -131,93 +117,8 @@
                             .getClassSignature()
                             .toObjectBoundWithSameFormals(
                                 new ClassTypeSignature(appView.dexItemFactory().objectType))),
-            fn);
-  }
-
-  private void ensureCompanionClassInitializesInterface(
-      DexProgramClass iface, InterfaceProcessingDesugaringEventConsumer eventConsumer) {
-    if (!hasStaticMethodThatTriggersNonTrivialClassInitializer(iface)) {
-      return;
-    }
-    DexEncodedField clinitField = ensureStaticClinitFieldToTriggerInterfaceInitialization(iface);
-    ProgramMethod clinit =
-        ensureCompanionMethod(
-            iface,
-            appView.dexItemFactory().classConstructorMethodName,
-            appView.dexItemFactory().createProto(appView.dexItemFactory().voidType),
-            appView,
-            methodBuilder -> createCompanionClassInitializer(iface, clinitField, methodBuilder));
-    eventConsumer.acceptCompanionClassClinit(clinit);
-  }
-
-  private DexEncodedField ensureStaticClinitFieldToTriggerInterfaceInitialization(
-      DexProgramClass iface) {
-    DexEncodedField clinitField =
-        findExistingStaticClinitFieldToTriggerInterfaceInitialization(iface);
-    if (clinitField == null) {
-      clinitField = createStaticClinitFieldToTriggerInterfaceInitialization(iface);
-      iface.appendStaticField(clinitField);
-    }
-    return clinitField;
-  }
-
-  private boolean hasStaticMethodThatTriggersNonTrivialClassInitializer(DexProgramClass iface) {
-    return iface.hasClassInitializer()
-        && iface
-            .getMethodCollection()
-            .hasDirectMethods(method -> method.isStatic() && !method.isClassInitializer());
-  }
-
-  private DexEncodedField findExistingStaticClinitFieldToTriggerInterfaceInitialization(
-      DexProgramClass iface) {
-    // Don't select a field that has been marked dead, since we'll assert later that these fields
-    // have been dead code eliminated.
-    for (DexEncodedField field :
-        iface.staticFields(field -> !field.isPrivate() && !field.getOptimizationInfo().isDead())) {
-      return field;
-    }
-    return null;
-  }
-
-  private DexEncodedField createStaticClinitFieldToTriggerInterfaceInitialization(
-      DexProgramClass iface) {
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    DexField clinitFieldReference =
-        dexItemFactory.createFreshFieldNameWithoutHolder(
-            iface.getType(),
-            dexItemFactory.intType,
-            "$desugar$clinit",
-            candidate -> iface.lookupField(candidate) == null);
-    return new DexEncodedField(
-        clinitFieldReference,
-        FieldAccessFlags.builder().setPackagePrivate().setStatic().setSynthetic().build(),
-        FieldTypeSignature.noSignature(),
-        DexAnnotationSet.empty(),
-        DexValueInt.DEFAULT);
-  }
-
-  private void createCompanionClassInitializer(
-      DexProgramClass iface, DexEncodedField clinitField, SyntheticMethodBuilder methodBuilder) {
-    SyntheticMethodBuilder.SyntheticCodeGenerator codeGenerator =
-        method ->
-            new CfCode(
-                method.holder,
-                clinitField.getType().isWideType() ? 2 : 1,
-                0,
-                ImmutableList.of(
-                    new CfFieldInstruction(
-                        Opcodes.GETSTATIC, clinitField.getReference(), clinitField.getReference()),
-                    clinitField.getType().isWideType()
-                        ? new CfStackInstruction(Opcode.Pop2)
-                        : new CfStackInstruction(Opcode.Pop),
-                    new CfReturnVoid()),
-                ImmutableList.of(),
-                ImmutableList.of());
-    methodBuilder
-        .setAccessFlags(
-            MethodAccessFlags.builder().setConstructor().setPackagePrivate().setStatic().build())
-        .setCode(codeGenerator)
-        .setClassFileVersion(iface.getInitialClassFileVersion());
+            methodBuilderCallback,
+            newMethodCallback);
   }
 
   private void processVirtualInterfaceMethods(DexProgramClass iface) {
@@ -248,7 +149,8 @@
     }
   }
 
-  private void processDirectInterfaceMethods(DexProgramClass iface) {
+  private void processDirectInterfaceMethods(
+      DexProgramClass iface, InterfaceProcessingDesugaringEventConsumer eventConsumer) {
     for (ProgramMethod method : iface.directProgramMethods()) {
       DexEncodedMethod definition = method.getDefinition();
       if (definition.isClassInitializer()) {
@@ -271,7 +173,9 @@
                 + " is expected to "
                 + "either be public or private in "
                 + iface.origin;
-        companion = helper.ensureStaticAsMethodOfProgramCompanionClassStub(method);
+        companion =
+            helper.ensureStaticAsMethodOfProgramCompanionClassStub(
+                method, eventConsumer::acceptCompanionClassClinit);
       } else {
         assert definition.isPrivate();
         Code code = definition.getCode();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/AccessBridgeFactory.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/AccessBridgeFactory.java
index 9266ad9..8814c2e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/AccessBridgeFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/AccessBridgeFactory.java
@@ -38,6 +38,8 @@
                     .build())
             .setMethod(bridgeMethodReference)
             .setD8R8Synthesized()
+            .setApiLevelForDefinition(field.getDefinition().getApiLevel())
+            .setApiLevelForCode(field.getDefinition().getApiLevel())
             .build());
   }
 
@@ -59,6 +61,8 @@
                     .build())
             .setMethod(bridgeMethodReference)
             .setD8R8Synthesized()
+            .setApiLevelForDefinition(method.getDefinition().getApiLevelForDefinition())
+            .setApiLevelForCode(method.getDefinition().getApiLevelForCode())
             .build());
   }
 
@@ -87,6 +91,8 @@
                         builder -> builder.setDirectTarget(method.getReference(), isInterface))
                     .build())
             .setMethod(bridgeMethodReference)
+            .setApiLevelForDefinition(method.getDefinition().getApiLevelForDefinition())
+            .setApiLevelForCode(method.getDefinition().getApiLevelForDefinition())
             .setD8R8Synthesized()
             .build());
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index a5ed9fb..2e74bb2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -33,7 +33,9 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.GraphLens;
@@ -61,6 +63,7 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeCustom;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
@@ -122,6 +125,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 
@@ -217,6 +221,13 @@
   }
 
   private DexProgramClass getEnumUnboxingCandidateOrNull(DexType type) {
+    if (type.isArrayType()) {
+      return getEnumUnboxingCandidateOrNull(type.toBaseType(appView.dexItemFactory()));
+    }
+    if (type.isPrimitiveType() || type.isVoidType()) {
+      return null;
+    }
+    assert type.isClassType();
     return enumUnboxingCandidatesInfo.getCandidateClassOrNull(type);
   }
 
@@ -253,6 +264,9 @@
           case Opcodes.CHECK_CAST:
             analyzeCheckCast(instruction.asCheckCast(), eligibleEnums);
             break;
+          case Opcodes.INVOKE_CUSTOM:
+            analyzeInvokeCustom(instruction.asInvokeCustom(), eligibleEnums);
+            break;
           case INVOKE_STATIC:
             analyzeInvokeStatic(instruction.asInvokeStatic(), eligibleEnums, code.context());
             break;
@@ -289,6 +303,49 @@
     }
   }
 
+  private void analyzeInvokeCustom(InvokeCustom invoke, Set<DexType> eligibleEnums) {
+    Consumer<DexType> typeReferenceConsumer =
+        type -> {
+          DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(type);
+          if (enumClass != null) {
+            eligibleEnums.add(enumClass.getType());
+          }
+        };
+    invoke.getCallSite().getMethodProto().forEachType(typeReferenceConsumer);
+    invoke
+        .getCallSite()
+        .getBootstrapArgs()
+        .forEach(
+            bootstrapArgument -> {
+              if (bootstrapArgument.isDexValueMethodHandle()) {
+                DexMethodHandle methodHandle =
+                    bootstrapArgument.asDexValueMethodHandle().getValue();
+                if (methodHandle.isMethodHandle()) {
+                  DexMethod method = methodHandle.asMethod();
+                  DexProgramClass enumClass =
+                      getEnumUnboxingCandidateOrNull(method.getHolderType());
+                  if (enumClass != null) {
+                    markEnumAsUnboxable(Reason.INVALID_INVOKE_CUSTOM, enumClass);
+                  } else {
+                    method.getProto().forEachType(typeReferenceConsumer);
+                  }
+                } else {
+                  assert methodHandle.isFieldHandle();
+                  DexField field = methodHandle.asField();
+                  DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(field.getHolderType());
+                  if (enumClass != null) {
+                    markEnumAsUnboxable(Reason.INVALID_INVOKE_CUSTOM, enumClass);
+                  } else {
+                    typeReferenceConsumer.accept(field.getType());
+                  }
+                }
+              } else if (bootstrapArgument.isDexValueMethodType()) {
+                DexProto proto = bootstrapArgument.asDexValueMethodType().getValue();
+                proto.forEachType(typeReferenceConsumer);
+              }
+            });
+  }
+
   private void analyzeFieldInstruction(
       FieldInstruction fieldInstruction, Set<DexType> eligibleEnums, ProgramMethod context) {
     DexField field = fieldInstruction.getField();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
index 7a95765..9614ede 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.enums;
 
-import static com.android.tools.r8.utils.AndroidApiLevelUtils.getApiLevelIfEnabledForNewMember;
+import static com.android.tools.r8.utils.AndroidApiLevel.minApiLevelIfEnabledOrUnknown;
 
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.cf.code.CfArrayStore;
@@ -48,7 +48,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
-import java.util.function.Function;
 import org.objectweb.asm.Opcodes;
 
 public class SharedEnumUnboxingUtilityClass extends EnumUnboxingUtilityClass {
@@ -238,7 +237,7 @@
               DexEncodedField.NO_STATIC_VALUE,
               DexEncodedField.NOT_DEPRECATED,
               DexEncodedField.D8_R8_SYNTHESIZED,
-              getApiLevelIfEnabledForNewMember(appView, Function.identity()));
+              minApiLevelIfEnabledOrUnknown(appView));
       fieldAccessInfoCollectionModifierBuilder
           .recordFieldReadInUnknownContext(valuesField.getReference())
           .recordFieldWriteInUnknownContext(valuesField.getReference());
@@ -258,8 +257,8 @@
           createClassInitializerCode(sharedUtilityClassType, valuesField),
           DexEncodedMethod.D8_R8_SYNTHESIZED,
           CfVersion.V1_6,
-          getApiLevelIfEnabledForNewMember(appView, Function.identity()),
-          getApiLevelIfEnabledForNewMember(appView, Function.identity()));
+          minApiLevelIfEnabledOrUnknown(appView),
+          minApiLevelIfEnabledOrUnknown(appView));
     }
 
     private CfCode createClassInitializerCode(
@@ -305,8 +304,8 @@
               createValuesMethodCode(sharedUtilityClassType, valuesField),
               DexEncodedMethod.D8_R8_SYNTHESIZED,
               CfVersion.V1_6,
-              getApiLevelIfEnabledForNewMember(appView, Function.identity()),
-              getApiLevelIfEnabledForNewMember(appView, Function.identity()));
+              minApiLevelIfEnabledOrUnknown(appView),
+              minApiLevelIfEnabledOrUnknown(appView));
       this.valuesMethod = valuesMethod;
       return valuesMethod;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
index 429bdae..bac0275 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
@@ -26,6 +26,7 @@
   public static final Reason INVALID_INVOKE = new StringReason("INVALID_INVOKE");
   public static final Reason INVALID_INVOKE_CLASSPATH =
       new StringReason("INVALID_INVOKE_CLASSPATH");
+  public static final Reason INVALID_INVOKE_CUSTOM = new StringReason("INVALID_INVOKE_CUSTOM");
   public static final Reason INVALID_INVOKE_ON_ARRAY = new StringReason("INVALID_INVOKE_ON_ARRAY");
   public static final Reason IMPLICIT_UP_CAST_IN_RETURN =
       new StringReason("IMPLICIT_UP_CAST_IN_RETURN");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
index 4fd2fcd..c790a02 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
-import com.android.tools.r8.utils.AndroidApiLevel;
 
 public class DefaultFieldOptimizationInfo extends FieldOptimizationInfo {
 
@@ -56,11 +55,6 @@
   }
 
   @Override
-  public AndroidApiLevel getApiReferenceLevelForDefinition(AndroidApiLevel minApi) {
-    throw new RuntimeException("Should never be called");
-  }
-
-  @Override
   public MutableFieldOptimizationInfo toMutableOptimizationInfo() {
     return new MutableFieldOptimizationInfo();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationWithMinApiInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationWithMinApiInfo.java
deleted file mode 100644
index a9c7462..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationWithMinApiInfo.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.optimize.info;
-
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-public class DefaultFieldOptimizationWithMinApiInfo extends DefaultFieldOptimizationInfo {
-
-  private static final DefaultFieldOptimizationWithMinApiInfo INSTANCE =
-      new DefaultFieldOptimizationWithMinApiInfo();
-
-  public static DefaultFieldOptimizationWithMinApiInfo getInstance() {
-    return INSTANCE;
-  }
-
-  @Override
-  public boolean hasApiReferenceLevelForDefinition() {
-    return true;
-  }
-
-  @Override
-  public AndroidApiLevel getApiReferenceLevelForDefinition(AndroidApiLevel minApi) {
-    return minApi;
-  }
-
-  @Override
-  public MutableFieldOptimizationInfo toMutableOptimizationInfo() {
-    MutableFieldOptimizationInfo updatableFieldOptimizationInfo = super.toMutableOptimizationInfo();
-    // Use null to specify that the min api is set to minApi.
-    updatableFieldOptimizationInfo.setMinApiReferenceLevel();
-    return updatableFieldOptimizationInfo;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 31ecb9b..24aeb3d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableSet;
 import java.util.BitSet;
@@ -196,26 +195,6 @@
   }
 
   @Override
-  public boolean hasApiReferenceLevelForCode() {
-    return false;
-  }
-
-  @Override
-  public AndroidApiLevel getApiReferenceLevelForCode(AndroidApiLevel minApi) {
-    throw new RuntimeException("Should never be called");
-  }
-
-  @Override
-  public AndroidApiLevel getApiReferenceLevelForDefinition(AndroidApiLevel minApi) {
-    throw new RuntimeException("Should never be called");
-  }
-
-  @Override
-  public boolean hasApiReferenceLevelForDefinition() {
-    return false;
-  }
-
-  @Override
   public MutableMethodOptimizationInfo toMutableOptimizationInfo() {
     return new MutableMethodOptimizationInfo();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationWithMinApiInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationWithMinApiInfo.java
deleted file mode 100644
index cd06dee..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationWithMinApiInfo.java
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.optimize.info;
-
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-public class DefaultMethodOptimizationWithMinApiInfo extends DefaultMethodOptimizationInfo {
-
-  private static final DefaultMethodOptimizationWithMinApiInfo DEFAULT_MIN_API_INSTANCE =
-      new DefaultMethodOptimizationWithMinApiInfo();
-
-  public static DefaultMethodOptimizationWithMinApiInfo getInstance() {
-    return DEFAULT_MIN_API_INSTANCE;
-  }
-
-  @Override
-  public boolean hasApiReferenceLevelForDefinition() {
-    return true;
-  }
-
-  @Override
-  public AndroidApiLevel getApiReferenceLevelForDefinition(AndroidApiLevel minApi) {
-    return minApi;
-  }
-
-  @Override
-  public boolean hasApiReferenceLevelForCode() {
-    return true;
-  }
-
-  @Override
-  public AndroidApiLevel getApiReferenceLevelForCode(AndroidApiLevel minApi) {
-    return minApi;
-  }
-
-  @Override
-  public MutableMethodOptimizationInfo toMutableOptimizationInfo() {
-    MutableMethodOptimizationInfo updatableMethodOptimizationInfo =
-        super.toMutableOptimizationInfo();
-    // Use null to specify that the min api is set to minApi.
-    updatableMethodOptimizationInfo.setMinApiReferenceLevel();
-    return updatableMethodOptimizationInfo;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MemberOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MemberOptimizationInfo.java
index 9cb8a2a..f5242e9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MemberOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MemberOptimizationInfo.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.info;
 
-import com.android.tools.r8.utils.AndroidApiLevel;
-
 public interface MemberOptimizationInfo<
     T extends MemberOptimizationInfo<T> & MutableOptimizationInfo> {
 
@@ -21,12 +19,6 @@
     return null;
   }
 
-  default boolean hasApiReferenceLevelForDefinition() {
-    return false;
-  }
-
-  AndroidApiLevel getApiReferenceLevelForDefinition(AndroidApiLevel minApi);
-
   T toMutableOptimizationInfo();
 
   default boolean isFieldOptimizationInfo() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index bb45fbc..aab9aa6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.BitSet;
 import java.util.Set;
@@ -101,10 +100,6 @@
 
   public abstract boolean returnValueHasBeenPropagated();
 
-  public abstract boolean hasApiReferenceLevelForCode();
-
-  public abstract AndroidApiLevel getApiReferenceLevelForCode(AndroidApiLevel minApi);
-
   @Override
   public boolean isMethodOptimizationInfo() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
index bdab6e8..9150ea0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
@@ -15,8 +15,6 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import java.util.Optional;
 import java.util.Set;
 
 /**
@@ -38,7 +36,6 @@
   private int readBits = 0;
   private ClassTypeElement dynamicLowerBoundType = null;
   private TypeElement dynamicUpperBoundType = null;
-  private Optional<AndroidApiLevel> apiReferenceLevel = null;
 
   public MutableFieldOptimizationInfo fixupClassTypeReferences(
       AppView<? extends AppInfoWithClassHierarchy> appView, GraphLens lens) {
@@ -69,7 +66,6 @@
   public MutableFieldOptimizationInfo mutableCopy() {
     MutableFieldOptimizationInfo copy = new MutableFieldOptimizationInfo();
     copy.flags = flags;
-    copy.apiReferenceLevel = apiReferenceLevel;
     return copy;
   }
 
@@ -154,29 +150,4 @@
   public MutableFieldOptimizationInfo asMutableFieldOptimizationInfo() {
     return this;
   }
-
-  @SuppressWarnings("OptionalAssignedToNull")
-  @Override
-  public boolean hasApiReferenceLevelForDefinition() {
-    return apiReferenceLevel != null;
-  }
-
-  @Override
-  public AndroidApiLevel getApiReferenceLevelForDefinition(AndroidApiLevel minApi) {
-    assert hasApiReferenceLevelForDefinition();
-    return apiReferenceLevel.orElse(minApi);
-  }
-
-  @Override
-  @SuppressWarnings("OptionalAssignedToNull")
-  public void setMinApiReferenceLevel() {
-    assert apiReferenceLevel == null;
-    this.apiReferenceLevel = Optional.empty();
-  }
-
-  @Override
-  public void setApiReferenceLevelForDefinition(AndroidApiLevel apiReferenceLevel) {
-    assert apiReferenceLevel != null;
-    this.apiReferenceLevel = Optional.of(apiReferenceLevel);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index 54184d78..3efe7f6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -25,11 +25,9 @@
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.BitSet;
-import java.util.Optional;
 import java.util.Set;
 
 public class MutableMethodOptimizationInfo extends MethodOptimizationInfo
@@ -75,9 +73,6 @@
   private SimpleInliningConstraint simpleInliningConstraint =
       NeverSimpleInliningConstraint.getInstance();
 
-  private Optional<AndroidApiLevel> codeApiReferenceLevel = null;
-  private Optional<AndroidApiLevel> definitionApiReferenceLevel = null;
-
   // To reduce the memory footprint of UpdatableMethodOptimizationInfo, all the boolean fields are
   // merged into a flag int field. The various static final FLAG fields indicate which bit is
   // used by each boolean. DEFAULT_FLAGS encodes the default value for efficient instantiation and
@@ -159,8 +154,6 @@
     nonNullParamOnNormalExits = template.nonNullParamOnNormalExits;
     classInlinerConstraint = template.classInlinerConstraint;
     enumUnboxerMethodClassification = template.enumUnboxerMethodClassification;
-    definitionApiReferenceLevel = template.definitionApiReferenceLevel;
-    codeApiReferenceLevel = template.codeApiReferenceLevel;
   }
 
   public MutableMethodOptimizationInfo fixupClassTypeReferences(
@@ -536,50 +529,6 @@
   }
 
   @Override
-  public AndroidApiLevel getApiReferenceLevelForDefinition(AndroidApiLevel minApi) {
-    assert hasApiReferenceLevelForDefinition();
-    return definitionApiReferenceLevel.orElse(minApi);
-  }
-
-  @SuppressWarnings("OptionalAssignedToNull")
-  @Override
-  public boolean hasApiReferenceLevelForDefinition() {
-    return definitionApiReferenceLevel != null;
-  }
-
-  @Override
-  @SuppressWarnings("OptionalAssignedToNull")
-  public boolean hasApiReferenceLevelForCode() {
-    return codeApiReferenceLevel != null;
-  }
-
-  @Override
-  public AndroidApiLevel getApiReferenceLevelForCode(AndroidApiLevel minApi) {
-    assert hasApiReferenceLevelForCode();
-    return codeApiReferenceLevel.orElse(minApi);
-  }
-
-  @Override
-  @SuppressWarnings("OptionalAssignedToNull")
-  public void setMinApiReferenceLevel() {
-    assert codeApiReferenceLevel == null;
-    assert definitionApiReferenceLevel == null;
-    this.codeApiReferenceLevel = Optional.empty();
-    this.definitionApiReferenceLevel = Optional.empty();
-  }
-
-  public void setApiReferenceLevelForCode(AndroidApiLevel apiLevel) {
-    assert apiLevel != null;
-    this.codeApiReferenceLevel = Optional.of(apiLevel);
-  }
-
-  @Override
-  public void setApiReferenceLevelForDefinition(AndroidApiLevel apiLevel) {
-    assert apiLevel != null;
-    this.definitionApiReferenceLevel = Optional.of(apiLevel);
-  }
-
-  @Override
   public boolean isMutableOptimizationInfo() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableOptimizationInfo.java
index 421f689..58f5470 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableOptimizationInfo.java
@@ -4,11 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.info;
 
-import com.android.tools.r8.utils.AndroidApiLevel;
-
 public interface MutableOptimizationInfo {
 
-  void setMinApiReferenceLevel();
-
-  void setApiReferenceLevelForDefinition(AndroidApiLevel apiLevel);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
index 4b7cffb..1e518d6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
@@ -177,11 +177,14 @@
       instructionIterator.removeOrReplaceByDebugLocalRead();
     } else if (inValue.isAlwaysNull(appView)) {
       if (singleTarget.getReference() == objectsMethods.requireNonNullElse) {
-        // Optimize Objects.requireNonNullElse(null, defaultObj) into defaultObj.
-        if (invoke.hasOutValue()) {
-          invoke.outValue().replaceUsers(invoke.getLastArgument(), affectedValues);
+        // Optimize Objects.requireNonNullElse(null, defaultObj) into defaultObj if defaultObj
+        // is never null.
+        if (invoke.getLastArgument().isNeverNull()) {
+          if (invoke.hasOutValue()) {
+            invoke.outValue().replaceUsers(invoke.getLastArgument(), affectedValues);
+          }
+          instructionIterator.removeOrReplaceByDebugLocalRead();
         }
-        instructionIterator.removeOrReplaceByDebugLocalRead();
       } else if (singleTarget.getReference() == objectsMethods.requireNonNullElseGet) {
         // Don't optimize Objects.requireNonNullElseGet. The result of calling supplier.get() still
         // needs a null-check, so two invokes will be needed.
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
index 4ce1fb6..7395cbf 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
@@ -32,8 +32,8 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverterEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.collections.ImmutableDeque;
 import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
@@ -58,21 +58,18 @@
     private final DexMethod forwardMethod;
     private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizer;
     private final boolean itfCall;
-    private final DesugaredLibraryAPIConverterEventConsumer eventConsumer;
 
     public APIConverterVivifiedWrapperCfCodeProvider(
         AppView<?> appView,
         DexMethod forwardMethod,
         DexField wrapperField,
         DesugaredLibraryWrapperSynthesizer wrapperSynthesizer,
-        boolean itfCall,
-        DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
+        boolean itfCall) {
       super(appView, wrapperField.holder);
       this.forwardMethod = forwardMethod;
       this.wrapperField = wrapperField;
       this.wrapperSynthesizer = wrapperSynthesizer;
       this.itfCall = itfCall;
-      this.eventConsumer = eventConsumer;
     }
 
     @Override
@@ -93,8 +90,8 @@
           instructions.add(
               new CfInvoke(
                   Opcodes.INVOKESTATIC,
-                  wrapperSynthesizer.ensureConversionMethod(
-                      param, param, vivifiedTypeFor(param), eventConsumer),
+                  wrapperSynthesizer.getExistingProgramConversionMethod(
+                      param, param, vivifiedTypeFor(param)),
                   false));
           newParameters[index - 1] = vivifiedTypeFor(param);
         }
@@ -125,8 +122,8 @@
         instructions.add(
             new CfInvoke(
                 Opcodes.INVOKESTATIC,
-                wrapperSynthesizer.ensureConversionMethod(
-                    returnType, vivifiedTypeFor(returnType), returnType, eventConsumer),
+                wrapperSynthesizer.getExistingProgramConversionMethod(
+                    returnType, vivifiedTypeFor(returnType), returnType),
                 false));
       }
       if (returnType == factory.voidType) {
@@ -138,31 +135,29 @@
     }
   }
 
-  public static class APIConverterWrapperCfCodeProvider
+  public abstract static class AbstractAPIConverterWrapperCfCodeProvider
       extends DesugaredLibraryAPIConversionCfCodeProvider {
 
-    DexField wrapperField;
     DexMethod forwardMethod;
     DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
     boolean itfCall;
-    private final DesugaredLibraryAPIConverterEventConsumer eventConsumer;
 
-    public APIConverterWrapperCfCodeProvider(
+    public AbstractAPIConverterWrapperCfCodeProvider(
         AppView<?> appView,
+        DexType holder,
         DexMethod forwardMethod,
-        DexField wrapperField,
         DesugaredLibraryWrapperSynthesizer wrapperSynthesizor,
-        boolean itfCall,
-        DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
-      //  Var wrapperField is null if should forward to receiver.
-      super(appView, wrapperField == null ? forwardMethod.holder : wrapperField.holder);
+        boolean itfCall) {
+      super(appView, holder);
       this.forwardMethod = forwardMethod;
-      this.wrapperField = wrapperField;
       this.wrapperSynthesizor = wrapperSynthesizor;
       this.itfCall = itfCall;
-      this.eventConsumer = eventConsumer;
     }
 
+    abstract void generatePushReceiver(List<CfInstruction> instructions);
+
+    abstract DexMethod ensureConversionMethod(DexType type, DexType srcType, DexType destType);
+
     @Override
     public CfCode generateCfCode() {
       DexItemFactory factory = appView.dexItemFactory();
@@ -170,13 +165,7 @@
       // Wrapped value is a type. Method uses vivifiedTypes as external. Forward method should
       // use types.
 
-      // Var wrapperField is null if should forward to receiver.
-      if (wrapperField == null) {
-        instructions.add(new CfLoad(ValueType.fromDexType(forwardMethod.holder), 0));
-      } else {
-        instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
-        instructions.add(new CfFieldInstruction(Opcodes.GETFIELD, wrapperField, wrapperField));
-      }
+      generatePushReceiver(instructions);
       int stackIndex = 1;
       for (DexType param : forwardMethod.proto.parameters.values) {
         instructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex));
@@ -184,8 +173,7 @@
           instructions.add(
               new CfInvoke(
                   Opcodes.INVOKESTATIC,
-                  wrapperSynthesizor.ensureConversionMethod(
-                      param, vivifiedTypeFor(param), param, eventConsumer),
+                  ensureConversionMethod(param, vivifiedTypeFor(param), param),
                   false));
         }
         if (param == factory.longType || param == factory.doubleType) {
@@ -205,8 +193,7 @@
         instructions.add(
             new CfInvoke(
                 Opcodes.INVOKESTATIC,
-                wrapperSynthesizor.ensureConversionMethod(
-                    returnType, returnType, vivifiedTypeFor(returnType), eventConsumer),
+                ensureConversionMethod(returnType, returnType, vivifiedTypeFor(returnType)),
                 false));
         returnType = vivifiedTypeFor(returnType);
       }
@@ -219,6 +206,59 @@
     }
   }
 
+  public static class APICallbackWrapperCfCodeProvider
+      extends AbstractAPIConverterWrapperCfCodeProvider {
+
+    private final DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer;
+
+    public APICallbackWrapperCfCodeProvider(
+        AppView<?> appView,
+        DexMethod forwardMethod,
+        DesugaredLibraryWrapperSynthesizer wrapperSynthesizor,
+        boolean itfCall,
+        DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer) {
+      super(appView, forwardMethod.holder, forwardMethod, wrapperSynthesizor, itfCall);
+      this.eventConsumer = eventConsumer;
+    }
+
+    @Override
+    void generatePushReceiver(List<CfInstruction> instructions) {
+      instructions.add(new CfLoad(ValueType.fromDexType(forwardMethod.holder), 0));
+    }
+
+    @Override
+    DexMethod ensureConversionMethod(DexType type, DexType srcType, DexType destType) {
+      return wrapperSynthesizor.ensureConversionMethod(type, srcType, destType, eventConsumer);
+    }
+  }
+
+  public static class APIConverterWrapperCfCodeProvider
+      extends AbstractAPIConverterWrapperCfCodeProvider {
+
+    private final DexField wrapperField;
+
+    public APIConverterWrapperCfCodeProvider(
+        AppView<?> appView,
+        DexMethod forwardMethod,
+        DexField wrapperField,
+        DesugaredLibraryWrapperSynthesizer wrapperSynthesizor,
+        boolean itfCall) {
+      super(appView, wrapperField.holder, forwardMethod, wrapperSynthesizor, itfCall);
+      this.wrapperField = wrapperField;
+    }
+
+    @Override
+    void generatePushReceiver(List<CfInstruction> instructions) {
+      instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
+      instructions.add(new CfFieldInstruction(Opcodes.GETFIELD, wrapperField, wrapperField));
+    }
+
+    @Override
+    DexMethod ensureConversionMethod(DexType type, DexType srcType, DexType destType) {
+      return wrapperSynthesizor.getExistingProgramConversionMethod(type, srcType, destType);
+    }
+  }
+
   public static class APIConverterWrapperConversionCfCodeProvider extends SyntheticCfCodeProvider {
 
     DexField reverseWrapperField;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java
index 373a5d2..6f34ad1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java
@@ -68,7 +68,7 @@
 
     private static KotlinAnnotationClassValueInfo create(KClassValue arg, DexItemFactory factory) {
       return new KotlinAnnotationClassValueInfo(
-          KotlinTypeReference.fromBinaryName(arg.getClassName(), factory),
+          KotlinTypeReference.fromBinaryName(arg.getClassName(), factory, arg.getClassName()),
           arg.getArrayDimensionCount());
     }
 
@@ -100,7 +100,8 @@
 
     private static KotlinAnnotationEnumValueInfo create(EnumValue arg, DexItemFactory factory) {
       return new KotlinAnnotationEnumValueInfo(
-          KotlinTypeReference.fromBinaryName(arg.getEnumClassName(), factory),
+          KotlinTypeReference.fromBinaryName(
+              arg.getEnumClassName(), factory, arg.getEnumClassName()),
           arg.getEnumEntryName());
     }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
index cbdd76e..d21cfb1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
@@ -34,7 +34,8 @@
 
   static KotlinAnnotationInfo create(KmAnnotation annotation, DexItemFactory factory) {
     return new KotlinAnnotationInfo(
-        KotlinTypeReference.fromBinaryName(annotation.getClassName(), factory),
+        KotlinTypeReference.fromBinaryName(
+            annotation.getClassName(), factory, annotation.getClassName()),
         KotlinAnnotationArgumentInfo.create(annotation.getArguments(), factory));
   }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
index aa3731f..9e7d703 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -187,7 +187,8 @@
       KmClass kmClass, DexItemFactory factory) {
     String anonymousObjectOriginName = JvmExtensionsKt.getAnonymousObjectOriginName(kmClass);
     if (anonymousObjectOriginName != null) {
-      return KotlinTypeReference.fromBinaryName(anonymousObjectOriginName, factory);
+      return KotlinTypeReference.fromBinaryName(
+          anonymousObjectOriginName, factory, anonymousObjectOriginName);
     }
     return null;
   }
@@ -198,7 +199,7 @@
     for (String nestedClass : nestedClasses) {
       String binaryName =
           clazz.type.toBinaryName() + DescriptorUtils.INNER_CLASS_SEPARATOR + nestedClass;
-      nestedTypes.add(KotlinTypeReference.fromBinaryName(binaryName, factory));
+      nestedTypes.add(KotlinTypeReference.fromBinaryName(binaryName, factory, nestedClass));
     }
     return nestedTypes.build();
   }
@@ -210,7 +211,7 @@
       String binaryName =
           sealedSubClass.replace(
               DescriptorUtils.JAVA_PACKAGE_SEPARATOR, DescriptorUtils.INNER_CLASS_SEPARATOR);
-      sealedTypes.add(KotlinTypeReference.fromBinaryName(binaryName, factory));
+      sealedTypes.add(KotlinTypeReference.fromBinaryName(binaryName, factory, sealedSubClass));
     }
     return sealedTypes.build();
   }
@@ -326,30 +327,31 @@
     }
     // Rewrite nested classes.
     for (KotlinTypeReference nestedClass : nestedClasses) {
-      rewritten |=
+      Box<String> nestedDescriptorBox = new Box<>();
+      boolean nestedClassRewritten =
           nestedClass.toRenamedBinaryNameOrDefault(
-              nestedDescriptor -> {
-                if (nestedDescriptor != null) {
-                  // If the class is a nested class, it should be on the form Foo.Bar$Baz, where Baz
-                  // is the
-                  // name we should record.
-                  int innerClassIndex =
-                      nestedDescriptor.lastIndexOf(DescriptorUtils.INNER_CLASS_SEPARATOR);
-                  kmClass.visitNestedClass(nestedDescriptor.substring(innerClassIndex + 1));
-                }
-              },
-              appView,
-              namingLens,
-              null);
+              nestedDescriptorBox::set, appView, namingLens, null);
+      if (nestedDescriptorBox.isSet()) {
+        if (nestedClassRewritten) {
+          // If the class is a nested class, it should be on the form Foo.Bar$Baz, where Baz
+          // is the name we should record.
+          String nestedDescriptor = nestedDescriptorBox.get();
+          int innerClassIndex = nestedDescriptor.lastIndexOf(DescriptorUtils.INNER_CLASS_SEPARATOR);
+          kmClass.visitNestedClass(nestedDescriptor.substring(innerClassIndex + 1));
+        } else {
+          kmClass.visitNestedClass(nestedClass.getOriginalName());
+        }
+      }
+      rewritten |= nestedClassRewritten;
     }
     // Rewrite sealed sub classes.
     for (KotlinTypeReference sealedSubClass : sealedSubClasses) {
       rewritten |=
           sealedSubClass.toRenamedBinaryNameOrDefault(
-              sealedDescriptor -> {
-                if (sealedDescriptor != null) {
+              sealedName -> {
+                if (sealedName != null) {
                   kmClass.visitSealedSubclass(
-                      sealedDescriptor.replace(
+                      sealedName.replace(
                           DescriptorUtils.INNER_CLASS_SEPARATOR,
                           DescriptorUtils.JAVA_PACKAGE_SEPARATOR));
                 }
@@ -409,7 +411,7 @@
     declarationContainerInfo.trace(definitionSupplier);
     forEachApply(typeParameters, param -> param::trace, definitionSupplier);
     forEachApply(superTypes, type -> type::trace, definitionSupplier);
-    forEachApply(sealedSubClasses, sealed -> sealed::trace, definitionSupplier);
+    forEachApply(sealedSubClasses, sealedClass -> sealedClass::trace, definitionSupplier);
     forEachApply(nestedClasses, nested -> nested::trace, definitionSupplier);
     localDelegatedProperties.trace(definitionSupplier);
     // TODO(b/154347404): trace enum entries.
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
index e6a249c..5001c40 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
@@ -106,7 +106,8 @@
       KmFunction kmFunction, DexItemFactory factory) {
     String lambdaClassOriginName = JvmExtensionsKt.getLambdaClassOriginName(kmFunction);
     if (lambdaClassOriginName != null) {
-      return KotlinTypeReference.fromBinaryName(lambdaClassOriginName, factory);
+      return KotlinTypeReference.fromBinaryName(
+          lambdaClassOriginName, factory, lambdaClassOriginName);
     }
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
index 0ff0070..fb6575c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -182,6 +182,13 @@
               writeMetadataFieldInfo);
       clazz.setAnnotations(clazz.annotations().rewrite(anno -> anno == oldMeta ? newMeta : anno));
     } catch (Throwable t) {
+      assert appView.checkForTesting(
+          () -> {
+            throw appView
+                .options()
+                .reporter
+                .fatalError(KotlinMetadataDiagnostic.unexpectedErrorWhenRewriting(clazz.type, t));
+          });
       appView
           .options()
           .reporter
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
index 7088c7f..0545b20 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
@@ -40,7 +40,7 @@
       DexItemFactory factory) {
     ImmutableList.Builder<KotlinTypeReference> builder = ImmutableList.builder();
     for (String partClassName : kmMultiFileClassFacade.getPartClassNames()) {
-      builder.add(KotlinTypeReference.fromBinaryName(partClassName, factory));
+      builder.add(KotlinTypeReference.fromBinaryName(partClassName, factory, partClassName));
     }
     return new KotlinMultiFileClassFacadeInfo(builder.build(), packageName, metadataVersion);
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java
index c166103..8cea528 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java
@@ -24,28 +24,33 @@
 class KotlinTypeReference implements EnqueuerMetadataTraceable {
 
   private final DexType known;
-  private final String unknown;
+  private final String originalName;
 
-  private KotlinTypeReference(DexType known) {
+  private KotlinTypeReference(String originalName, DexType known) {
+    this.originalName = originalName;
     this.known = known;
-    this.unknown = null;
     assert known != null;
   }
 
-  private KotlinTypeReference(String unknown) {
+  private KotlinTypeReference(String originalName) {
     this.known = null;
-    this.unknown = unknown;
-    assert unknown != null;
+    this.originalName = originalName;
+    assert originalName != null;
   }
 
   public DexType getKnown() {
     return known;
   }
 
-  static KotlinTypeReference fromBinaryName(String binaryName, DexItemFactory factory) {
+  public String getOriginalName() {
+    return originalName;
+  }
+
+  static KotlinTypeReference fromBinaryName(
+      String binaryName, DexItemFactory factory, String originalName) {
     if (DescriptorUtils.isValidBinaryName(binaryName)) {
       return fromDescriptor(
-          DescriptorUtils.getDescriptorFromClassBinaryName(binaryName), factory, binaryName);
+          DescriptorUtils.getDescriptorFromClassBinaryName(binaryName), factory, originalName);
     }
     return new KotlinTypeReference(binaryName);
   }
@@ -55,12 +60,12 @@
   }
 
   static KotlinTypeReference fromDescriptor(
-      String descriptor, DexItemFactory factory, String unknownValue) {
+      String descriptor, DexItemFactory factory, String originalName) {
     if (DescriptorUtils.isDescriptor(descriptor)) {
       DexType type = factory.createType(descriptor);
-      return new KotlinTypeReference(type);
+      return new KotlinTypeReference(originalName, type);
     }
-    return new KotlinTypeReference(unknownValue);
+    return new KotlinTypeReference(originalName);
   }
 
   boolean toRenamedDescriptorOrDefault(
@@ -68,11 +73,10 @@
       AppView<?> appView,
       NamingLens namingLens,
       String defaultValue) {
-    if (unknown != null) {
-      rewrittenConsumer.accept(unknown);
+    if (known == null) {
+      rewrittenConsumer.accept(originalName);
       return false;
     }
-    assert known != null;
     DexType rewrittenType = toRewrittenTypeOrNull(appView, known);
     if (rewrittenType == null) {
       rewrittenConsumer.accept(defaultValue);
@@ -85,7 +89,7 @@
 
   String toKotlinClassifier(boolean isLocalOrAnonymous) {
     if (known == null) {
-      return unknown;
+      return originalName;
     }
     return getKotlinLocalOrAnonymousNameFromDescriptor(
         known.toDescriptorString(), isLocalOrAnonymous);
@@ -96,9 +100,9 @@
       AppView<?> appView,
       NamingLens namingLens,
       String defaultValue) {
-    if (unknown != null) {
+    if (known == null) {
       // Unknown values are always on the input form, so we can just return it.
-      rewrittenConsumer.accept(unknown);
+      rewrittenConsumer.accept(originalName);
       return false;
     }
     return toRenamedDescriptorOrDefault(
@@ -136,7 +140,7 @@
 
   @Override
   public String toString() {
-    return known != null ? known.descriptor.toString() : unknown;
+    return known != null ? known.descriptor.toString() : originalName;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index e53f285..ca54e1b 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -93,6 +93,10 @@
         registerClassAsUsed(clazz.type, descriptor);
       }
     }
+    appView
+        .appInfo()
+        .getMissingClasses()
+        .forEach(missingClass -> registerClassAsUsed(missingClass, missingClass.getDescriptor()));
     timing.end();
 
     timing.begin("rename-classes");
diff --git a/src/main/java/com/android/tools/r8/naming/MapVersion.java b/src/main/java/com/android/tools/r8/naming/MapVersion.java
index 7069351..9c9c8d3 100644
--- a/src/main/java/com/android/tools/r8/naming/MapVersion.java
+++ b/src/main/java/com/android/tools/r8/naming/MapVersion.java
@@ -6,10 +6,11 @@
 import com.android.tools.r8.utils.structural.Ordered;
 
 public enum MapVersion implements Ordered<MapVersion> {
-  MapVersionNone("none"),
-  MapVersionExperimental("experimental");
+  MAP_VERSION_NONE("none"),
+  MAP_VERSION_1_0("1.0"),
+  MAP_VERSION_EXPERIMENTAL("experimental");
 
-  public static final MapVersion STABLE = MapVersionNone;
+  public static final MapVersion STABLE = MAP_VERSION_1_0;
 
   private final String name;
 
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index f1e483f..52b63e0 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -90,7 +90,7 @@
   private int lineNo = 0;
   private int lineOffset = 0;
   private String line;
-  private MapVersion version = MapVersion.MapVersionNone;
+  private MapVersion version = MapVersion.MAP_VERSION_NONE;
 
   private int peekCodePoint() {
     return lineOffset < line.length() ? line.codePointAt(lineOffset) : '\n';
@@ -271,13 +271,13 @@
         info -> {
           MapVersionMappingInformation generatorInfo = info.asMetaInfMappingInformation();
           if (generatorInfo != null) {
-            if (generatorInfo.getMapVersion().equals(MapVersion.MapVersionExperimental)) {
+            if (generatorInfo.getMapVersion().equals(MapVersion.MAP_VERSION_EXPERIMENTAL)) {
               // A mapping file that is marked "experimental" will be treated as an unversioned
               // file if the compiler/tool is not explicitly running with experimental support.
               version =
                   allowExperimentalMapping
-                      ? MapVersion.MapVersionExperimental
-                      : MapVersion.MapVersionNone;
+                      ? MapVersion.MAP_VERSION_EXPERIMENTAL
+                      : MapVersion.MAP_VERSION_NONE;
             } else {
               version = generatorInfo.getMapVersion();
             }
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
index 8ac5e71..2d36290 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
@@ -104,7 +104,7 @@
     builder.append("# common_typos_disable" + "\n");
     // Emit the R8 specific map-file version.
     MapVersion mapVersion = options.getMapFileVersion();
-    if (mapVersion.isGreaterThan(MapVersion.MapVersionNone)) {
+    if (mapVersion.isGreaterThan(MapVersion.MAP_VERSION_NONE)) {
       builder
           .append("# ")
           .append(new MapVersionMappingInformation(mapVersion).serialize())
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/CompilerSynthesizedMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/CompilerSynthesizedMappingInformation.java
index 0c49e28..d26d9a8 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/CompilerSynthesizedMappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/CompilerSynthesizedMappingInformation.java
@@ -12,7 +12,7 @@
 
 public class CompilerSynthesizedMappingInformation extends MappingInformation {
 
-  public static final MapVersion SUPPORTED_VERSION = MapVersion.MapVersionExperimental;
+  public static final MapVersion SUPPORTED_VERSION = MapVersion.MAP_VERSION_1_0;
   public static final String ID = "com.android.tools.r8.synthesized";
 
   public static class Builder {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
index 13455ba..dbec312 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
@@ -80,10 +80,7 @@
     WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList(clazz);
     while (worklist.hasNext()) {
       DexProgramClass current = worklist.next();
-      immediateSubtypingInfo.forEachImmediateSuperClassMatching(
-          current,
-          (supertype, superclass) -> superclass != null && superclass.isProgramClass(),
-          (supertype, superclass) -> worklist.addIfNotSeen(superclass.asProgramClass()));
+      immediateSubtypingInfo.forEachImmediateProgramSuperClass(current, worklist::addIfNotSeen);
       worklist.addIfNotSeen(immediateSubtypingInfo.getSubclasses(current));
     }
     return worklist.getSeenSet();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java
index 9a478bd..36dae85 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java
@@ -137,12 +137,11 @@
           unoptimizableInterfaceMethods.computeIfAbsent(clazz, ignoreKey(Sets::newIdentityHashSet));
 
       // Add the unoptimizable interface methods from the parent interfaces.
-      immediateSubtypingInfo.forEachImmediateSuperClassMatching(
+      immediateSubtypingInfo.forEachImmediateProgramSuperClass(
           clazz,
-          (supertype, superclass) -> superclass != null && superclass.isProgramClass(),
-          (supertype, superclass) ->
+          superclass ->
               unoptimizableInterfaceMethodsForClass.addAll(
-                  unoptimizableInterfaceMethods.get(superclass.asProgramClass())));
+                  unoptimizableInterfaceMethods.get(superclass)));
 
       // Propagate the unoptimizable interface methods of this interface to all immediate
       // (non-interface) subclasses.
@@ -221,12 +220,9 @@
           unoptimizableMethods.computeIfAbsent(clazz, ignoreKey(DexMethodSignatureSet::create));
 
       // Add the unoptimizable methods from the parent classes.
-      immediateSubtypingInfo.forEachImmediateSuperClassMatching(
+      immediateSubtypingInfo.forEachImmediateProgramSuperClass(
           clazz,
-          (supertype, superclass) -> superclass != null && superclass.isProgramClass(),
-          (supertype, superclass) ->
-              unoptimizableMethodsForClass.addAll(
-                  unoptimizableMethods.get(superclass.asProgramClass())));
+          superclass -> unoptimizableMethodsForClass.addAll(unoptimizableMethods.get(superclass)));
 
       // Disable argument propagation for the unoptimizable methods of this class.
       clazz.forEachProgramVirtualMethod(
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
index 99a0bac..69cbb7b 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
@@ -82,12 +82,11 @@
       DexProgramClass interfaceDefinition) {
     // Join the state for all parent interfaces into a fresh state created for this interface.
     MethodStateCollectionBySignature interfaceState = MethodStateCollectionBySignature.create();
-    immediateSubtypingInfo.forEachImmediateSuperClassMatching(
+    immediateSubtypingInfo.forEachImmediateProgramSuperClass(
         interfaceDefinition,
-        (supertype, superclass) -> superclass != null && superclass.isProgramClass(),
-        (supertype, superclass) -> {
+        superclass -> {
           MethodStateCollectionBySignature implementedInterfaceState =
-              methodStatesToPropagate.get(superclass.asProgramClass());
+              methodStatesToPropagate.get(superclass);
           assert implementedInterfaceState != null;
           interfaceState.addMethodStates(appView, implementedInterfaceState);
         });
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
index 14c58f8..771a718 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
@@ -46,10 +46,8 @@
 
     PropagationState(DexProgramClass clazz) {
       // Join the argument information from each of the super types.
-      immediateSubtypingInfo.forEachImmediateSuperClassMatching(
-          clazz,
-          (supertype, superclass) -> superclass != null && superclass.isProgramClass(),
-          (supertype, superclass) -> addParentState(clazz, superclass.asProgramClass()));
+      immediateSubtypingInfo.forEachImmediateProgramSuperClass(
+          clazz, superclass -> addParentState(clazz, superclass));
     }
 
     // TODO(b/190154391): This currently copies the state of the superclass into its immediate
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
index 6621857..8e34c3f 100644
--- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -274,16 +274,25 @@
     }
 
     // Always repackage outer classes first, if any.
-    InnerClassAttribute innerClassAttribute = classToRepackage.getInnerClassAttributeForThisClass();
     DexProgramClass outerClass = null;
-    if (innerClassAttribute != null && innerClassAttribute.getOuter() != null) {
-      outerClass = asProgramClassOrNull(appView.definitionFor(innerClassAttribute.getOuter()));
-      if (outerClass != null) {
-        if (pkg.contains(outerClass)) {
-          processClass(outerClass, pkg, newPackageDescriptor, mappings);
-        } else {
-          outerClass = null;
-        }
+    if (classToRepackage.hasEnclosingMethodAttribute()) {
+      DexType enclosingClass = classToRepackage.getEnclosingMethodAttribute().getEnclosingClass();
+      if (enclosingClass != null) {
+        outerClass = asProgramClassOrNull(appView.definitionFor(enclosingClass));
+      }
+    }
+    if (outerClass == null) {
+      InnerClassAttribute innerClassAttribute =
+          classToRepackage.getInnerClassAttributeForThisClass();
+      if (innerClassAttribute != null && innerClassAttribute.getOuter() != null) {
+        outerClass = asProgramClassOrNull(appView.definitionFor(innerClassAttribute.getOuter()));
+      }
+    }
+    if (outerClass != null) {
+      if (pkg.contains(outerClass)) {
+        processClass(outerClass, pkg, newPackageDescriptor, mappings);
+      } else {
+        outerClass = null;
       }
     }
     mappings.put(
@@ -318,13 +327,13 @@
 
   public static class DefaultRepackagingConfiguration implements RepackagingConfiguration {
 
-    private final AppView<?> appView;
+    private final AppView<AppInfoWithLiveness> appView;
     private final DexItemFactory dexItemFactory;
     private final InternalOptions options;
     private final ProguardConfiguration proguardConfiguration;
     private final MinificationPackageNamingStrategy packageMinificationStrategy;
 
-    public DefaultRepackagingConfiguration(AppView<?> appView) {
+    public DefaultRepackagingConfiguration(AppView<AppInfoWithLiveness> appView) {
       this.appView = appView;
       this.dexItemFactory = appView.dexItemFactory();
       this.options = appView.options();
@@ -342,7 +351,7 @@
         return newPackageDescriptor;
       } else if (packageObfuscationMode.isMinification()) {
         assert !proguardConfiguration.hasApplyMappingFile();
-        // Always keep top-level classes since there packages can never be minified.
+        // Always keep top-level classes since their packages can never be minified.
         if (pkg.getPackageDescriptor().equals("")
             || proguardConfiguration.getKeepPackageNamesPatterns().matches(pkg)
             || mayHavePinnedPackagePrivateOrProtectedItem(pkg)) {
@@ -383,15 +392,21 @@
 
     private boolean mayHavePinnedPackagePrivateOrProtectedItem(ProgramPackage pkg) {
       // Go through all package classes and members to see if there is a pinned package-private
-      // item, in which case we cannot move it because there may be a reflective access to it.
+      // item, in which case we cannot move it because there could be an access to it from outside
+      // the program, which would be rewritten with -applymapping.
       for (DexProgramClass clazz : pkg.classesInPackage()) {
         if (clazz.getAccessFlags().isPackagePrivateOrProtected()
-            && appView.getKeepInfo().getClassInfo(clazz).isPinned(options)) {
+            && !appView.getKeepInfo().getClassInfo(clazz).isShrinkingAllowed(options)) {
           return true;
         }
         for (DexEncodedMember<?, ?> member : clazz.members()) {
+          // Skip the class initializer. Even if it is kept, it cannot be invoked, and thus we don't
+          // need any special handling to make sure it is always accessible to callers from tests.
+          if (member.isDexEncodedMethod() && member.asDexEncodedMethod().isClassInitializer()) {
+            continue;
+          }
           if (member.getAccessFlags().isPackagePrivateOrProtected()
-              && appView.getKeepInfo().getMemberInfo(member, clazz).isPinned(options)) {
+              && !appView.getKeepInfo().getMemberInfo(member, clazz).isShrinkingAllowed(options)) {
             return true;
           }
         }
@@ -427,17 +442,19 @@
       }
       // Ensure that the generated name is unique.
       DexType finalRepackagedDexType = repackagedDexType;
-      for (int i = 1; isRepackageTypeUsed(finalRepackagedDexType, mappings, appView); i++) {
+      for (int i = 1; isRepackageTypeUsed(finalRepackagedDexType, mappings); i++) {
         finalRepackagedDexType = repackagedDexType.addSuffix(i + "", dexItemFactory);
       }
       return finalRepackagedDexType;
     }
-  }
 
-  private static boolean isRepackageTypeUsed(
-      DexType type, BiMap<DexType, DexType> mappings, AppView<?> appView) {
-    return mappings.inverse().containsKey(type)
-        || (appView.hasLiveness() && appView.withLiveness().appInfo().wasPruned(type));
+    private boolean isRepackageTypeUsed(DexType type, BiMap<DexType, DexType> mappings) {
+      if (mappings.inverse().containsKey(type)) {
+        return true;
+      }
+      return appView.appInfo().wasPruned(type)
+          || appView.appInfo().getMissingClasses().contains(type);
+    }
   }
 
   /** Testing only. */
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
index 27247d4..2e7b6b5 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
@@ -116,13 +116,22 @@
       MemberResolutionResult<?, ?> resolutionResult, boolean isInvoke) {
     if (!resolutionResult.isSuccessfulMemberResolutionResult()) {
       // To preserve errors in the original program, we need to look at the failure dependencies.
-      // For example, if this method accesses in a package-private method in another package, and we
-      // move the two methods to the same package, then the invoke would no longer fail with an
+      // For example, if this member accesses a package-private method in another package, and we
+      // move the two members to the same package, then the invoke would no longer fail with an
       // IllegalAccessError.
-      if (isInvoke) {
-        // TODO(b/150589374): Only add this if we are in the minification mode of repackaging.
-        node.addNeighbor(missingTypeNode);
+
+      // For fields and methods that cannot be found the chance of recovering by repackaging is
+      // pretty slim thus we allow for repackaging the callers.
+      if (resolutionResult.isFieldResolutionResult()) {
+        assert resolutionResult.asFieldResolutionResult().isFailedResolution();
+        return;
       }
+      MethodResolutionResult methodResult = resolutionResult.asMethodResolutionResult();
+      if (methodResult.isClassNotFoundResult()
+          || methodResult.isNoSuchMethodErrorResult(context.getContextClass(), appInfo)) {
+        return;
+      }
+      node.addNeighbor(missingTypeNode);
       return;
     }
 
@@ -161,9 +170,6 @@
     DexClass clazz = appInfo.definitionFor(type);
     if (clazz != null) {
       consumer.accept(clazz);
-    } else {
-      // The missing type reference can be package private and we cannot repackage.
-      node.addNeighbor(missingTypeNode);
     }
   }
 
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 aef58b7..be31b16 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -986,11 +986,6 @@
     if (!keepInfo.getInfo(clazz).isRepackagingAllowed(clazz, options())) {
       return false;
     }
-    for (DexType superType : clazz.allImmediateSupertypes()) {
-      if (definitionFor(superType) == null) {
-        return false;
-      }
-    }
     return clazz
         .traverseProgramMembers(
             member -> {
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
index 3d76d73..bfc3ae7 100644
--- a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.shaking;
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-import static com.android.tools.r8.utils.AndroidApiLevelUtils.getApiLevelIfEnabledForNewMember;
+import static com.android.tools.r8.utils.AndroidApiLevel.minApiLevelIfEnabledOrUnknown;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
@@ -22,7 +22,6 @@
 import com.android.tools.r8.utils.Visibility;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.function.Function;
 
 public class ClassInitFieldSynthesizer {
 
@@ -93,7 +92,7 @@
               null,
               deprecated,
               DexEncodedField.D8_R8_SYNTHESIZED,
-              getApiLevelIfEnabledForNewMember(appView, Function.identity()));
+              minApiLevelIfEnabledOrUnknown(appView));
       clazz.appendStaticField(encodedClinitField);
     }
     lensBuilder.map(type, encodedClinitField.getReference());
diff --git a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
index 5ffc72b..1dd5360 100644
--- a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
@@ -31,6 +31,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 public class MissingClasses {
@@ -56,6 +57,10 @@
         .build();
   }
 
+  public void forEach(Consumer<DexType> missingClassConsumer) {
+    missingClasses.forEach(missingClassConsumer);
+  }
+
   public boolean contains(DexType type) {
     return missingClasses.contains(type);
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 0a49379..27d25bb 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.code.Invoke.Type.DIRECT;
 import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
-import static com.android.tools.r8.utils.AndroidApiLevelUtils.getApiLevelIfEnabledForNewMember;
 
 import com.android.tools.r8.androidapi.AndroidApiReferenceLevelCache;
 import com.android.tools.r8.errors.Unreachable;
@@ -1451,8 +1450,8 @@
               code,
               true,
               method.hasClassFileVersion() ? method.getClassFileVersion() : null,
-              getApiLevelIfEnabledForNewMember(appView, method::getApiReferenceLevelForDefinition),
-              getApiLevelIfEnabledForNewMember(appView, Function.identity()));
+              method.getApiLevelForDefinition(),
+              method.getApiLevelForDefinition());
       bridge.setLibraryMethodOverride(method.isLibraryMethodOverride());
       if (method.accessFlags.isPromotedToPublic()) {
         // The bridge is now the public method serving the role of the original method, and should
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index eb97f48..a86e143 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -508,16 +508,31 @@
       DexProgramClass context,
       AppView<?> appView,
       Consumer<SyntheticProgramClassBuilder> fn) {
-    // Obtain the outer synthesizing context in the case the context itself is synthetic.
-    // This is to ensure a flat input-type -> synthetic-item mapping.
-    SynthesizingContext outerContext =
-        context.isProgramClass()
-            ? getSynthesizingContext(context.asProgramClass(), appView)
-            : SynthesizingContext.fromNonSyntheticInputContext(context.asClasspathOrLibraryClass());
+    SynthesizingContext outerContext = internalGetOuterContext(context, appView);
     DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory());
     return internalCreateClass(kind, fn, outerContext, type, appView.dexItemFactory());
   }
 
+  public DexProgramClass getExistingFixedClass(
+      SyntheticKind kind, DexClass context, AppView<?> appView) {
+    assert kind.isFixedSuffixSynthetic;
+    SynthesizingContext outerContext = internalGetOuterContext(context, appView);
+    DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory());
+    DexClass clazz = appView.definitionFor(type);
+    assert clazz != null;
+    assert isSyntheticClass(type);
+    assert clazz.isProgramClass();
+    return clazz.asProgramClass();
+  }
+
+  // Obtain the outer synthesizing context in the case the context itself is synthetic.
+  // This is to ensure a flat input-type -> synthetic-item mapping.
+  private SynthesizingContext internalGetOuterContext(DexClass context, AppView<?> appView) {
+    return context.isProgramClass()
+        ? getSynthesizingContext(context.asProgramClass(), appView)
+        : SynthesizingContext.fromNonSyntheticInputContext(context.asClasspathOrLibraryClass());
+  }
+
   /**
    * Ensure that a fixed synthetic class exists.
    *
@@ -530,12 +545,7 @@
       Consumer<SyntheticProgramClassBuilder> fn,
       Consumer<DexProgramClass> onCreationConsumer) {
     assert kind.isFixedSuffixSynthetic;
-    // Obtain the outer synthesizing context in the case the context itself is synthetic.
-    // This is to ensure a flat input-type -> synthetic-item mapping.
-    SynthesizingContext outerContext =
-        context.isProgramClass()
-            ? getSynthesizingContext(context.asProgramClass(), appView)
-            : SynthesizingContext.fromNonSyntheticInputContext(context.asClasspathOrLibraryClass());
+    SynthesizingContext outerContext = internalGetOuterContext(context, appView);
     DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory());
     // Fast path is that the synthetic is already present. If so it must be a program class.
     DexClass clazz = appView.definitionFor(type);
@@ -678,12 +688,8 @@
       Consumer<SyntheticMethodBuilder> buildMethodCallback,
       Consumer<T> newMethodCallback) {
     MethodCollection methodCollection = clazz.getMethodCollection();
-    DexEncodedMethod methodDefinition = methodCollection.getMethod(methodReference);
-    if (methodDefinition != null) {
-      return methodDefinition;
-    }
     synchronized (methodCollection) {
-      methodDefinition = methodCollection.getMethod(methodReference);
+      DexEncodedMethod methodDefinition = methodCollection.getMethod(methodReference);
       if (methodDefinition != null) {
         return methodDefinition;
       }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
index adc3f40..196af3b 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
@@ -56,7 +56,7 @@
 
   public SyntheticMethodBuilder setName(DexString name) {
     assert name != null;
-    assert this.name == null;
+    assert this.name == null || this.name == name;
     this.name = name;
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
index 1e4082b..e127d6c 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.utils.structural.Ordered;
 import java.util.Arrays;
 import java.util.List;
@@ -75,6 +76,12 @@
     return DexVersion.getDexVersion(this);
   }
 
+  public static AndroidApiLevel minApiLevelIfEnabledOrUnknown(AppView<?> appView) {
+    return appView.options().apiModelingOptions().enableApiCallerIdentification
+        ? appView.options().minApiLevel
+        : UNKNOWN;
+  }
+
   public static List<AndroidApiLevel> getAndroidApiLevelsSorted() {
     return Arrays.asList(AndroidApiLevel.values());
   }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
index 9272066..96c4a2b 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
@@ -4,32 +4,10 @@
 
 package com.android.tools.r8.utils;
 
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
-import java.util.function.Function;
 
 public class AndroidApiLevelUtils {
 
-  // Static api-level indicating that the api level is min-api.
-  public static final AndroidApiLevel MIN_API_LEVEL = null;
-
-  public static AndroidApiLevel getApiLevelIfEnabledForNewMember(
-      AppView<?> appView, Function<AndroidApiLevel, AndroidApiLevel> getter) {
-    AndroidApiLevel apiLevelIfEnabled = getApiLevelIfEnabled(appView, getter);
-    if (apiLevelIfEnabled == appView.options().minApiLevel) {
-      return MIN_API_LEVEL;
-    }
-    return apiLevelIfEnabled;
-  }
-
-  public static AndroidApiLevel getApiLevelIfEnabled(
-      AppView<?> appView, Function<AndroidApiLevel, AndroidApiLevel> getter) {
-    if (!appView.options().apiModelingOptions().enableApiCallerIdentification) {
-      return AndroidApiLevel.UNKNOWN;
-    }
-    return getter.apply(appView.options().minApiLevel);
-  }
-
   public static OptionalBool isApiSafeForInlining(
       ProgramMethod caller, ProgramMethod inlinee, InternalOptions options) {
     if (!options.apiModelingOptions().enableApiCallerIdentification) {
@@ -41,8 +19,7 @@
     return OptionalBool.of(
         caller
             .getDefinition()
-            .getApiReferenceLevel(options.minApiLevel)
-            .isGreaterThanOrEqualTo(
-                inlinee.getDefinition().getApiReferenceLevelForCode(options.minApiLevel)));
+            .getApiLevel()
+            .isGreaterThanOrEqualTo(inlinee.getDefinition().getApiLevelForCode()));
   }
 }
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 d58387f..b1a2757 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -75,7 +75,11 @@
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 import java.util.zip.ZipOutputStream;
+import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.util.CheckClassAdapter;
 
 /**
  * Collection of program files needed for processing.
@@ -762,6 +766,30 @@
     return extractor.getDescriptor();
   }
 
+  public void validateInputs() {
+    for (ProgramResourceProvider programResourceProvider : getProgramResourceProviders()) {
+      try {
+        for (ProgramResource programResource : programResourceProvider.getProgramResources()) {
+          try {
+            Kind kind = programResource.getKind();
+            if (kind == Kind.DEX) {
+              continue;
+            }
+            byte[] bytes = programResource.getBytes();
+            ClassReader classReader = new ClassReader(bytes);
+            classReader.accept(
+                new CheckClassAdapter(Opcodes.ASM9, new ClassNode(), true) {},
+                ClassReader.EXPAND_FRAMES);
+          } catch (Throwable e) {
+            throw new CompilationError("Failed validating " + programResource.getOrigin(), e);
+          }
+        }
+      } catch (ResourceException e) {
+        throw new CompilationError("Resource exception in validation", e);
+      }
+    }
+  }
+
   /**
    * Builder interface for constructing an AndroidApp.
    */
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 c0c5504..5ee21c1 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1210,7 +1210,7 @@
         System.getProperty("com.android.tools.r8.noCfMarkerForDesugaredCode") != null;
   }
 
-  public static class CallSiteOptimizationOptions {
+  public class CallSiteOptimizationOptions {
 
     // Each time we see an invoke with more dispatch targets than the threshold, we stop call site
     // propagation for all these dispatch targets. The motivation for this is that it is expensive
@@ -1223,7 +1223,7 @@
     private boolean enableExperimentalArgumentPropagation = false;
     private boolean enableTypePropagation = true;
 
-    private void disableOptimization() {
+    public void disableOptimization() {
       enableConstantPropagation = false;
       enableTypePropagation = false;
     }
@@ -1232,18 +1232,14 @@
       enableTypePropagation = false;
     }
 
-    // TODO(b/69963623): Remove this once enabled.
-    @VisibleForTesting
-    public static void enableConstantPropagationForTesting(InternalOptions options) {
-      assert !options.callSiteOptimizationOptions().isConstantPropagationEnabled();
-      options.callSiteOptimizationOptions().enableConstantPropagation = true;
-    }
-
     public int getMaxNumberOfDispatchTargetsBeforeAbandoning() {
       return maxNumberOfDispatchTargetsBeforeAbandoning;
     }
 
     public boolean isEnabled() {
+      if (!isOptimizing()) {
+        return false;
+      }
       return enableConstantPropagation || enableTypePropagation;
     }
 
@@ -1264,12 +1260,19 @@
       enableConstantPropagation = true;
     }
 
-    public void setEnableExperimentalArgumentPropagation() {
-      assert !isExperimentalArgumentPropagationEnabled();
-      enableExperimentalArgumentPropagation = true;
+    public void setEnableExperimentalArgumentPropagation(
+        boolean enableExperimentalArgumentPropagation) {
+      this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     }
   }
 
+  // TODO(b/69963623): Remove this once enabled.
+  @VisibleForTesting
+  public static void enableConstantArgumentPropagationForTesting(InternalOptions options) {
+    assert !options.callSiteOptimizationOptions().isConstantPropagationEnabled();
+    options.callSiteOptimizationOptions().enableConstantPropagation = true;
+  }
+
   public class HorizontalClassMergerOptions {
 
     // TODO(b/138781768): Set enable to true when this bug is resolved.
@@ -1571,6 +1574,7 @@
     public boolean disableMappingToOriginalProgramVerification = false;
     public boolean allowInvalidCfAccessFlags =
         System.getProperty("com.android.tools.r8.allowInvalidCfAccessFlags") != null;
+    public boolean verifyInputs = System.getProperty("com.android.tools.r8.verifyInputs") != null;
     // TODO(b/177333791): Set to true
     public boolean checkForNotExpandingMainDexTracingResult = false;
     public Set<String> allowedUnusedDontWarnPatterns = new HashSet<>();
@@ -1640,7 +1644,7 @@
 
   public MapVersion getMapFileVersion() {
     return testing.enableExperimentalMapFileVersion
-        ? MapVersion.MapVersionExperimental
+        ? MapVersion.MAP_VERSION_EXPERIMENTAL
         : MapVersion.STABLE;
   }
 
diff --git a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
index d8e0196..848c566 100644
--- a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
+++ b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
@@ -116,8 +116,12 @@
                     containsString(
                         "java.lang.NoClassDefFoundError: "
                             + "Failed resolution of: Ljava/util/concurrent/Flow$Subscriber;"),
-                    // Art 10+.
-                    containsString("java.lang.ClassNotFoundException: MySubscriber")));
+                    // Art 10.
+                    containsString("java.lang.ClassNotFoundException: MySubscriber"),
+                    // Art 11+.
+                    containsString(
+                        "java.lang.ClassNotFoundException: "
+                            + "java.util.concurrent.SubmissionPublisher")));
       } else {
         if (parameters.getRuntime().asCf().getVm() == CfVm.JDK8) {
           // java.util.concurrent.Flow$Subscriber not present in JDK8.
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index bcd4fea..f58f1fd 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -95,15 +95,17 @@
   private static final String ART_TESTS_NATIVE_LIBRARY_DIR = "tests/2017-10-04/art/lib64";
   private static final String ART_LEGACY_TESTS_NATIVE_LIBRARY_DIR = "tests/2016-12-19/art/lib64";
 
-  private static final RuntimeSet LEGACY_RUNTIME = TestCondition.runtimes(
-      DexVm.Version.V4_0_4,
-      DexVm.Version.V4_4_4,
-      DexVm.Version.V5_1_1,
-      DexVm.Version.V6_0_1,
-      DexVm.Version.V7_0_0,
-      DexVm.Version.V8_1_0,
-      DexVm.Version.V9_0_0,
-      DexVm.Version.V10_0_0);
+  private static final RuntimeSet LEGACY_RUNTIME =
+      TestCondition.runtimes(
+          DexVm.Version.V4_0_4,
+          DexVm.Version.V4_4_4,
+          DexVm.Version.V5_1_1,
+          DexVm.Version.V6_0_1,
+          DexVm.Version.V7_0_0,
+          DexVm.Version.V8_1_0,
+          DexVm.Version.V9_0_0,
+          DexVm.Version.V10_0_0,
+          DexVm.Version.V12_0_0);
 
   // Input jar for jctf tests.
   private static final String JCTF_COMMON_JAR = "build/libs/jctfCommon.jar";
@@ -185,6 +187,18 @@
           .put(
               "134-reg-promotion",
               TestCondition.match(TestCondition.runtimes(DexVm.Version.V10_0_0)))
+          // TODO(b/197078742): Triage - test OOMs.
+          .put(
+              "134-reg-promotion",
+              TestCondition.match(TestCondition.runtimes(DexVm.Version.V12_0_0)))
+          // TODO(b/197078746): Triage - fails with "java.lang.NoSuchMethodException:
+          //  org.apache.harmony.dalvik.ddmc.DdmVmInternal.enableRecentAllocations [boolean]"
+          .put("098-ddmc", TestCondition.match(TestCondition.runtimes(DexVm.Version.V12_0_0)))
+          // TODO(b/197079442): Triage - fails with "java.lang.NoSuchMethodException:
+          //  org.apache.harmony.dalvik.ddmc.DdmVmInternal.enableRecentAllocations [boolean]"
+          .put(
+              "145-alloc-tracking-stress",
+              TestCondition.match(TestCondition.runtimes(DexVm.Version.V12_0_0)))
           .build();
 
   // Tests that are flaky with the Art version we currently use.
@@ -488,115 +502,134 @@
   static {
     ImmutableMap.Builder<DexVm.Version, List<String>> builder = ImmutableMap.builder();
     builder
-        .put(DexVm.Version.V10_0_0, ImmutableList.of(
-            // TODO(b/144975341): Triage, Verif error.
-            "518-null-array-get",
-            // TODO(b/144975341): Triage, Linking error.
-            "457-regs",
-            "543-env-long-ref",
-            "454-get-vreg"
-        ))
-        .put(DexVm.Version.V9_0_0, ImmutableList.of(
-            // TODO(120400625): Triage.
-            "454-get-vreg",
-            // TODO(120402198): Triage.
-            "457-regs",
-            // TODO(120401674): Triage.
-            "543-env-long-ref",
-            // TODO(120261858) Triage.
-            "518-null-array-get"
-        ))
-        .put(DexVm.Version.V8_1_0, ImmutableList.of(
-            // TODO(119938529): Triage.
-            "709-checker-varhandles",
-            "454-get-vreg",
-            "457-regs"
-        ))
-        .put(DexVm.Version.V7_0_0, ImmutableList.of(
-            // Addition of checks for super-class-initialization cause this to abort on non-ToT art.
-            "008-exceptions",
+        .put(
+            DexVm.Version.V12_0_0,
+            ImmutableList.of("454-get-vreg", "457-regs", "543-env-long-ref", "518-null-array-get"))
+        .put(
+            DexVm.Version.V10_0_0,
+            ImmutableList.of(
+                // TODO(b/144975341): Triage, Verif error.
+                "518-null-array-get",
+                // TODO(b/144975341): Triage, Linking error.
+                "457-regs",
+                "543-env-long-ref",
+                "454-get-vreg"))
+        .put(
+            DexVm.Version.V9_0_0,
+            ImmutableList.of(
+                // TODO(120400625): Triage.
+                "454-get-vreg",
+                // TODO(120402198): Triage.
+                "457-regs",
+                // TODO(120401674): Triage.
+                "543-env-long-ref",
+                // TODO(120261858) Triage.
+                "518-null-array-get"))
+        .put(
+            DexVm.Version.V8_1_0,
+            ImmutableList.of(
+                // TODO(119938529): Triage.
+                "709-checker-varhandles", "454-get-vreg", "457-regs"))
+        .put(
+            DexVm.Version.V7_0_0,
+            ImmutableList.of(
+                // Addition of checks for super-class-initialization cause this to abort on non-ToT
+                // art.
+                "008-exceptions",
 
-            // Generally fails on non-R8/D8 running.
-            "156-register-dex-file-multi-loader",
-            "412-new-array",
-            "610-arraycopy",
-            "625-checker-licm-regressions"))
-        .put(DexVm.Version.V6_0_1, ImmutableList.of(
-            // Addition of checks for super-class-initialization cause this to abort on non-ToT art.
-            "008-exceptions",
+                // Generally fails on non-R8/D8 running.
+                "156-register-dex-file-multi-loader",
+                "412-new-array",
+                "610-arraycopy",
+                "625-checker-licm-regressions"))
+        .put(
+            DexVm.Version.V6_0_1,
+            ImmutableList.of(
+                // Addition of checks for super-class-initialization cause this to abort on non-ToT
+                // art.
+                "008-exceptions",
 
-            // Generally fails on non-R8/D8 running.
-            "004-checker-UnsafeTest18",
-            "005-annotations",
-            "008-exceptions",
-            "082-inline-execute",
-            "099-vmdebug",
-            "156-register-dex-file-multi-loader",
-            "412-new-array",
-            "580-checker-round",
-            "594-invoke-super",
-            "625-checker-licm-regressions",
-            "626-const-class-linking"))
-        .put(DexVm.Version.V5_1_1, ImmutableList.of(
-            // Addition of checks for super-class-initialization cause this to abort on non-ToT art.
-            "008-exceptions",
+                // Generally fails on non-R8/D8 running.
+                "004-checker-UnsafeTest18",
+                "005-annotations",
+                "008-exceptions",
+                "082-inline-execute",
+                "099-vmdebug",
+                "156-register-dex-file-multi-loader",
+                "412-new-array",
+                "580-checker-round",
+                "594-invoke-super",
+                "625-checker-licm-regressions",
+                "626-const-class-linking"))
+        .put(
+            DexVm.Version.V5_1_1,
+            ImmutableList.of(
+                // Addition of checks for super-class-initialization cause this to abort on non-ToT
+                // art.
+                "008-exceptions",
 
-            // Generally fails on non R8/D8 running.
-            "004-checker-UnsafeTest18",
-            "004-NativeAllocations",
-            "005-annotations",
-            "008-exceptions",
-            "082-inline-execute",
-            "099-vmdebug",
-            "143-string-value",
-            "156-register-dex-file-multi-loader",
-            "536-checker-intrinsic-optimization",
-            "552-invoke-non-existent-super",
-            "580-checker-round",
-            "580-checker-string-fact-intrinsics",
-            "594-invoke-super",
-            "605-new-string-from-bytes",
-            "626-const-class-linking"))
-        .put(DexVm.Version.V4_4_4, ImmutableList.of(
-            // Addition of checks for super-class-initialization cause this to abort on non-ToT art.
-            "008-exceptions",
+                // Generally fails on non R8/D8 running.
+                "004-checker-UnsafeTest18",
+                "004-NativeAllocations",
+                "005-annotations",
+                "008-exceptions",
+                "082-inline-execute",
+                "099-vmdebug",
+                "143-string-value",
+                "156-register-dex-file-multi-loader",
+                "536-checker-intrinsic-optimization",
+                "552-invoke-non-existent-super",
+                "580-checker-round",
+                "580-checker-string-fact-intrinsics",
+                "594-invoke-super",
+                "605-new-string-from-bytes",
+                "626-const-class-linking"))
+        .put(
+            DexVm.Version.V4_4_4,
+            ImmutableList.of(
+                // Addition of checks for super-class-initialization cause this to abort on non-ToT
+                // art.
+                "008-exceptions",
 
-            // Generally fails on non R8/D8 running.
-            "004-checker-UnsafeTest18",
-            "004-NativeAllocations",
-            "005-annotations",
-            "008-exceptions",
-            "082-inline-execute",
-            "099-vmdebug",
-            "143-string-value",
-            "156-register-dex-file-multi-loader",
-            "536-checker-intrinsic-optimization",
-            "552-invoke-non-existent-super",
-            "580-checker-round",
-            "580-checker-string-fact-intrinsics",
-            "594-invoke-super",
-            "605-new-string-from-bytes",
-            "626-const-class-linking"))
-        .put(DexVm.Version.V4_0_4, ImmutableList.of(
-            // Addition of checks for super-class-initialization cause this to abort on non-ToT art.
-            "008-exceptions",
+                // Generally fails on non R8/D8 running.
+                "004-checker-UnsafeTest18",
+                "004-NativeAllocations",
+                "005-annotations",
+                "008-exceptions",
+                "082-inline-execute",
+                "099-vmdebug",
+                "143-string-value",
+                "156-register-dex-file-multi-loader",
+                "536-checker-intrinsic-optimization",
+                "552-invoke-non-existent-super",
+                "580-checker-round",
+                "580-checker-string-fact-intrinsics",
+                "594-invoke-super",
+                "605-new-string-from-bytes",
+                "626-const-class-linking"))
+        .put(
+            DexVm.Version.V4_0_4,
+            ImmutableList.of(
+                // Addition of checks for super-class-initialization cause this to abort on non-ToT
+                // art.
+                "008-exceptions",
 
-            // Generally fails on non R8/D8 running.
-            "004-checker-UnsafeTest18",
-            "004-NativeAllocations",
-            "005-annotations",
-            "008-exceptions",
-            "082-inline-execute",
-            "099-vmdebug",
-            "143-string-value",
-            "156-register-dex-file-multi-loader",
-            "536-checker-intrinsic-optimization",
-            "552-invoke-non-existent-super",
-            "580-checker-round",
-            "580-checker-string-fact-intrinsics",
-            "594-invoke-super",
-            "605-new-string-from-bytes",
-            "626-const-class-linking"));
+                // Generally fails on non R8/D8 running.
+                "004-checker-UnsafeTest18",
+                "004-NativeAllocations",
+                "005-annotations",
+                "008-exceptions",
+                "082-inline-execute",
+                "099-vmdebug",
+                "143-string-value",
+                "156-register-dex-file-multi-loader",
+                "536-checker-intrinsic-optimization",
+                "552-invoke-non-existent-super",
+                "580-checker-round",
+                "580-checker-string-fact-intrinsics",
+                "594-invoke-super",
+                "605-new-string-from-bytes",
+                "626-const-class-linking"));
     expectedToFailRunWithArtVersion = builder.build();
   }
 
@@ -2050,6 +2083,10 @@
           System.out.println("Running on 10.0.0 is disabled, see b/144966342");
           continue;
         }
+        if (vm.getVersion() == DexVm.Version.V12_0_0) {
+          System.out.println("Running on 12.0.0 is disabled, see b/197078995");
+          continue;
+        }
         vms.add(new DexRuntime(vm));
       }
     }
diff --git a/src/test/java/com/android/tools/r8/TestCondition.java b/src/test/java/com/android/tools/r8/TestCondition.java
index d72ba77..07fff0e 100644
--- a/src/test/java/com/android/tools/r8/TestCondition.java
+++ b/src/test/java/com/android/tools/r8/TestCondition.java
@@ -25,6 +25,7 @@
     ART_V8_1_0,
     ART_V9_0_0,
     ART_V10_0_0,
+    ART_V12_0_0,
     ART_DEFAULT,
     JAVA;
 
@@ -49,6 +50,8 @@
           return ART_V9_0_0;
         case V10_0_0:
           return ART_V10_0_0;
+        case V12_0_0:
+          return ART_V12_0_0;
         case DEFAULT:
           return ART_DEFAULT;
         default:
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index 23e2e5c..90d4f75 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -145,6 +145,14 @@
     return assertFailure();
   }
 
+  public RR assertFailureWithErrorThatMatchesIf(boolean condition, Matcher<String> matcher) {
+    if (condition) {
+      assertStderrMatches(matcher);
+      return assertFailure();
+    }
+    return self();
+  }
+
   public RR assertFailureWithOutput(String expected) {
     assertStdoutMatches(is(expected));
     return assertFailure();
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index a1bdeb8..a38852e 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -256,7 +256,9 @@
     ART_9_0_0_TARGET(Version.V9_0_0, Kind.TARGET),
     ART_9_0_0_HOST(Version.V9_0_0, Kind.HOST),
     ART_10_0_0_TARGET(Version.V10_0_0, Kind.TARGET),
-    ART_10_0_0_HOST(Version.V10_0_0, Kind.HOST);
+    ART_10_0_0_HOST(Version.V10_0_0, Kind.HOST),
+    ART_12_0_0_TARGET(Version.V12_0_0, Kind.TARGET),
+    ART_12_0_0_HOST(Version.V12_0_0, Kind.HOST);
 
     private static final ImmutableMap<String, DexVm> SHORT_NAME_MAP =
         Arrays.stream(DexVm.values()).collect(ImmutableMap.toImmutableMap(
@@ -271,7 +273,8 @@
       V8_1_0("8.1.0"),
       DEFAULT("default"),
       V9_0_0("9.0.0"),
-      V10_0_0("10.0.0");
+      V10_0_0("10.0.0"),
+      V12_0_0("12.0.0");
 
       Version(String shortName) {
         this.shortName = shortName;
@@ -289,6 +292,10 @@
         return this == last();
       }
 
+      public boolean isEqualTo(Version other) {
+        return compareTo(other) == 0;
+      }
+
       public boolean isNewerThan(Version other) {
         return compareTo(other) > 0;
       }
@@ -321,7 +328,7 @@
       }
 
       public static Version last() {
-        return V10_0_0;
+        return V12_0_0;
       }
 
       static {
@@ -358,10 +365,22 @@
       return SHORT_NAME_MAP.get(version.shortName + "_" + Kind.HOST.toString());
     }
 
+    public boolean isEqualTo(DexVm other) {
+      return version.isNewerThanOrEqual(other.version);
+    }
+
     public boolean isNewerThan(DexVm other) {
       return version.isNewerThan(other.version);
     }
 
+    public boolean isNewerThanOrEqual(DexVm other) {
+      return version.isNewerThanOrEqual(other.version);
+    }
+
+    public boolean isOlderThan(DexVm other) {
+      return version.isOlderThan(other.version);
+    }
+
     public boolean isOlderThanOrEqual(DexVm other) {
       return version.isOlderThanOrEqual(other.version);
     }
@@ -580,6 +599,7 @@
   private static final Map<DexVm, String> ART_DIRS =
       ImmutableMap.<DexVm, String>builder()
           .put(DexVm.ART_DEFAULT, "art")
+          .put(DexVm.ART_12_0_0_HOST, "host/art-12.0.0-beta4")
           .put(DexVm.ART_10_0_0_HOST, "art-10.0.0")
           .put(DexVm.ART_9_0_0_HOST, "art-9.0.0")
           .put(DexVm.ART_8_1_0_HOST, "art-8.1.0")
@@ -587,10 +607,12 @@
           .put(DexVm.ART_6_0_1_HOST, "art-6.0.1")
           .put(DexVm.ART_5_1_1_HOST, "art-5.1.1")
           .put(DexVm.ART_4_4_4_HOST, "dalvik")
-          .put(DexVm.ART_4_0_4_HOST, "dalvik-4.0.4").build();
+          .put(DexVm.ART_4_0_4_HOST, "dalvik-4.0.4")
+          .build();
   private static final Map<DexVm, String> ART_BINARY_VERSIONS =
       ImmutableMap.<DexVm, String>builder()
           .put(DexVm.ART_DEFAULT, "bin/art")
+          .put(DexVm.ART_12_0_0_HOST, "bin/art")
           .put(DexVm.ART_10_0_0_HOST, "bin/art")
           .put(DexVm.ART_9_0_0_HOST, "bin/art")
           .put(DexVm.ART_8_1_0_HOST, "bin/art")
@@ -598,16 +620,19 @@
           .put(DexVm.ART_6_0_1_HOST, "bin/art")
           .put(DexVm.ART_5_1_1_HOST, "bin/art")
           .put(DexVm.ART_4_4_4_HOST, "bin/dalvik")
-          .put(DexVm.ART_4_0_4_HOST, "bin/dalvik").build();
+          .put(DexVm.ART_4_0_4_HOST, "bin/dalvik")
+          .build();
 
   private static final Map<DexVm, String> ART_BINARY_VERSIONS_X64 =
       ImmutableMap.<DexVm, String>builder()
           .put(DexVm.ART_DEFAULT, "bin/art")
+          .put(DexVm.ART_12_0_0_HOST, "bin/art")
           .put(DexVm.ART_10_0_0_HOST, "bin/art")
           .put(DexVm.ART_9_0_0_HOST, "bin/art")
           .put(DexVm.ART_8_1_0_HOST, "bin/art")
           .put(DexVm.ART_7_0_0_HOST, "bin/art")
-          .put(DexVm.ART_6_0_1_HOST, "bin/art").build();
+          .put(DexVm.ART_6_0_1_HOST, "bin/art")
+          .build();
 
   private static final List<String> DALVIK_BOOT_LIBS =
       ImmutableList.of(
@@ -627,6 +652,7 @@
     ImmutableMap.Builder<DexVm, List<String>> builder = ImmutableMap.builder();
     builder
         .put(DexVm.ART_DEFAULT, ART_BOOT_LIBS)
+        .put(DexVm.ART_12_0_0_HOST, ART_BOOT_LIBS)
         .put(DexVm.ART_10_0_0_HOST, ART_BOOT_LIBS)
         .put(DexVm.ART_9_0_0_HOST, ART_BOOT_LIBS)
         .put(DexVm.ART_8_1_0_HOST, ART_BOOT_LIBS)
@@ -644,6 +670,7 @@
     ImmutableMap.Builder<DexVm, String> builder = ImmutableMap.builder();
     builder
         .put(DexVm.ART_DEFAULT, "angler")
+        .put(DexVm.ART_12_0_0_HOST, "redfin")
         .put(DexVm.ART_10_0_0_HOST, "coral")
         .put(DexVm.ART_9_0_0_HOST, "marlin")
         .put(DexVm.ART_8_1_0_HOST, "marlin")
@@ -1002,6 +1029,8 @@
     switch (dexVm.version) {
       case DEFAULT:
         return AndroidApiLevel.O;
+      case V12_0_0:
+        return AndroidApiLevel.S;
       case V10_0_0:
         return AndroidApiLevel.Q;
       case V9_0_0:
@@ -1898,7 +1927,8 @@
   public static ProcessResult runDex2OatRaw(Path file, Path outFile, DexVm vm) throws IOException {
     // TODO(jmhenaff): find a way to run this on windows (push dex and run on device/emulator?)
     Assume.assumeTrue(ToolHelper.isDex2OatSupported());
-    Assume.assumeFalse("b/144975341", vm.version == DexVm.Version.V10_0_0);
+    Assume.assumeFalse(
+        "b/144975341", vm.version == DexVm.Version.V10_0_0 || vm.version == DexVm.Version.V12_0_0);
     if (vm.isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
       // Run default dex2oat for tests on dalvik runtimes.
       vm = DexVm.ART_DEFAULT;
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchLinkInterfaceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchLinkInterfaceTest.java
index 04337ec..c87f2cc 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchLinkInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchLinkInterfaceTest.java
@@ -7,7 +7,7 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.addTracedApiReferenceLevelCallBack;
 import static com.android.tools.r8.utils.AndroidApiLevel.LATEST;
 import static com.android.tools.r8.utils.AndroidApiLevel.R;
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -56,7 +56,7 @@
             addTracedApiReferenceLevelCallBack(
                 (method, apiLevel) -> {
                   if (Reference.methodFromMethod(main).equals(method)) {
-                    assertEquals(R, apiLevel);
+                    assertTrue(apiLevel.isGreaterThanOrEqualTo(R));
                   }
                 }))
         .compile();
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/InvalidStackHeightTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/InvalidStackHeightTest.java
index 71b6637..9fffa07 100644
--- a/src/test/java/com/android/tools/r8/cf/stackmap/InvalidStackHeightTest.java
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/InvalidStackHeightTest.java
@@ -6,6 +6,8 @@
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -88,6 +90,23 @@
         .assertSuccessWithOutputLines(EXPECTED);
   }
 
+  @Test()
+  public void testR8InputVerification() throws Exception {
+    try {
+      testForR8(parameters.getBackend())
+          .addProgramClassFileData(getMainWithChangedMaxStackHeight())
+          .enableInliningAnnotations()
+          .addKeepMainRule(Main.class)
+          .setMinApi(parameters.getApiLevel())
+          .addOptionsModification(options -> options.testing.verifyInputs = true)
+          .compile();
+    } catch (CompilationFailedException e) {
+      assertTrue(e.getCause().getMessage().contains("Insufficient maximum stack size"));
+      return;
+    }
+    fail("Should always throw");
+  }
+
   public byte[] getMainWithChangedMaxStackHeight() throws Exception {
     return transformer(Main.class).setMaxStackHeight(MethodPredicate.onName("main"), 1).transform();
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java
index c39b7b5..d1cedfc 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.naming.retrace.StackTrace;
-import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -55,21 +54,7 @@
         .inspectStackTrace(
             (stackTrace, codeInspector) -> {
               assertThat(codeInspector.clazz(A.class), isPresent());
-              StackTrace expectedStackTraceWithMergedConstructor =
-                  StackTrace.builder()
-                      .add(expectedStackTrace)
-                      .add(
-                          2,
-                          StackTraceLine.builder()
-                              .setClassName(A.class.getTypeName())
-                              // TODO(b/124483578): The synthetic method should not be part of the
-                              //  retraced stack trace.
-                              .setMethodName("$r8$init$synthetic")
-                              .setFileName(getClass().getSimpleName() + ".java")
-                              .setLineNumber(0)
-                              .build())
-                      .build();
-              assertThat(stackTrace, isSame(expectedStackTraceWithMergedConstructor));
+              assertThat(stackTrace, isSame(expectedStackTrace));
               assertThat(codeInspector.clazz(B.class), not(isPresent()));
             });
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorWithEquivalenceStackTraceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorWithEquivalenceStackTraceTest.java
index 9b5b87d..cb0b232 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorWithEquivalenceStackTraceTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorWithEquivalenceStackTraceTest.java
@@ -13,9 +13,7 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfRuntime;
-import com.android.tools.r8.classmerging.horizontal.MergedConstructorStackTraceTest.A;
 import com.android.tools.r8.naming.retrace.StackTrace;
-import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -63,16 +61,6 @@
                       //  constructors should retrace to the set of lines from each of the
                       //  individual source constructors.
                       .map(1, stackTraceLine -> stackTraceLine.builderOf().setLineNumber(0).build())
-                      // TODO(b/124483578): The synthetic method should not be part of the retraced
-                      //  stack trace.
-                      .add(
-                          2,
-                          StackTraceLine.builder()
-                              .setClassName(A.class.getTypeName())
-                              .setMethodName("$r8$init$synthetic")
-                              .setFileName(getClass().getSimpleName() + ".java")
-                              .setLineNumber(0)
-                              .build())
                       .build();
               assertThat(stackTrace, isSame(expectedStackTraceWithMergedConstructor));
               assertThat(codeInspector.clazz(B.class), not(isPresent()));
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStackTraceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStackTraceTest.java
index 0375885..12edd71 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStackTraceTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStackTraceTest.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.naming.retrace.StackTrace;
-import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -54,20 +53,7 @@
             (stackTrace, codeInspector) -> {
               assertThat(codeInspector.clazz(Program.A.class), isPresent());
               assertThat(codeInspector.clazz(Program.B.class), isAbsent());
-              StackTrace expectedStackTraceWithMergedMethod =
-                  StackTrace.builder()
-                      .add(expectedStackTrace)
-                      .add(
-                          1,
-                          StackTraceLine.builder()
-                              .setClassName(Program.A.class.getTypeName())
-                              .setMethodName("foo$bridge")
-                              .setFileName("Program.java")
-                              .setFileName(getClass().getSimpleName() + ".java")
-                              .setLineNumber(stackTrace.get(1).lineNumber)
-                              .build())
-                      .build();
-              assertThat(stackTrace, isSame(expectedStackTraceWithMergedMethod));
+              assertThat(stackTrace, isSame(expectedStackTrace));
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/LambdaMissingInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/LambdaMissingInterfaceTest.java
index bdcfd8a..59853ee 100644
--- a/src/test/java/com/android/tools/r8/desugar/LambdaMissingInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/LambdaMissingInterfaceTest.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.desugar;
 
+import static org.hamcrest.CoreMatchers.containsString;
+
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -38,7 +40,12 @@
         .compile()
         .addRunClasspathClasses(MissingInterface.class)
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatThrows(AbstractMethodError.class);
+        // We allow for renaming if the class is missing
+        .assertFailureWithErrorThatMatchesIf(
+            parameters.getDexRuntimeVersion().isDalvik(),
+            containsString(descriptor(MissingInterface.class) + "' is not accessible"))
+        .assertFailureWithErrorThatThrowsIf(
+            !parameters.getDexRuntimeVersion().isDalvik(), IllegalAccessError.class);
   }
 
   interface MissingInterface {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ByteBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/ByteBackportJava9Test.java
index 37aa606..81038d6 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/ByteBackportJava9Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ByteBackportJava9Test.java
@@ -4,17 +4,18 @@
 
 package com.android.tools.r8.desugar.backports;
 
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
-import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-
 @RunWith(Parameterized.class)
 public final class ByteBackportJava9Test extends AbstractBackportTest {
   @Parameters(name = "{0}")
@@ -31,7 +32,8 @@
 
   public ByteBackportJava9Test(TestParameters parameters) {
     super(parameters, Byte.class, TEST_JAR, "backport.ByteBackportJava9Main");
-    // Note: None of the methods in this test exist in the latest android.jar. If/when they ship in
-    // an actual API level, migrate these tests to ByteBackportTest.
+
+    // Byte.compareUnsigned added in API 31.
+    registerTarget(AndroidApiLevel.S, 16);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ListBackportJava10Test.java b/src/test/java/com/android/tools/r8/desugar/backports/ListBackportJava10Test.java
index 309b319..7b80e96 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/ListBackportJava10Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ListBackportJava10Test.java
@@ -4,9 +4,12 @@
 
 package com.android.tools.r8.desugar.backports;
 
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
@@ -14,8 +17,6 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
-import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-
 @RunWith(Parameterized.class)
 public class ListBackportJava10Test extends AbstractBackportTest {
   @Parameters(name = "{0}")
@@ -40,5 +41,8 @@
     ignoreInvokes("get");
     ignoreInvokes("set");
     ignoreInvokes("size");
+
+    // List.copyOf added in API 31.
+    registerTarget(AndroidApiLevel.S, 3);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ListBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/ListBackportJava9Test.java
index 0d55065..ddf502b 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/ListBackportJava9Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ListBackportJava9Test.java
@@ -44,13 +44,16 @@
     ignoreInvokes("get");
     ignoreInvokes("set");
     ignoreInvokes("size");
+
+    // List.of added in API 30.
+    registerTarget(AndroidApiLevel.R, 18);
   }
 
   @Test
   public void desugaringApiLevelR() throws Exception {
-    // TODO(154759404): This test should start to fail when testing on an Android R VM.
-    if (parameters.getRuntime().isDex()
-        && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.Q)) {
+    // TODO(b/154759404): This test should start to fail when testing on an Android R VM.
+    // This has now been checked with S, when R testing is added check and remove this.
+    if (parameters.getRuntime().isDex() && parameters.getApiLevel().isEqualTo(AndroidApiLevel.Q)) {
       testForD8()
           .setMinApi(AndroidApiLevel.R)
           .addProgramClasses(MiniAssert.class, IgnoreInvokes.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/MapBackportJava10Test.java b/src/test/java/com/android/tools/r8/desugar/backports/MapBackportJava10Test.java
index e54d7d8..11008cc 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/MapBackportJava10Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/MapBackportJava10Test.java
@@ -4,9 +4,12 @@
 
 package com.android.tools.r8.desugar.backports;
 
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Map;
@@ -14,8 +17,6 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
-import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-
 @RunWith(Parameterized.class)
 public class MapBackportJava10Test extends AbstractBackportTest {
   @Parameters(name = "{0}")
@@ -40,5 +41,8 @@
     ignoreInvokes("get");
     ignoreInvokes("put");
     ignoreInvokes("size");
+
+    // Map.copyOf added in API 31.
+    registerTarget(AndroidApiLevel.S, 4);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/MapBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/MapBackportJava9Test.java
index 27cb824..73ffa29 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/MapBackportJava9Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/MapBackportJava9Test.java
@@ -44,13 +44,16 @@
     ignoreInvokes("get");
     ignoreInvokes("put");
     ignoreInvokes("size");
+
+    // Map.entry, Map.of and Map.ofEntries added in API 30.
+    registerTarget(AndroidApiLevel.R, 29);
   }
 
   @Test
   public void desugaringApiLevelR() throws Exception {
-    // TODO(154759404): This test should start to fail when testing on an Android R VM.
-    if (parameters.getRuntime().isDex()
-        && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.Q)) {
+    // TODO(b/154759404): This test should start to fail when testing on an Android R VM.
+    // This has now been checked with S, when R testing is added check and remove this.
+    if (parameters.getRuntime().isDex() && parameters.getApiLevel().isEqualTo(AndroidApiLevel.R)) {
       testForD8()
           .setMinApi(AndroidApiLevel.R)
           .addProgramClasses(MiniAssert.class, IgnoreInvokes.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/MathBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/MathBackportJava9Test.java
index 387697e..2f70fc2 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/MathBackportJava9Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/MathBackportJava9Test.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import org.junit.runner.RunWith;
@@ -31,7 +32,9 @@
 
   public MathBackportJava9Test(TestParameters parameters) {
     super(parameters, Math.class, TEST_JAR, "backport.MathBackportJava9Main");
-    // Note: None of the methods in this test exist in the latest android.jar. If/when they ship in
-    // an actual API level, migrate these tests to MathBackportTest.
+
+    // Math.floorDiv, Math.floorMod, Math.multiplyExact, Math.multiplyFull and Math.multiplyHigh
+    // added in API 31.
+    registerTarget(AndroidApiLevel.S, 27);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportJava9Test.java
index 09aef1e..527d2c0 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportJava9Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportJava9Test.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Objects;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -34,16 +35,20 @@
   private static final String TEST_CLASS = "backport.ObjectsBackportJava9Main";
 
   public ObjectsBackportJava9Test(TestParameters parameters) {
-    super(parameters, Short.class, TEST_JAR, TEST_CLASS);
+    super(parameters, Objects.class, TEST_JAR, TEST_CLASS);
     // Note: None of the methods in this test exist in the latest android.jar. If/when they ship in
     // an actual API level, migrate these tests to ObjectsBackportTest.
+
+    // Objects.checkFromIndexSize, Objects.checkFromToIndex, Objects.checkIndex,
+    // Objects.requireNonNullElse and Objects.requireNonNullElseGet added in API 30.
+    registerTarget(AndroidApiLevel.R, 28);
   }
 
   @Test
   public void desugaringApiLevelR() throws Exception {
-    // TODO(154759404): This test should start to fail when testing on an Android R VM.
-    if (parameters.getRuntime().isDex()
-        && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.Q)) {
+    // TODO(b/154759404): This test should start to fail when testing on an Android R VM.
+    // This has now been checked with S, when R testing is added chck and remove this.
+    if (parameters.getRuntime().isDex() && parameters.getApiLevel().isEqualTo(AndroidApiLevel.R)) {
       testForD8()
           .setMinApi(AndroidApiLevel.R)
           .addProgramClasses(MiniAssert.class, IgnoreInvokes.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/SetBackportJava10Test.java b/src/test/java/com/android/tools/r8/desugar/backports/SetBackportJava10Test.java
index 5c08b8f..2a17cb2 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/SetBackportJava10Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/SetBackportJava10Test.java
@@ -4,9 +4,12 @@
 
 package com.android.tools.r8.desugar.backports;
 
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Set;
@@ -14,8 +17,6 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
-import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-
 @RunWith(Parameterized.class)
 public class SetBackportJava10Test extends AbstractBackportTest {
   @Parameters(name = "{0}")
@@ -39,5 +40,10 @@
     ignoreInvokes("add");
     ignoreInvokes("contains");
     ignoreInvokes("size");
+
+    // Set.of added in API 30
+    registerTarget(AndroidApiLevel.R, 1);
+    // Set.copyOf added in API 31
+    registerTarget(AndroidApiLevel.S, 5);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/SetBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/SetBackportJava9Test.java
index 408b203..652cde0 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/SetBackportJava9Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/SetBackportJava9Test.java
@@ -43,13 +43,16 @@
     ignoreInvokes("add");
     ignoreInvokes("contains");
     ignoreInvokes("size");
+
+    // Set.of added in API 30.
+    registerTarget(AndroidApiLevel.R, 21);
   }
 
   @Test
   public void desugaringApiLevelR() throws Exception {
-    // TODO(154759404): This test should start to fail when testing on an Android R VM.
-    if (parameters.getRuntime().isDex()
-        && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.Q)) {
+    // TODO(b/154759404): This test should start to fail when testing on an Android R VM.
+    // This has now been checked with S, when R testing is added check and remove this.
+    if (parameters.getRuntime().isDex() && parameters.getApiLevel().isEqualTo(AndroidApiLevel.R)) {
       testForD8()
           .setMinApi(AndroidApiLevel.R)
           .addProgramClasses(MiniAssert.class, IgnoreInvokes.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ShortBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/ShortBackportJava9Test.java
index beffd8a..d1d3c95 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/ShortBackportJava9Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ShortBackportJava9Test.java
@@ -4,17 +4,18 @@
 
 package com.android.tools.r8.desugar.backports;
 
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
-import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-
 @RunWith(Parameterized.class)
 public final class ShortBackportJava9Test extends AbstractBackportTest {
   @Parameters(name = "{0}")
@@ -31,7 +32,8 @@
 
   public ShortBackportJava9Test(TestParameters parameters) {
     super(parameters, Short.class, TEST_JAR, "backport.ShortBackportJava9Main");
-    // Note: None of the methods in this test exist in the latest android.jar. If/when they ship in
-    // an actual API level, migrate these tests to ShortBackportTest.
+
+    // Short.compareUnsigned added in API 31.
+    registerTarget(AndroidApiLevel.S, 16);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/SparseArrayBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/SparseArrayBackportTest.java
index 61cf95d..1ebfe97 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/SparseArrayBackportTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/SparseArrayBackportTest.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.desugar.backports;
 
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import org.junit.runner.RunWith;
@@ -24,18 +26,29 @@
   public SparseArrayBackportTest(TestParameters parameters) throws IOException {
     super(
         parameters,
-        SparseArrayBackportTest.getSparseArray(),
+        SparseArrayBackportTest.getSparseArray(parameters),
         ImmutableList.of(
-            SparseArrayBackportTest.getTestRunner(), SparseArrayBackportTest.getSparseArray()));
+            SparseArrayBackportTest.getTestRunner(),
+            SparseArrayBackportTest.getSparseArray(parameters)));
 
     // The constructor is used by the test and put has been available since API 1 and is the
     // method set is rewritten to.
     ignoreInvokes("<init>");
     ignoreInvokes("put");
+
+    // android.util.SparseArray.set added in API 31.
+    registerTarget(AndroidApiLevel.S, 1);
   }
 
-  private static byte[] getSparseArray() throws IOException {
-    return transformer(SparseArray.class).setClassDescriptor(SPARSE_ARRAY_DESCRIPTOR).transform();
+  private static byte[] getSparseArray(TestParameters parameters) throws IOException {
+    if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.S)) {
+      assert parameters.getRuntime().asDex().getVm().isNewerThanOrEqual(DexVm.ART_12_0_0_HOST);
+      return transformer(SparseArrayAndroid12.class)
+          .setClassDescriptor(SPARSE_ARRAY_DESCRIPTOR)
+          .transform();
+    } else {
+      return transformer(SparseArray.class).setClassDescriptor(SPARSE_ARRAY_DESCRIPTOR).transform();
+    }
   }
 
   private static byte[] getTestRunner() throws IOException {
@@ -56,6 +69,17 @@
     }
   }
 
+  public static class SparseArrayAndroid12 {
+    public void set(int index, Object value) {
+      TestRunner.doAssertEquals(42, index);
+      TestRunner.doAssertEquals("Forty two", value);
+    }
+
+    public void put(int index, Object value) {
+      TestRunner.doFail("put should not be called");
+    }
+  }
+
   public static class TestRunner extends MiniAssert {
 
     public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java
index c8ddd5d..2d6b833 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -134,6 +135,10 @@
 
   @Test
   public void testTime() throws Exception {
+    if (parameters.getRuntime().asDex().getVm().isEqualTo(DexVm.ART_12_0_0_HOST)) {
+      // TODO(b/197078988): Many additional tests fails with Android 12.
+      return;
+    }
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String verbosity = "2";
     D8TestCompileResult compileResult =
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPrivateStaticResolutionInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPrivateStaticResolutionInvokeVirtualTest.java
index cd2b968..363e539 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPrivateStaticResolutionInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPrivateStaticResolutionInvokeVirtualTest.java
@@ -111,13 +111,6 @@
         result.assertSuccessWithOutput(EXPECTED);
         return;
       }
-      if (!unexpectedArtFailure() && !parameters.canUseDefaultAndStaticInterfaceMethods()) {
-        assert false : "Dead code until future ART behavior change. See b/152199517";
-        // Desugaring will insert a forwarding bridge which will hide the "invalid invoke" case.
-        // Thus, a future ART runtime that does not have the invalid IAE for the private override
-        // will end up calling the forward method to I.m.
-        result.assertSuccessWithOutput(EXPECTED);
-      }
       // The expected behavior is IAE since the resolved method is private.
       result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
       return;
@@ -142,7 +135,8 @@
 
   private boolean unexpectedArtFailure() {
     return parameters.isDexRuntime()
-        && parameters.getRuntime().asDex().getVm().isNewerThan(DexVm.ART_6_0_1_HOST);
+        && parameters.getRuntime().asDex().getVm().isNewerThan(DexVm.ART_6_0_1_HOST)
+        && parameters.getRuntime().asDex().getVm().isOlderThan(DexVm.ART_12_0_0_HOST);
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest.java
index 92f5365..12869e2 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest.java
@@ -116,7 +116,8 @@
 
     if (isR8
         && parameters.isDexRuntime()
-        && parameters.getDexRuntimeVersion().isNewerThan(Version.V6_0_1)) {
+        && parameters.getDexRuntimeVersion().isNewerThan(Version.V6_0_1)
+        && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0)) {
       // TODO(b/182255398): This should be EXPECTED.
       result.assertSuccessWithOutput(EXPECTED_R8);
       return;
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceWithClinitAndNoStaticMethodReferencesTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceWithClinitAndNoStaticMethodReferencesTest.java
new file mode 100644
index 0000000..7589f8a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceWithClinitAndNoStaticMethodReferencesTest.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugaring.interfacemethods;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceWithClinitAndNoStaticMethodReferencesTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("allocated A", "called A.m");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public InterfaceWithClinitAndNoStaticMethodReferencesTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForDesugaring(parameters)
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InterfaceWithClinitAndNoStaticMethodReferencesTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  static class A {
+
+    A() {
+      System.out.println("allocated A");
+    }
+
+    void m() {
+      System.out.println("called A.m");
+    }
+  }
+
+  interface I {
+
+    A a = new A();
+
+    static void f() {
+      System.out.println("unused");
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      I.a.m();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceWithClinitAndNoStaticMethodsTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceWithClinitAndNoStaticMethodsTest.java
new file mode 100644
index 0000000..414520f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceWithClinitAndNoStaticMethodsTest.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugaring.interfacemethods;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceWithClinitAndNoStaticMethodsTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("allocated A", "called A.m");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public InterfaceWithClinitAndNoStaticMethodsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForDesugaring(parameters)
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InterfaceWithClinitAndNoStaticMethodsTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  static class A {
+
+    A() {
+      System.out.println("allocated A");
+    }
+
+    void m() {
+      System.out.println("called A.m");
+    }
+  }
+
+  interface I {
+
+    A a = new A();
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      I.a.m();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationLibraryLambdaPropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationLibraryLambdaPropagationTest.java
index d53a470..9b42105 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationLibraryLambdaPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationLibraryLambdaPropagationTest.java
@@ -45,14 +45,12 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(CallSiteOptimizationLibraryLambdaPropagationTest.class)
         .addKeepMainRule(TestClass.class)
-        .applyIf(
-            enableExperimentalArgumentPropagation,
-            builder ->
-                builder.addOptionsModification(
-                    options ->
-                        options
-                            .callSiteOptimizationOptions()
-                            .setEnableExperimentalArgumentPropagation()))
+        .addOptionsModification(
+            options ->
+                options
+                    .callSiteOptimizationOptions()
+                    .setEnableExperimentalArgumentPropagation(
+                        enableExperimentalArgumentPropagation))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationPinnedMethodOverridePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationPinnedMethodOverridePropagationTest.java
index 2bf8f8d..ea88ead 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationPinnedMethodOverridePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationPinnedMethodOverridePropagationTest.java
@@ -63,14 +63,12 @@
                         + "Arg getArg2(); \npublic static "
                         + CLASS_PREFIX
                         + "Call getCaller(); \n}"))
-            .applyIf(
-                enableExperimentalArgumentPropagation,
-                builder ->
-                    builder.addOptionsModification(
-                        options ->
-                            options
-                                .callSiteOptimizationOptions()
-                                .setEnableExperimentalArgumentPropagation()))
+            .addOptionsModification(
+                options ->
+                    options
+                        .callSiteOptimizationOptions()
+                        .setEnableExperimentalArgumentPropagation(
+                            enableExperimentalArgumentPropagation))
             .enableNoVerticalClassMergingAnnotations()
             .enableNoHorizontalClassMergingAnnotations()
             .enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationProgramLambdaPropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationProgramLambdaPropagationTest.java
index 6d8e9d4..2771975 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationProgramLambdaPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationProgramLambdaPropagationTest.java
@@ -38,14 +38,12 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(CallSiteOptimizationProgramLambdaPropagationTest.class)
         .addKeepMainRule(TestClass.class)
-        .applyIf(
-            enableExperimentalArgumentPropagation,
-            builder ->
-                builder.addOptionsModification(
-                    options ->
-                        options
-                            .callSiteOptimizationOptions()
-                            .setEnableExperimentalArgumentPropagation()))
+        .addOptionsModification(
+            options ->
+                options
+                    .callSiteOptimizationOptions()
+                    .setEnableExperimentalArgumentPropagation(
+                        enableExperimentalArgumentPropagation))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithInvokeCustomTargetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithInvokeCustomTargetTest.java
index d0d2d22..5edca84 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithInvokeCustomTargetTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithInvokeCustomTargetTest.java
@@ -67,14 +67,12 @@
         .addProgramClassFileData(getProgramClassFileData())
         .addKeepMainRule(TestClass.class)
         .addKeepMethodRules(methodFromMethod(TestClass.class.getDeclaredMethod("bar", int.class)))
-        .applyIf(
-            enableExperimentalArgumentPropagation,
-            builder ->
-                builder.addOptionsModification(
-                    options ->
-                        options
-                            .callSiteOptimizationOptions()
-                            .setEnableExperimentalArgumentPropagation()))
+        .addOptionsModification(
+            options ->
+                options
+                    .callSiteOptimizationOptions()
+                    .setEnableExperimentalArgumentPropagation(
+                        enableExperimentalArgumentPropagation))
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithLambdaTargetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithLambdaTargetTest.java
index 4db8c2f..20827d8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithLambdaTargetTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithLambdaTargetTest.java
@@ -38,14 +38,12 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(CallSiteOptimizationWithLambdaTargetTest.class)
         .addKeepMainRule(TestClass.class)
-        .applyIf(
-            enableExperimentalArgumentPropagation,
-            builder ->
-                builder.addOptionsModification(
-                    options ->
-                        options
-                            .callSiteOptimizationOptions()
-                            .setEnableExperimentalArgumentPropagation()))
+        .addOptionsModification(
+            options ->
+                options
+                    .callSiteOptimizationOptions()
+                    .setEnableExperimentalArgumentPropagation(
+                        enableExperimentalArgumentPropagation))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/KeptMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/KeptMethodTest.java
index b579686..452e472 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/KeptMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/KeptMethodTest.java
@@ -48,14 +48,12 @@
         .addInnerClasses(KeptMethodTest.class)
         .addKeepMainRule(MAIN)
         .addKeepClassAndMembersRules(A.class)
-        .applyIf(
-            enableExperimentalArgumentPropagation,
-            builder ->
-                builder.addOptionsModification(
-                    options ->
-                        options
-                            .callSiteOptimizationOptions()
-                            .setEnableExperimentalArgumentPropagation()))
+        .addOptionsModification(
+            options ->
+                options
+                    .callSiteOptimizationOptions()
+                    .setEnableExperimentalArgumentPropagation(
+                        enableExperimentalArgumentPropagation))
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addOptionsModification(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java
index ea7641f..2d6bec7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java
@@ -63,14 +63,12 @@
         .addOptionsModification(
             o ->
                 o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect)
-        .applyIf(
-            enableExperimentalArgumentPropagation,
-            builder ->
-                builder.addOptionsModification(
-                    options ->
-                        options
-                            .callSiteOptimizationOptions()
-                            .setEnableExperimentalArgumentPropagation()))
+        .addOptionsModification(
+            options ->
+                options
+                    .callSiteOptimizationOptions()
+                    .setEnableExperimentalArgumentPropagation(
+                        enableExperimentalArgumentPropagation))
         .enableInliningAnnotations()
         .setMinApi(parameters.getRuntime())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
index 55daa59..e3fa7d7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
-import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -52,7 +52,7 @@
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
             })
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(CallSiteOptimizationOptions::enableConstantPropagationForTesting)
+        .addOptionsModification(InternalOptions::enableConstantArgumentPropagationForTesting)
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("non-null")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
index 9f25fdb..93594ff 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
-import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -49,7 +49,7 @@
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
-        .addOptionsModification(CallSiteOptimizationOptions::enableConstantPropagationForTesting)
+        .addOptionsModification(InternalOptions::enableConstantArgumentPropagationForTesting)
         .addOptionsModification(
             o -> {
               // To prevent invoke-interface from being rewritten to invoke-virtual w/ a single
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
index 3a00d96..096362a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
-import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -50,7 +50,7 @@
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
             })
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(CallSiteOptimizationOptions::enableConstantPropagationForTesting)
+        .addOptionsModification(InternalOptions::enableConstantArgumentPropagationForTesting)
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("non-null")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
index 5b3b309..e254dc6 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
-import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -53,7 +53,7 @@
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
             })
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(CallSiteOptimizationOptions::enableConstantPropagationForTesting)
+        .addOptionsModification(InternalOptions::enableConstantArgumentPropagationForTesting)
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("non-null", "null")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsRequireNonNullElseTest.java b/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsRequireNonNullElseTest.java
index 39acb1e..bde8237 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsRequireNonNullElseTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsRequireNonNullElseTest.java
@@ -36,6 +36,16 @@
   }
 
   @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(getProgramClassFileData())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Foo", "Bar", "Expected NPE");
+  }
+
+  @Test
   public void test() throws Exception {
     testForR8(parameters.getBackend())
         .addProgramClassFileData(getProgramClassFileData())
@@ -62,7 +72,7 @@
                   testNullArgumentMethodSubject, not(invokesMethodWithName("requireNonNullElse")));
             })
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines("Foo", "Bar");
+        .assertSuccessWithOutputLines("Foo", "Bar", "Expected NPE");
   }
 
   private byte[] getProgramClassFileData() throws IOException {
@@ -77,6 +87,12 @@
     public static void main(String[] args) {
       testNonNullArgument();
       testNullArgument();
+      try {
+        testNullArgumentAndNullDefaultValue();
+        System.out.println("Unexpected");
+      } catch (NullPointerException e) {
+        System.out.println("Expected NPE");
+      }
     }
 
     @NeverInline
@@ -88,6 +104,11 @@
     static void testNullArgument() {
       System.out.println(Mock.requireNonNullElse(null, "Bar"));
     }
+
+    @NeverInline
+    static void testNullArgumentAndNullDefaultValue() {
+      System.out.println(Mock.requireNonNullElse(null, null));
+    }
   }
 
   // References to this class are rewritten to java.util.Objects by transformation.
diff --git a/src/test/java/com/android/tools/r8/naming/MissingReferenceNamingClashTest.java b/src/test/java/com/android/tools/r8/naming/MissingReferenceNamingClashTest.java
new file mode 100644
index 0000000..1082d2e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/MissingReferenceNamingClashTest.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming;
+
+import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** This is a reproduction of b/196406764 */
+@RunWith(Parameterized.class)
+public class MissingReferenceNamingClashTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MissingReferenceNamingClashTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    // The references to Missing will be rewritten to a.a but the definition will not be present.
+    String newDescriptor = "La/a;";
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, Main.class)
+        .addProgramClassFileData(
+            transformer(Anno.class)
+                .replaceClassDescriptorInMembers(descriptor(Missing.class), newDescriptor)
+                .transform())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(Anno.class)
+        .addKeepClassRulesWithAllowObfuscation(A.class)
+        .addKeepRuntimeVisibleAnnotations()
+        .addDontWarn(descriptorToJavaType(newDescriptor))
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject aSubject = inspector.clazz(A.class);
+              assertThat(aSubject, isPresent());
+              assertEquals("La/b;", aSubject.getFinalDescriptor());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A::foo");
+  }
+
+  /* Will be missing on input */
+  public enum Missing {
+    FOO;
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.TYPE)
+  public @interface Anno {
+
+    /* Rewritten to a.a */ Missing missing();
+  }
+
+  public static class A {
+
+    public void foo() {
+      System.out.println("A::foo");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new A().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/PackageMinificationWithDontOptimizeTest.java b/src/test/java/com/android/tools/r8/naming/PackageMinificationWithDontOptimizeTest.java
new file mode 100644
index 0000000..45c9f14
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/PackageMinificationWithDontOptimizeTest.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.util.Properties;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PackageMinificationWithDontOptimizeTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addDontOptimize()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject classSubject = inspector.clazz(TestClass.class);
+              assertThat(classSubject, isPresent());
+              assertEquals("a.a", classSubject.getFinalName());
+            });
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      TestClass.getProperty("abc");
+      TestClass.getProperty("def");
+    }
+  }
+
+  public static class TestClass {
+
+    private static final Properties properties = new Properties();
+
+    public static String getProperty(String key) {
+      return properties.getProperty(key);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
index 1fbc7cf..7a4914a 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
@@ -12,7 +12,6 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
@@ -76,12 +75,6 @@
     assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
   }
 
-  @Override
-  public void configure(R8TestBuilder<?> builder) {
-    // Enable pruning of lambda synthetics in retrace.
-    builder.enableExperimentalMapFileVersion();
-  }
-
   @Test
   public void testSourceFileAndLineNumberTable() throws Exception {
     runTest(ImmutableList.of("-keepattributes SourceFile,LineNumberTable"), this::checkIsSame);
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodDirectRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodDirectRetraceTest.java
index b0a87ee..2cbb911 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodDirectRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodDirectRetraceTest.java
@@ -48,7 +48,6 @@
   @Override
   public void configure(R8TestBuilder<?> builder) {
     builder.enableInliningAnnotations();
-    builder.enableExperimentalMapFileVersion();
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsInlineIntoStaticRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsInlineIntoStaticRetraceTest.java
index 38ef3038..56f306c 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsInlineIntoStaticRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsInlineIntoStaticRetraceTest.java
@@ -38,7 +38,6 @@
   @Override
   public void configure(R8TestBuilder<?> builder) {
     builder.enableInliningAnnotations();
-    builder.enableExperimentalMapFileVersion();
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/StaticMethodWithConstantArgumentTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/StaticMethodWithConstantArgumentTest.java
index 4e44d1f..9b246e1 100644
--- a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/StaticMethodWithConstantArgumentTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/StaticMethodWithConstantArgumentTest.java
@@ -44,7 +44,7 @@
             options -> {
               CallSiteOptimizationOptions callSiteOptimizationOptions =
                   options.callSiteOptimizationOptions();
-              callSiteOptimizationOptions.setEnableExperimentalArgumentPropagation();
+              callSiteOptimizationOptions.setEnableExperimentalArgumentPropagation(true);
               callSiteOptimizationOptions.setEnableConstantPropagation();
             })
         .enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/StaticMethodWithConstantArgumentThroughCallChainTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/StaticMethodWithConstantArgumentThroughCallChainTest.java
index b0387b2..45cd552 100644
--- a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/StaticMethodWithConstantArgumentThroughCallChainTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/StaticMethodWithConstantArgumentThroughCallChainTest.java
@@ -44,7 +44,7 @@
             options -> {
               CallSiteOptimizationOptions callSiteOptimizationOptions =
                   options.callSiteOptimizationOptions();
-              callSiteOptimizationOptions.setEnableExperimentalArgumentPropagation();
+              callSiteOptimizationOptions.setEnableExperimentalArgumentPropagation(true);
               callSiteOptimizationOptions.setEnableConstantPropagation();
             })
         .enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageLambdaMissingInterfaceTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageLambdaMissingInterfaceTest.java
index df0bfa6..2265ba0 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageLambdaMissingInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageLambdaMissingInterfaceTest.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.repackage;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
-import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.startsWith;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
@@ -52,10 +52,6 @@
         .inspect(
             inspector -> {
               // Find the generated lambda class
-              if (!parameters.isDexRuntime()) {
-                assertThat(ClassWithLambda.class, isNotRepackaged(inspector));
-                return;
-              }
               if (repackage) {
                 assertThat(ClassWithLambda.class, isRepackaged(inspector));
               } else {
@@ -66,7 +62,8 @@
                   clazz -> {
                     if (clazz.isSynthesizedJavaLambdaClass()) {
                       assertThat(
-                          clazz.getFinalName(), containsString(Main.class.getPackage().getName()));
+                          clazz.getFinalName(),
+                          startsWith(repackage ? getRepackagePackage() : "a."));
                     }
                   });
             })
@@ -74,7 +71,7 @@
         .run(parameters.getRuntime(), Main.class);
   }
 
-  interface MissingInterface {
+  public interface MissingInterface {
 
     void bar(int x);
   }
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageMissingMemberReferenceTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageMissingMemberReferenceTest.java
index 9dc8746..8a40226 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageMissingMemberReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageMissingMemberReferenceTest.java
@@ -4,10 +4,10 @@
 
 package com.android.tools.r8.repackage;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -25,16 +25,16 @@
 
   @Test
   public void testR8WithoutRepackaging() throws Exception {
-    runTest(false).assertSuccessWithOutputLines(EXPECTED);
+    runTest(false);
   }
 
   @Test
   public void testR8() throws Exception {
-    runTest(true).assertSuccessWithOutputLines(EXPECTED);
+    runTest(true);
   }
 
-  private R8TestRunResult runTest(boolean repackage) throws Exception {
-    return testForR8(parameters.getBackend())
+  private void runTest(boolean repackage) throws Exception {
+    testForR8(parameters.getBackend())
         .addProgramClasses(ClassWithMissingReferenceInCode.class, Main.class)
         .addKeepMainRule(Main.class)
         .applyIf(repackage, this::configureRepackaging)
@@ -43,13 +43,20 @@
         .enableInliningAnnotations()
         .compile()
         .inspect(
-            inspector ->
-                assertThat(ClassWithMissingReferenceInCode.class, isNotRepackaged(inspector)))
+            inspector -> {
+              if (repackage) {
+                assertThat(ClassWithMissingReferenceInCode.class, isRepackaged(inspector));
+              } else {
+                assertThat(
+                    inspector.clazz(ClassWithMissingReferenceInCode.class), isPresentAndRenamed());
+              }
+            })
         .addRunClasspathClasses(MissingReference.class)
-        .run(parameters.getRuntime(), Main.class);
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
   }
 
-  static class MissingReference {
+  public static class MissingReference {
     public static void doSomething() {
       System.out.println("MissingReference::doSomething");
     }
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageMissingSuperInterfaceTestTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageMissingSuperInterfaceTestTest.java
index c853a99..e5ffadc 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageMissingSuperInterfaceTestTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageMissingSuperInterfaceTestTest.java
@@ -4,12 +4,13 @@
 
 package com.android.tools.r8.repackage;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -26,16 +27,16 @@
 
   @Test
   public void testR8WithoutRepackaging() throws Exception {
-    runTest(false).assertSuccessWithOutputLines(EXPECTED);
+    runTest(false);
   }
 
   @Test
   public void testR8() throws Exception {
-    runTest(true).assertSuccessWithOutputLines(EXPECTED);
+    runTest(true);
   }
 
-  private R8TestRunResult runTest(boolean repackage) throws Exception {
-    return testForR8(parameters.getBackend())
+  private void runTest(boolean repackage) throws Exception {
+    testForR8(parameters.getBackend())
         .addProgramClasses(ClassImplementingMissingInterface.class, Main.class)
         .addKeepMainRule(Main.class)
         .applyIf(repackage, this::configureRepackaging)
@@ -46,13 +47,20 @@
         .compile()
         .inspect(
             inspector -> {
-              assertThat(ClassImplementingMissingInterface.class, isNotRepackaged(inspector));
+              if (repackage) {
+                assertThat(ClassImplementingMissingInterface.class, isRepackaged(inspector));
+              } else {
+                // The class is minified.
+                ClassSubject clazz = inspector.clazz(ClassImplementingMissingInterface.class);
+                assertThat(clazz, isPresentAndRenamed());
+              }
             })
         .addRunClasspathClasses(MissingInterface.class)
-        .run(parameters.getRuntime(), Main.class);
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
   }
 
-  private interface MissingInterface {
+  public interface MissingInterface {
 
     void bar();
   }
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageMissingSuperTypeTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageMissingSuperTypeTest.java
index 5880f86..af4995f 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageMissingSuperTypeTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageMissingSuperTypeTest.java
@@ -4,11 +4,11 @@
 
 package com.android.tools.r8.repackage;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -32,16 +32,16 @@
 
   @Test
   public void testR8WithoutRepackaging() throws Exception {
-    runTest(false).assertSuccessWithOutputLines(EXPECTED);
+    runTest(false);
   }
 
   @Test
   public void testR8() throws Exception {
-    runTest(true).assertSuccessWithOutputLines(EXPECTED);
+    runTest(true);
   }
 
-  private R8TestRunResult runTest(boolean repackage) throws Exception {
-    return testForR8(parameters.getBackend())
+  private void runTest(boolean repackage) throws Exception {
+    testForR8(parameters.getBackend())
         .addProgramClasses(
             ClassWithSuperCall.class,
             ClassWithoutSuperCall.class,
@@ -56,14 +56,20 @@
         .compile()
         .inspect(
             inspector -> {
-              assertThat(ClassWithSuperCall.class, isNotRepackaged(inspector));
-              assertThat(ClassWithoutSuperCall.class, isNotRepackaged(inspector));
+              if (repackage) {
+                assertThat(ClassWithSuperCall.class, isRepackaged(inspector));
+                assertThat(ClassWithoutSuperCall.class, isRepackaged(inspector));
+              } else {
+                assertThat(inspector.clazz(ClassWithSuperCall.class), isPresentAndRenamed());
+                assertThat(inspector.clazz(ClassWithoutSuperCall.class), isPresentAndRenamed());
+              }
             })
         .addRunClasspathClasses(MissingSuperType.class)
-        .run(parameters.getRuntime(), Main.class);
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
   }
 
-  static class MissingSuperType {
+  public static class MissingSuperType {
 
     public void foo() {
       System.out.println("MissingSuperType::foo");
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageMissingTypeCollisionTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageMissingTypeCollisionTest.java
new file mode 100644
index 0000000..2fc2cb0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageMissingTypeCollisionTest.java
@@ -0,0 +1,143 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.R8CompatTestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/* This is a reproduction of b/196406764 where a collision will appear when repackaging */
+@RunWith(Parameterized.class)
+public class RepackageMissingTypeCollisionTest extends RepackageTestBase {
+
+  public RepackageMissingTypeCollisionTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    String newMissingTypeName =
+        getRepackagePackage() + (isFlattenPackageHierarchy() ? ".a.a" : ".a");
+    String newMissingDescriptor = DescriptorUtils.javaTypeToDescriptor(newMissingTypeName);
+    String newATypeName = A.class.getPackage().getName() + ".a";
+    String newADescriptor = DescriptorUtils.javaTypeToDescriptor(newATypeName);
+    testForJvm()
+        .addProgramClassFileData(
+            transformer(A.class).setClassDescriptor(newADescriptor).transform(),
+            transformer(Anno.class)
+                .replaceClassDescriptorInMembers(descriptor(Missing.class), newMissingDescriptor)
+                .replaceClassDescriptorInAnnotationDefault(
+                    descriptor(Missing.class), newMissingDescriptor)
+                .transform(),
+            transformer(Main.class)
+                .replaceClassDescriptorInMethodInstructions(descriptor(A.class), newADescriptor)
+                .replaceClassDescriptorInMethodInstructions(
+                    descriptor(Missing.class), newMissingDescriptor)
+                .transform())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
+  }
+
+  @Test
+  public void testRepackageMissingCollision() throws Exception {
+    testMissingReference(true)
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject clazz = inspector.clazz(getNewATypeName());
+              assertThat(clazz, isPresentAndRenamed());
+              assertEquals(
+                  getRepackagePackage() + (isFlattenPackageHierarchy() ? ".a.b" : ".b"),
+                  clazz.getFinalName());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
+  }
+
+  @Test
+  public void testNoRepackage() throws Exception {
+    testMissingReference(false)
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
+  }
+
+  private String getNewMissingTypeName() {
+    return getRepackagePackage() + (isFlattenPackageHierarchy() ? ".a.a" : ".a");
+  }
+
+  private String getNewATypeName() {
+    return A.class.getPackage().getName() + ".a";
+  }
+
+  private R8CompatTestBuilder testMissingReference(boolean repackage) throws Exception {
+    // The references to Missing will be rewritten to <repackage>.a but the definition will not be
+    // present.
+    String newMissingDescriptor = DescriptorUtils.javaTypeToDescriptor(getNewMissingTypeName());
+    String newADescriptor = DescriptorUtils.javaTypeToDescriptor(getNewATypeName());
+    return testForR8Compat(parameters.getBackend())
+        .addProgramClassFileData(
+            transformer(A.class).setClassDescriptor(newADescriptor).transform(),
+            transformer(Anno.class)
+                .replaceClassDescriptorInMembers(descriptor(Missing.class), newMissingDescriptor)
+                .replaceClassDescriptorInAnnotationDefault(
+                    descriptor(Missing.class), newMissingDescriptor)
+                .transform(),
+            transformer(Main.class)
+                .replaceClassDescriptorInMethodInstructions(descriptor(A.class), newADescriptor)
+                .replaceClassDescriptorInMethodInstructions(
+                    descriptor(Missing.class), newMissingDescriptor)
+                .transform())
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(Anno.class)
+        .addKeepRuntimeVisibleAnnotations()
+        .applyIf(repackage, this::configureRepackaging)
+        .setMinApi(parameters.getApiLevel())
+        .addDontWarn(getNewMissingTypeName())
+        .addOptionsModification(internalOptions -> internalOptions.enableEnumUnboxing = false)
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged);
+  }
+
+  /* Will be missing on input */
+  public enum Missing {
+    foo;
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.TYPE)
+  public @interface Anno {
+
+    /* Renamed to <repackage>.a */ Missing missing() default Missing.foo;
+  }
+
+  @Anno
+  public enum /* renamed to a */ A {
+    foo;
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      Anno annotation = A.class.getAnnotation(Anno.class);
+      System.out.println(annotation.missing());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java b/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
index f7aae45..252dcc5 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
@@ -66,7 +66,6 @@
   public void testD8() throws Exception {
     testForD8(parameters.getBackend())
         .internalEnableMappingOutput()
-        .enableExperimentalMapFileVersion()
         .addInnerClasses(getClass())
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
@@ -102,7 +101,6 @@
         "Skip R8/CF for min-api > 1 (R8/CF does not desugar)",
         parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
     testForR8(parameters.getBackend())
-        .enableExperimentalMapFileVersion()
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .addKeepPackageNamesRule(getClass().getPackage())
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 efa7b4e..e110f57 100644
--- a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
@@ -74,6 +74,7 @@
               case V4_0_4:
               case V4_4_4:
               case V10_0_0:
+              case V12_0_0:
                 return StringUtils.joinLines("Hello!", "Goodbye!", "");
 
               case V7_0_0:
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index fb2db95..ea7b6cc 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromDescriptor;
 import static com.android.tools.r8.utils.StringUtils.replaceAll;
 import static org.objectweb.asm.Opcodes.ASM7;
+import static org.objectweb.asm.Opcodes.ASM9;
 
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
@@ -740,6 +741,57 @@
         });
   }
 
+  public ClassFileTransformer replaceClassDescriptorInMembers(
+      String oldDescriptor, String newDescriptor) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public FieldVisitor visitField(
+              int access, String name, String descriptor, String signature, Object value) {
+            return super.visitField(
+                access,
+                name,
+                replaceAll(descriptor, oldDescriptor, newDescriptor),
+                signature,
+                value);
+          }
+
+          @Override
+          public MethodVisitor visitMethod(
+              int access, String name, String descriptor, String signature, String[] exceptions) {
+            return super.visitMethod(
+                access,
+                name,
+                replaceAll(descriptor, oldDescriptor, newDescriptor),
+                signature,
+                exceptions);
+          }
+        });
+  }
+
+  public ClassFileTransformer replaceClassDescriptorInAnnotationDefault(
+      String oldDescriptor, String newDescriptor) {
+    return addMethodTransformer(
+        new MethodTransformer() {
+
+          @Override
+          public AnnotationVisitor visitAnnotationDefault() {
+            return new AnnotationVisitor(ASM9, super.visitAnnotationDefault()) {
+              @Override
+              public void visit(String name, Object value) {
+                super.visit(name, value);
+              }
+
+              @Override
+              public void visitEnum(String name, String descriptor, String value) {
+                super.visitEnum(
+                    name, descriptor.equals(oldDescriptor) ? newDescriptor : descriptor, value);
+              }
+            };
+          }
+        });
+  }
+
   public ClassFileTransformer replaceClassDescriptorInMethodInstructions(
       String oldDescriptor, String newDescriptor) {
     return addMethodTransformer(
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 83bc2aa..e8070da 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -252,6 +252,19 @@
   utils.download_file_from_cloud_storage(source, dest)
   return dest
 
+
+def clean_config(file):
+  with open(file) as f:
+    lines = f.readlines()
+  with open(file, 'w') as f:
+    for line in lines:
+      if ('-injars' not in line and '-libraryjars' not in line and
+          '-print' not in line):
+        f.write(line)
+      else:
+        print('Removing from config line: \n%s' % line)
+
+
 def prepare_wrapper(dist, temp, jdkhome):
   wrapper_file = os.path.join(
       utils.REPO_ROOT,
@@ -329,6 +342,10 @@
     if compiler != 'd8' and dump.config_file():
       if hasattr(args, 'config_file_consumer') and args.config_file_consumer:
         args.config_file_consumer(dump.config_file())
+      else:
+        # If we get a dump from the wild we can't use -injars, -libraryjars or
+        # -print{mapping,usage}
+        clean_config(dump.config_file())
       cmd.extend(['--pg-conf', dump.config_file()])
     if dump.main_dex_rules_resource():
       cmd.extend(['--main-dex-rules', dump.main_dex_rules_resource()])
diff --git a/tools/linux/README.art-versions b/tools/linux/README.art-versions
index 9cf0717..ff57695 100644
--- a/tools/linux/README.art-versions
+++ b/tools/linux/README.art-versions
@@ -41,6 +41,33 @@
   repo init -u https://android.googlesource.com/platform/manifest -m aosp_master_manifest.xml
   <continue with repo sync as above>
 
+
+art-12.0.0 (Android S)
+---------------------
+Build from branch sc-beta4-release.
+
+export BRANCH=sc-beta4-release
+mkdir ${BRANCH}
+cd ${BRANCH}
+repo init -u https://android.googlesource.com/platform/manifest -b ${BRANCH}
+repo sync -cq -j24
+source build/envsetup.sh
+lunch aosp_redfin-userdebug
+m -j48
+m -j48 build-art
+m -j48 test-art-host
+
+Collected into tools/linux/host/art-12.0.0-beta4. The "host" path element is checked
+by the script for running Art.
+
+  cd <r8 checkout>
+  scripts/update-host-art.sh \
+     --android-checkout /usr/local/ssd/android/${BRANCH} \
+     --art-dir host/art-12.0.0-beta4 \
+     --android-product redfin
+
+(cd tools/linux/host; upload_to_google_storage.py -a --bucket r8-deps art-12.0.0-beta4)
+
 art-10.0.0 (Android Q)
 ---------------------
 Build from branch android-10.0.0_r14.
diff --git a/tools/linux/host/art-12.0.0-beta4.tar.gz.sha1 b/tools/linux/host/art-12.0.0-beta4.tar.gz.sha1
new file mode 100644
index 0000000..b95d909
--- /dev/null
+++ b/tools/linux/host/art-12.0.0-beta4.tar.gz.sha1
@@ -0,0 +1 @@
+9b6d0b061669e0fb1b09ba92c3650d5c7bdc9e85
\ No newline at end of file
diff --git a/tools/test.py b/tools/test.py
index 9c35352..6553358 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -23,6 +23,7 @@
 
 ALL_ART_VMS = [
     "default",
+    "12.0.0",
     "10.0.0",
     "9.0.0",
     "8.1.0",