Merge commit '9c538dbccb1c1252a36b6c430d47425d5ecdc625' into dev-release
diff --git a/.gitignore b/.gitignore
index d78f4a4..f16c1ba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -112,8 +112,8 @@
 third_party/kotlin/kotlin-compiler-1.3.72
 third_party/kotlin/kotlin-compiler-1.4.20.tar.gz
 third_party/kotlin/kotlin-compiler-1.4.20
-third_party/kotlin/kotlin-compiler-1.5.0-M2.tar.gz
-third_party/kotlin/kotlin-compiler-1.5.0-M2
+third_party/kotlin/kotlin-compiler-1.5.0.tar.gz
+third_party/kotlin/kotlin-compiler-1.5.0
 third_party/kotlinx-coroutines-1.3.6.tar.gz
 third_party/kotlinx-coroutines-1.3.6
 third_party/nest/*
diff --git a/build.gradle b/build.gradle
index 1dc777d..01d41fc 100644
--- a/build.gradle
+++ b/build.gradle
@@ -35,7 +35,7 @@
 
 ext {
     androidSupportVersion = '25.4.0'
-    asmVersion = '9.0'  // When updating update tools/asmifier.py and Toolhelper as well.
+    asmVersion = '9.2'  // When updating update tools/asmifier.py and Toolhelper as well.
     espressoVersion = '3.0.0'
     fastutilVersion = '7.2.0'
     guavaVersion = '23.0'
@@ -45,7 +45,7 @@
     mockitoVersion = '2.10.0'
     // The kotlin version is only here to specify the kotlin language level,
     // all kotlin compilations are done in tests.
-    kotlinVersion = '1.3.72'
+    kotlinVersion = '1.5.0'
     kotlinExtMetadataJVMVersion = '0.2.0'
     smaliVersion = '2.2b4'
     errorproneVersion = '2.3.2'
@@ -331,7 +331,7 @@
                 "kotlin/kotlin-compiler-1.3.41",
                 "kotlin/kotlin-compiler-1.3.72",
                 "kotlin/kotlin-compiler-1.4.20",
-                "kotlin/kotlin-compiler-1.5.0-M2",
+                "kotlin/kotlin-compiler-1.5.0",
                 "kotlinx-coroutines-1.3.6",
                 "openjdk/openjdk-rt-1.8",
                 "openjdk/desugar_jdk_libs",
diff --git a/src/main/java/com/android/tools/r8/DumpOptions.java b/src/main/java/com/android/tools/r8/DumpOptions.java
index 3b502b5..1c0e170 100644
--- a/src/main/java/com/android/tools/r8/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/DumpOptions.java
@@ -123,8 +123,7 @@
 
   private boolean hasDesugaredLibraryConfiguration() {
     return desugaredLibraryConfiguration != null
-        && desugaredLibraryConfiguration
-            != DesugaredLibraryConfiguration.EMPTY_DESUGARED_LIBRARY_CONFIGURATION;
+        && !desugaredLibraryConfiguration.isEmptyConfiguration();
   }
 
   public String getDesugaredLibraryJsonSource() {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 3bd0653..c2ee174 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -357,7 +357,7 @@
         }
 
         AnnotationRemover.Builder annotationRemoverBuilder =
-            options.isShrinking() ? AnnotationRemover.builder() : null;
+            options.isShrinking() ? AnnotationRemover.builder(Mode.INITIAL_TREE_SHAKING) : null;
         AppView<AppInfoWithLiveness> appViewWithLiveness =
             runEnqueuer(
                 annotationRemoverBuilder,
@@ -640,6 +640,7 @@
 
             assert appView.allMergedClasses().verifyAllSourcesPruned(appViewWithLiveness);
             assert appView.validateUnboxedEnumsHaveBeenPruned();
+            assert appView.withLiveness().appInfo().verifyNoIteratingOverPrunedClasses();
 
             processWhyAreYouKeepingAndCheckDiscarded(
                 appView.rootSet(),
@@ -653,12 +654,20 @@
                 executorService);
 
             // Remove annotations that refer to types that no longer exist.
-            AnnotationRemover.builder()
+            AnnotationRemover.builder(Mode.FINAL_TREE_SHAKING)
                 .build(appView.withLiveness(), removedClasses)
                 .run(executorService);
             new GenericSignatureRewriter(
                     appView, NamingLens.getIdentityLens(), genericContextBuilder)
                 .run(appView.appInfo().classes(), executorService);
+            assert appView.checkForTesting(
+                    () ->
+                        GenericSignatureCorrectnessHelper.createForVerification(
+                                appView,
+                                GenericSignatureContextBuilder.create(appView.appInfo().classes()))
+                            .run(appView.appInfo().classes())
+                            .isValid())
+                : "Could not validate generic signatures";
 
             // Synthesize fields for triggering class initializers.
             new ClassInitFieldSynthesizer(appViewWithLiveness).run(executorService);
@@ -738,6 +747,9 @@
       // need to build IR.
       appView.dexItemFactory().clearTypeElementsCache();
 
+      GenericSignatureContextBuilder genericContextBuilderBeforeFinalMerging =
+          GenericSignatureContextBuilder.create(appView.appInfo().classes());
+
       // Run horizontal class merging. This runs even if shrinking is disabled to ensure synthetics
       // are always merged.
       HorizontalClassMerger.createForFinalClassMerging(appView)
@@ -812,6 +824,17 @@
         options.syntheticProguardRulesConsumer.accept(synthesizedProguardRules);
       }
 
+      NamingLens prefixRewritingNamingLens =
+          PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView, namingLens);
+
+      timing.begin("MinifyKotlinMetadata");
+      new KotlinMetadataRewriter(appView, prefixRewritingNamingLens).runForR8(executorService);
+      timing.end();
+
+      new GenericSignatureRewriter(
+              appView, prefixRewritingNamingLens, genericContextBuilderBeforeFinalMerging)
+          .run(appView.appInfo().classes(), executorService);
+
       assert appView.checkForTesting(
               () ->
                   !options.isShrinking()
@@ -822,16 +845,6 @@
                           .isValid())
           : "Could not validate generic signatures";
 
-      NamingLens prefixRewritingNamingLens =
-          PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView, namingLens);
-
-      timing.begin("MinifyKotlinMetadata");
-      new KotlinMetadataRewriter(appView, prefixRewritingNamingLens).runForR8(executorService);
-      timing.end();
-
-      new GenericSignatureRewriter(appView, prefixRewritingNamingLens)
-          .run(appView.appInfo().classes(), executorService);
-
       new DesugaredLibraryKeepRuleGenerator(appView, prefixRewritingNamingLens)
           .runIfNecessary(timing);
 
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
index 124ea3f..f2502e2 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
@@ -11,29 +11,31 @@
 import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.TraversalContinuation;
-import java.util.IdentityHashMap;
-import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 public class AndroidApiReferenceLevelCache {
 
   private static final int BUILD_CACHE_TRESHOLD = 20;
 
-  private final Map<DexType, AndroidApiClass> apiTypeLookup;
-  private final Map<DexReference, AndroidApiLevel> apiMemberLookup = new IdentityHashMap<>();
+  private final ConcurrentHashMap<DexType, AndroidApiClass> apiTypeLookup;
+  private final ConcurrentHashMap<DexReference, AndroidApiLevel> apiMemberLookup =
+      new ConcurrentHashMap<>();
+  private final DesugaredLibraryConfiguration desugaredLibraryConfiguration;
   private final AppView<?> appView;
 
   private AndroidApiReferenceLevelCache(AppView<?> appView) {
-    this.appView = appView;
-    this.apiTypeLookup = new IdentityHashMap<>();
+    this(appView, new ConcurrentHashMap<>());
   }
 
   private AndroidApiReferenceLevelCache(
-      AppView<?> appView, Map<DexType, AndroidApiClass> apiTypeLookup) {
+      AppView<?> appView, ConcurrentHashMap<DexType, AndroidApiClass> apiTypeLookup) {
     this.appView = appView;
     this.apiTypeLookup = apiTypeLookup;
+    desugaredLibraryConfiguration = appView.options().desugaredLibraryConfiguration;
   }
 
   public static AndroidApiReferenceLevelCache create(AppView<?> appView) {
@@ -49,7 +51,7 @@
     }
     // The apiTypeLookup is build lazily except for the mocked api types that we define in tests
     // externally.
-    Map<DexType, AndroidApiClass> apiTypeLookup = new IdentityHashMap<>();
+    ConcurrentHashMap<DexType, AndroidApiClass> apiTypeLookup = new ConcurrentHashMap<>();
     appView
         .options()
         .apiModelingOptions()
@@ -78,6 +80,11 @@
     if (!clazz.isLibraryClass()) {
       return appView.options().minApiLevel;
     }
+    if (desugaredLibraryConfiguration.isSupported(reference, appView)) {
+      // If we end up desugaring the reference, the library classes is bridged by j$ which is part
+      // of the program.
+      return appView.options().minApiLevel;
+    }
     AndroidApiClass androidApiClass =
         apiTypeLookup.computeIfAbsent(
             contextType, type -> AndroidApiDatabaseBuilder.buildClass(type.asClassReference()));
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 40b630c..50362b0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -47,7 +47,7 @@
   }
 
   public final Origin origin;
-  public DexType type;
+  public final DexType type;
   public final ClassAccessFlags accessFlags;
   public DexType superType;
   public DexTypeList interfaces;
@@ -109,7 +109,7 @@
     this.type = type;
     setStaticFields(staticFields);
     setInstanceFields(instanceFields);
-    this.methodCollection = new MethodCollection(this, directMethods, virtualMethods);
+    this.methodCollection = MethodCollection.create(this, directMethods, virtualMethods);
     this.nestHost = nestHost;
     this.nestMembers = nestMembers;
     assert nestMembers != null;
@@ -181,6 +181,10 @@
     this.sourceFile = sourceFile;
   }
 
+  public Iterable<DexClassAndField> classFields() {
+    return Iterables.transform(fields(), field -> DexClassAndField.create(this, field));
+  }
+
   public Iterable<DexEncodedField> fields() {
     return fields(Predicates.alwaysTrue());
   }
@@ -204,6 +208,10 @@
     return methodCollection;
   }
 
+  public Iterable<DexClassAndMethod> classMethods() {
+    return Iterables.transform(methods(), method -> DexClassAndMethod.create(this, method));
+  }
+
   public Iterable<DexEncodedMethod> methods() {
     return methodCollection.methods();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
index 4aebe97..52cb7a9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
@@ -24,6 +24,10 @@
 
   public abstract AccessFlags<?> getAccessFlags();
 
+  public final DexAnnotationSet getAnnotations() {
+    return definition.annotations();
+  }
+
   @Override
   public DexType getContextType() {
     return getHolderType();
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
index 53cbfe8..2f18405 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -66,6 +66,14 @@
     return getReference().getParameters();
   }
 
+  public DexAnnotationSet getParameterAnnotation(int index) {
+    return getParameterAnnotations().get(index);
+  }
+
+  public final ParameterAnnotationsList getParameterAnnotations() {
+    return getDefinition().getParameterAnnotations();
+  }
+
   public DexProto getProto() {
     return getReference().getProto();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinition.java b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
index c2e8fae..25c339e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
@@ -100,19 +100,33 @@
 
   public abstract DexReference getReference();
 
-  private static <T extends DexDefinition> Stream<T> filter(
-      Stream<DexDefinition> stream,
-      Predicate<DexDefinition> pred,
-      Function<DexDefinition, T> f) {
+  private static <T> Stream<T> filter(
+      Stream<DexDefinition> stream, Predicate<DexDefinition> pred, Function<DexDefinition, T> f) {
     return stream.filter(pred).map(f);
   }
 
   public static Stream<DexEncodedField> filterDexEncodedField(Stream<DexDefinition> stream) {
-    return filter(stream, DexDefinition::isDexEncodedField, DexDefinition::asDexEncodedField);
+    return filterDexEncodedField(stream, DexDefinition::asDexEncodedField);
+  }
+
+  public static <T> Stream<T> filterDexEncodedField(
+      Stream<DexDefinition> stream, Function<DexEncodedField, T> transform) {
+    return filter(
+        stream,
+        DexDefinition::isDexEncodedField,
+        field -> transform.apply(field.asDexEncodedField()));
   }
 
   public static Stream<DexEncodedMethod> filterDexEncodedMethod(Stream<DexDefinition> stream) {
-    return filter(stream, DexDefinition::isDexEncodedMethod, DexDefinition::asDexEncodedMethod);
+    return filterDexEncodedMethod(stream, DexDefinition::asDexEncodedMethod);
+  }
+
+  public static <T> Stream<T> filterDexEncodedMethod(
+      Stream<DexDefinition> stream, Function<DexEncodedMethod, T> transform) {
+    return filter(
+        stream,
+        DexDefinition::isDexEncodedMethod,
+        field -> transform.apply(field.asDexEncodedMethod()));
   }
 
   public abstract boolean isStatic();
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 0dc90ad..2fdb279 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -389,6 +389,10 @@
     return getReference().getParameters();
   }
 
+  public DexType getReturnType() {
+    return getReference().getReturnType();
+  }
+
   public DexMethodSignature getSignature() {
     return new DexMethodSignature(getReference());
   }
@@ -848,6 +852,10 @@
     return classFileVersion;
   }
 
+  public CfVersion getClassFileVersionOrElse(CfVersion defaultValue) {
+    return hasClassFileVersion() ? getClassFileVersion() : defaultValue;
+  }
+
   public boolean hasClassFileVersion() {
     checkIfObsolete();
     return classFileVersion != null;
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 25cf31b..e8f5829 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1008,8 +1008,12 @@
 
   public class ByteMembers extends LibraryMembers {
 
+    public final DexMethod byteValue =
+        createMethod(boxedByteType, createProto(byteType), "byteValue");
     public final DexMethod toString =
         createMethod(boxedByteType, createProto(stringType), "toString");
+    public final DexMethod valueOf =
+        createMethod(boxedByteType, createProto(boxedByteType, byteType), "valueOf");
 
     private ByteMembers() {}
   }
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 5b3456c..64608dc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -672,25 +672,6 @@
     methodCollection.replaceVirtualMethod(virtualMethod, replacement);
   }
 
-  public void replaceInterfaces(List<ClassTypeSignature> newInterfaces) {
-    if (newInterfaces.isEmpty()) {
-      return;
-    }
-    clearInterfaces();
-    addExtraInterfaces(newInterfaces);
-  }
-
-  private void clearInterfaces() {
-    interfaces = DexTypeList.empty();
-    if (classSignature.hasSignature()) {
-      classSignature =
-          new ClassSignature(
-              classSignature.formalTypeParameters,
-              classSignature.superClassSignature,
-              ImmutableList.of());
-    }
-  }
-
   public void addExtraInterfaces(List<ClassTypeSignature> extraInterfaces) {
     if (extraInterfaces.isEmpty()) {
       return;
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeList.java b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
index 684dd7d..f399895 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -59,6 +59,10 @@
     return values[index];
   }
 
+  public DexType[] getBacking() {
+    return values;
+  }
+
   public DexTypeList keepIf(Predicate<DexType> predicate) {
     DexType[] filtered = ArrayUtils.filter(DexType[].class, values, predicate);
     if (filtered != values) {
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignature.java b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
index 419125d..920fbc1 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignature.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -15,6 +15,7 @@
 import com.google.common.collect.ImmutableList;
 import java.lang.reflect.GenericSignatureFormatError;
 import java.nio.CharBuffer;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Predicate;
 
@@ -293,6 +294,53 @@
     public ClassSignature toObjectBoundWithSameFormals(ClassTypeSignature objectBound) {
       return new ClassSignature(formalTypeParameters, objectBound, getEmptySuperInterfaces());
     }
+
+    public List<FieldTypeSignature> getGenericArgumentsToSuperType(DexType type) {
+      assert hasSignature();
+      if (superClassSignature.type == type) {
+        return superClassSignature.typeArguments;
+      }
+      for (ClassTypeSignature superInterfaceSignature : superInterfaceSignatures) {
+        if (superInterfaceSignature.type == type) {
+          return superInterfaceSignature.typeArguments;
+        }
+      }
+      return null;
+    }
+
+    public static ClassSignatureBuilder builder() {
+      return new ClassSignatureBuilder();
+    }
+
+    public static class ClassSignatureBuilder {
+
+      private List<FormalTypeParameter> formalTypeParameters = new ArrayList<>();
+      private ClassTypeSignature superClassSignature = null;
+      private List<ClassTypeSignature> superInterfaceSignatures = new ArrayList<>();
+
+      private ClassSignatureBuilder() {}
+
+      public ClassSignatureBuilder addFormalTypeParameters(List<FormalTypeParameter> formals) {
+        formalTypeParameters.addAll(formals);
+        return this;
+      }
+
+      public ClassSignatureBuilder setSuperClassSignature(ClassTypeSignature superClassSignature) {
+        this.superClassSignature = superClassSignature;
+        return this;
+      }
+
+      public ClassSignatureBuilder addInterface(ClassTypeSignature iface) {
+        superInterfaceSignatures.add(iface);
+        return this;
+      }
+
+      public ClassSignature build() {
+        ClassSignature classSignature =
+            new ClassSignature(formalTypeParameters, superClassSignature, superInterfaceSignatures);
+        return classSignature;
+      }
+    }
   }
 
   private static class InvalidClassSignature extends ClassSignature {
@@ -585,7 +633,7 @@
         return null;
       }
       List<FieldTypeSignature> rewrittenArguments =
-          visitor.visitTypeArguments(visitedType, typeArguments);
+          visitor.visitTypeArguments(type, visitedType, typeArguments);
       ClassTypeSignature rewrittenOuter = null;
       if (enclosingTypeSignature != null) {
         rewrittenOuter = visitor.visitEnclosing(enclosingTypeSignature, this);
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureContextBuilder.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureContextBuilder.java
index 00534ba..1b1eabf 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureContextBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureContextBuilder.java
@@ -6,8 +6,10 @@
 
 import static com.android.tools.r8.graph.GenericSignatureContextBuilder.TypeParameterContext.empty;
 
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.utils.WorkList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -25,26 +27,26 @@
 
   private static class TypeParameterSubstitutions {
 
-    private final Map<String, DexType> parametersWithBounds;
+    private final Map<String, FieldTypeSignature> parametersWithBounds;
 
-    private TypeParameterSubstitutions(Map<String, DexType> parametersWithBounds) {
+    private TypeParameterSubstitutions(Map<String, FieldTypeSignature> parametersWithBounds) {
       this.parametersWithBounds = parametersWithBounds;
     }
 
     private static TypeParameterSubstitutions create(List<FormalTypeParameter> formals) {
-      Map<String, DexType> map = new IdentityHashMap<>();
+      Map<String, FieldTypeSignature> map = new HashMap<>();
       formals.forEach(
           formal -> {
-            DexType bound = null;
             if (formal.getClassBound() != null
                 && formal.getClassBound().hasSignature()
                 && formal.getClassBound().isClassTypeSignature()) {
-              bound = formal.getClassBound().asClassTypeSignature().type;
+              map.put(formal.getName(), formal.getClassBound());
             } else if (!formal.getInterfaceBounds().isEmpty()
                 && formal.getInterfaceBounds().get(0).isClassTypeSignature()) {
-              bound = formal.getInterfaceBounds().get(0).asClassTypeSignature().type;
+              map.put(formal.getName(), formal.getInterfaceBounds().get(0));
+            } else {
+              map.put(formal.getName(), null);
             }
-            map.put(formal.getName(), bound);
           });
       return new TypeParameterSubstitutions(map);
     }
@@ -55,11 +57,11 @@
     private static final TypeParameterContext EMPTY =
         new TypeParameterContext(Collections.emptyMap(), Collections.emptySet());
 
-    private final Map<String, DexType> prunedParametersWithBounds;
+    private final Map<String, FieldTypeSignature> prunedParametersWithBounds;
     private final Set<String> liveParameters;
 
     private TypeParameterContext(
-        Map<String, DexType> prunedParametersWithBounds, Set<String> liveParameters) {
+        Map<String, FieldTypeSignature> prunedParametersWithBounds, Set<String> liveParameters) {
       this.prunedParametersWithBounds = prunedParametersWithBounds;
       this.liveParameters = liveParameters;
     }
@@ -81,7 +83,7 @@
       return liveParameters.contains(parameterName);
     }
 
-    public DexType getPrunedSubstitution(String parameterName) {
+    public FieldTypeSignature getPrunedSubstitution(String parameterName) {
       assert !isLiveParameter(parameterName);
       return prunedParametersWithBounds.get(parameterName);
     }
@@ -93,7 +95,7 @@
       HashSet<String> newLiveParameters = new HashSet<>();
       newLiveParameters.addAll(liveParameters);
       newLiveParameters.addAll(typeParameters);
-      HashMap<String, DexType> newPruned = new HashMap<>();
+      HashMap<String, FieldTypeSignature> newPruned = new HashMap<>();
       prunedParametersWithBounds.forEach(
           (name, type) -> {
             if (!typeParameters.contains(name)) {
@@ -103,11 +105,12 @@
       return new TypeParameterContext(newPruned, newLiveParameters);
     }
 
-    public TypeParameterContext addPrunedSubstitutions(Map<String, DexType> substitutions) {
+    public TypeParameterContext addPrunedSubstitutions(
+        Map<String, FieldTypeSignature> substitutions) {
       if (substitutions.isEmpty()) {
         return this;
       }
-      HashMap<String, DexType> newPruned = new HashMap<>();
+      HashMap<String, FieldTypeSignature> newPruned = new HashMap<>();
       newPruned.putAll(prunedParametersWithBounds);
       newPruned.putAll(substitutions);
       HashSet<String> newLiveParameters = new HashSet<>();
@@ -121,38 +124,6 @@
     }
   }
 
-  public static class AlwaysLiveTypeParameterContext extends TypeParameterContext {
-
-    private AlwaysLiveTypeParameterContext() {
-      super(Collections.emptyMap(), Collections.emptySet());
-    }
-
-    public static AlwaysLiveTypeParameterContext create() {
-      return new AlwaysLiveTypeParameterContext();
-    }
-
-    @Override
-    public boolean isLiveParameter(String parameterName) {
-      return true;
-    }
-
-    @Override
-    public DexType getPrunedSubstitution(String parameterName) {
-      assert false;
-      return null;
-    }
-
-    @Override
-    public TypeParameterContext addLiveParameters(Collection<String> typeParameters) {
-      return this;
-    }
-
-    @Override
-    public TypeParameterContext addPrunedSubstitutions(Map<String, DexType> substitutions) {
-      return this;
-    }
-  }
-
   private GenericSignatureContextBuilder(
       Map<DexReference, TypeParameterSubstitutions> formalsInfo,
       Map<DexReference, DexReference> enclosingInfo) {
@@ -160,7 +131,7 @@
     this.enclosingInfo = enclosingInfo;
   }
 
-  public static GenericSignatureContextBuilder create(List<DexProgramClass> programClasses) {
+  public static GenericSignatureContextBuilder create(Collection<DexProgramClass> programClasses) {
     Map<DexReference, TypeParameterSubstitutions> formalsInfo = new IdentityHashMap<>();
     Map<DexReference, DexReference> enclosingInfo = new IdentityHashMap<>();
     programClasses.forEach(
@@ -201,9 +172,27 @@
     return new GenericSignatureContextBuilder(formalsInfo, enclosingInfo);
   }
 
+  public static GenericSignatureContextBuilder createForSingleClass(
+      AppView<?> appView, DexProgramClass clazz) {
+    WorkList<DexProgramClass> workList = WorkList.newIdentityWorkList(clazz);
+    while (workList.hasNext()) {
+      DexProgramClass current = workList.next();
+      DexClass outer = null;
+      if (current.getEnclosingMethodAttribute() != null) {
+        outer = appView.definitionFor(current.getEnclosingMethodAttribute().getEnclosingType());
+      } else if (current.getInnerClassAttributeForThisClass() != null) {
+        outer = appView.definitionFor(current.getInnerClassAttributeForThisClass().getOuter());
+      }
+      if (outer != null && outer.isProgramClass()) {
+        workList.addIfNotSeen(outer.asProgramClass());
+      }
+    }
+    return create(workList.getSeenSet());
+  }
+
   public TypeParameterContext computeTypeParameterContext(
       AppView<?> appView, DexReference reference, Predicate<DexType> wasPruned) {
-    assert !wasPruned.test(reference.getContextType());
+    assert !wasPruned.test(reference.getContextType()) : "Building context for pruned type";
     return computeTypeParameterContext(appView, reference, wasPruned, false);
   }
 
@@ -218,27 +207,42 @@
     DexType contextType = reference.getContextType();
     // TODO(b/187035453): We should visit generic signatures in the enqueuer.
     DexClass clazz = appView.appInfo().definitionForWithoutExistenceAssert(contextType);
-    boolean prunedHere = seenPruned || clazz == null;
-    if (appView.hasLiveness()
-        && appView.withLiveness().appInfo().getMissingClasses().contains(contextType)) {
-      prunedHere = seenPruned;
-    }
+    boolean prunedHere = seenPruned;
+    // If the class cannot be looked up and it is not missing it was pruned here.
+    prunedHere |=
+        clazz == null
+            && appView.hasLiveness()
+            && !appView.withLiveness().appInfo().getMissingClasses().contains(contextType);
     // Lookup the formals in the enclosing context.
+    TypeParameterSubstitutions formalsInfo = this.formalsInfo.get(contextType);
+    // If formals has been pruned then the context is also pruned here.
+    prunedHere |=
+        clazz != null
+            && formalsInfo != null
+            && !formalsInfo.parametersWithBounds.isEmpty()
+            && clazz.getClassSignature().getFormalTypeParameters().isEmpty();
+    DexReference enclosingReference = enclosingInfo.get(contextType);
     TypeParameterContext typeParameterContext =
         computeTypeParameterContext(
                 appView,
-                enclosingInfo.get(contextType),
+                enclosingReference,
                 wasPruned,
                 prunedHere
-                    || hasPrunedRelationship(
-                        appView, enclosingInfo.get(contextType), contextType, wasPruned))
+                    || hasPrunedRelationship(appView, enclosingReference, contextType, wasPruned))
             // Add formals for the context
-            .combine(formalsInfo.get(contextType), prunedHere);
+            .combine(formalsInfo, prunedHere);
     if (!reference.isDexMethod()) {
       return typeParameterContext;
     }
-    prunedHere = prunedHere || clazz == null || clazz.lookupMethod(reference.asDexMethod()) == null;
-    return typeParameterContext.combine(formalsInfo.get(reference), prunedHere);
+    TypeParameterSubstitutions methodFormals = this.formalsInfo.get(reference);
+    if (clazz != null && !prunedHere) {
+      DexEncodedMethod method = clazz.lookupMethod(reference.asDexMethod());
+      prunedHere =
+          method == null
+              || (!methodFormals.parametersWithBounds.isEmpty()
+                  && method.getGenericSignature().getFormalTypeParameters().isEmpty());
+    }
+    return typeParameterContext.combine(methodFormals, prunedHere);
   }
 
   public boolean hasPrunedRelationship(
@@ -257,15 +261,9 @@
     }
     // TODO(b/187035453): We should visit generic signatures in the enqueuer.
     DexClass enclosingClass =
-        appView
-            .appInfo()
-            .definitionForWithoutExistenceAssert(
-                appView.graphLens().lookupClassType(enclosingReference.getContextType()));
+        appView.appInfo().definitionForWithoutExistenceAssert(enclosingReference.getContextType());
     DexClass enclosedClass =
-        appView
-            .appInfo()
-            .definitionForWithoutExistenceAssert(
-                appView.graphLens().lookupClassType(enclosedClassType));
+        appView.appInfo().definitionForWithoutExistenceAssert(enclosedClassType);
     if (enclosingClass == null || enclosedClass == null) {
       return true;
     }
@@ -279,12 +277,12 @@
     }
   }
 
-  public boolean hasGenericTypeVariables(
+  public static boolean hasGenericTypeVariables(
       AppView<?> appView, DexType type, Predicate<DexType> wasPruned) {
     if (wasPruned.test(type)) {
       return false;
     }
-    DexClass clazz = appView.definitionFor(appView.graphLens().lookupClassType(type));
+    DexClass clazz = appView.definitionFor(type);
     if (clazz == null || clazz.isNotProgramClass() || clazz.getClassSignature().isInvalid()) {
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
index 03f4259..1946512 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
@@ -234,8 +234,7 @@
       if (context.superType == appView.dexItemFactory().objectType
           && classSignature.superClassSignature().hasNoSignature()) {
         // We represent no signature as object.
-      } else if (context.superType
-          != appView.graphLens().lookupClassType(classSignature.superClassSignature().type())) {
+      } else if (context.superType != classSignature.superClassSignature().type()) {
         assert mode.doNotVerify() : "Super type inconsistency in generic signature";
         return INVALID_SUPER_TYPE;
       }
@@ -393,10 +392,7 @@
         }
       }
       // TODO(b/187035453): We should visit generic signatures in the enqueuer.
-      DexClass clazz =
-          appView
-              .appInfo()
-              .definitionForWithoutExistenceAssert(appView.graphLens().lookupClassType(type));
+      DexClass clazz = appView.appInfo().definitionForWithoutExistenceAssert(type);
       if (clazz == null) {
         // We do not know if the application of arguments works or not.
         return VALID;
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java b/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
index 56a6496..12ca041 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
@@ -26,6 +26,8 @@
   private final BiPredicate<DexType, DexType> enclosingPruned;
   private final Predicate<DexType> hasGenericTypeParameters;
   private final AppView<?> appView;
+  private final ClassTypeSignature objectArgument;
+  private boolean makeAllTypeArgumentsObject = false;
 
   private GenericSignaturePartialTypeArgumentApplier(
       AppView<?> appView,
@@ -36,6 +38,9 @@
     this.typeParameterContext = typeParameterContext;
     this.enclosingPruned = enclosingPruned;
     this.hasGenericTypeParameters = hasGenericTypeParameters;
+    objectArgument =
+        new ClassTypeSignature(appView.dexItemFactory().objectType)
+            .asArgument(WildcardIndicator.NONE);
   }
 
   public static GenericSignaturePartialTypeArgumentApplier build(
@@ -78,6 +83,10 @@
 
   @Override
   public DexType visitType(DexType type) {
+    // It is important that the type is not looked up in the applier. The type-parameter context is
+    // a mapping from fully applied -> old references, which may seem a bit odd, but that is simply
+    // because we do not rewrite the signatures in lock step with rewriting the app.
+    // The actual lookup will be performed in the GenericSignatureTypeRewriter.
     return type;
   }
 
@@ -117,8 +126,12 @@
 
   @Override
   public List<FieldTypeSignature> visitTypeArguments(
-      DexType type, List<FieldTypeSignature> typeArguments) {
-    if (typeArguments.isEmpty() || !hasGenericTypeParameters.test(type)) {
+      DexType originalType, DexType lookedUpType, List<FieldTypeSignature> typeArguments) {
+    assert originalType == lookedUpType;
+    if (typeArguments.isEmpty()) {
+      return typeArguments;
+    }
+    if (!hasGenericTypeParameters.test(appView.graphLens().lookupType(originalType))) {
       return getEmptyTypeArguments();
     }
     return ListUtils.mapOrElse(typeArguments, this::visitFieldTypeSignature);
@@ -145,7 +158,9 @@
   @Override
   public ClassTypeSignature visitEnclosing(
       ClassTypeSignature enclosingSignature, ClassTypeSignature enclosedSignature) {
-    if (enclosingPruned.test(enclosingSignature.type(), enclosedSignature.type())) {
+    DexType enclosingType = appView.graphLens().lookupType(enclosingSignature.type());
+    DexType enclosedType = appView.graphLens().lookupType(enclosedSignature.type());
+    if (enclosingPruned.test(enclosingType, enclosedType)) {
       return null;
     } else {
       return enclosingSignature.visit(this);
@@ -208,15 +223,26 @@
       return fieldSignature.asArrayTypeSignature().visit(this);
     } else {
       assert fieldSignature.isTypeVariableSignature();
-      String typeVariableName = fieldSignature.asTypeVariableSignature().typeVariable();
-      if (!typeParameterContext.isLiveParameter(typeVariableName)) {
-        DexType substitution = typeParameterContext.getPrunedSubstitution(typeVariableName);
-        if (substitution == null) {
-          substitution = appView.dexItemFactory().objectType;
-        }
-        return new ClassTypeSignature(substitution).asArgument(WildcardIndicator.NONE);
+      // TODO(b/b/191871201): If we track where type-variables are introduced, we can move this
+      //  past typeParameterContext.isLiveParameter(typeVariableName) and get more precision.
+      if (makeAllTypeArgumentsObject) {
+        return objectArgument;
       }
-      return fieldSignature;
+      String typeVariableName = fieldSignature.asTypeVariableSignature().typeVariable();
+      if (typeParameterContext.isLiveParameter(typeVariableName)) {
+        return fieldSignature;
+      }
+      FieldTypeSignature substitution =
+          typeParameterContext.getPrunedSubstitution(typeVariableName);
+      if (substitution == null) {
+        return objectArgument;
+      }
+      makeAllTypeArgumentsObject = true;
+      substitution = visitFieldTypeSignature(substitution);
+      makeAllTypeArgumentsObject = false;
+      return substitution.isArgument()
+          ? substitution
+          : substitution.asArgument(WildcardIndicator.NONE);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
index b111c57..9d54705 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
@@ -146,7 +146,7 @@
 
   @Override
   public List<FieldTypeSignature> visitTypeArguments(
-      DexType type, List<FieldTypeSignature> typeArguments) {
+      DexType originalType, DexType lookedUpType, List<FieldTypeSignature> typeArguments) {
     if (typeArguments.isEmpty()) {
       return typeArguments;
     }
@@ -207,7 +207,7 @@
         }
         sb.append(".").append(innerClassName);
       }
-      visitTypeArguments(null, classTypeSignature.typeArguments);
+      visitTypeArguments(null, null, classTypeSignature.typeArguments);
       if (!printingOuter) {
         sb.append(";");
       }
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
index 332aa8d..7f224bb 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
@@ -29,26 +29,31 @@
   private final DexProgramClass context;
 
   private final ClassTypeSignature objectTypeSignature;
+  private final Predicate<DexType> hasGenericTypeVariables;
 
-  public GenericSignatureTypeRewriter(AppView<?> appView, DexProgramClass context) {
+  public GenericSignatureTypeRewriter(
+      AppView<?> appView, DexProgramClass context, Predicate<DexType> hasGenericTypeVariables) {
     this(
         appView.dexItemFactory(),
         appView.appInfo().hasLiveness()
             ? appView.appInfo().withLiveness()::wasPruned
             : alwaysFalse(),
         appView.graphLens()::lookupType,
-        context);
+        context,
+        hasGenericTypeVariables);
   }
 
   public GenericSignatureTypeRewriter(
       DexItemFactory factory,
       Predicate<DexType> wasPruned,
       Function<DexType, DexType> lookupType,
-      DexProgramClass context) {
+      DexProgramClass context,
+      Predicate<DexType> hasGenericTypeVariables) {
     this.factory = factory;
     this.wasPruned = wasPruned;
     this.lookupType = lookupType;
     this.context = context;
+    this.hasGenericTypeVariables = hasGenericTypeVariables;
     objectTypeSignature = new ClassTypeSignature(factory.objectType, getEmptyTypeArguments());
   }
 
@@ -138,10 +143,13 @@
 
     @Override
     public ClassTypeSignature visitSuperClass(ClassTypeSignature classTypeSignature) {
+      if (context.superType == factory.objectType) {
+        return classTypeSignature.type == factory.objectType
+            ? classTypeSignature
+            : objectTypeSignature;
+      }
       ClassTypeSignature rewritten = classTypeSignature.visit(this);
-      return rewritten == null || rewritten.type() == context.type
-          ? objectTypeSignature
-          : rewritten;
+      return rewritten == null ? objectTypeSignature : rewritten;
     }
 
     @Override
@@ -239,10 +247,17 @@
 
     @Override
     public List<FieldTypeSignature> visitTypeArguments(
-        DexType type, List<FieldTypeSignature> typeArguments) {
+        DexType originalType, DexType lookedUpType, List<FieldTypeSignature> typeArguments) {
+      assert lookedUpType != null;
       if (typeArguments.isEmpty()) {
         return typeArguments;
       }
+      // If the original type has been pruned it must be because the old type has been merged into
+      // the looked up type. We can therefore not guarantee the type arguments to be consistent and
+      // have to remove them.
+      if (wasPruned.test(originalType) || !hasGenericTypeVariables.test(lookedUpType)) {
+        return getEmptyTypeArguments();
+      }
       return ListUtils.mapOrElse(
           typeArguments,
           fieldTypeSignature -> {
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java
index 59cd879..a619129 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java
@@ -144,7 +144,7 @@
 
   @Override
   public List<FieldTypeSignature> visitTypeArguments(
-      DexType type, List<FieldTypeSignature> typeArguments) {
+      DexType originalType, DexType lookedUpType, List<FieldTypeSignature> typeArguments) {
     typeArguments.forEach(this::visitFieldTypeSignature);
     return typeArguments;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java
index b321ad1..37b3cbe 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java
@@ -84,7 +84,7 @@
   }
 
   default List<FieldTypeSignature> visitTypeArguments(
-      DexType type, List<FieldTypeSignature> typeArguments) {
+      DexType originalType, DexType lookedUpType, List<FieldTypeSignature> typeArguments) {
     throw new Unreachable("Implement if visited");
   }
 
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 a07a380..8e436ba 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -89,8 +89,9 @@
       return rewritings.getOrDefault(reboundReference, reboundReference);
     }
 
-    public R getRewrittenReboundReference(Map<R, R> rewritings) {
-      return rewritings.getOrDefault(reboundReference, reboundReference);
+    public R getRewrittenReboundReference(Function<R, R> rewritings) {
+      R rewrittenReboundReference = rewritings.apply(reboundReference);
+      return rewrittenReboundReference != null ? rewrittenReboundReference : reboundReference;
     }
 
     abstract static class Builder<R extends DexMember<?, R>, Self extends Builder<R, Self>> {
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 2412ea7..3509b8c 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -2,6 +2,7 @@
 
 import static com.google.common.base.Predicates.alwaysTrue;
 
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.TraversalContinuation;
 import java.util.ArrayList;
@@ -24,7 +25,7 @@
   private MethodCollectionBacking backing;
   private DexEncodedMethod cachedClassInitializer = DexEncodedMethod.SENTINEL;
 
-  public MethodCollection(
+  MethodCollection(
       DexClass holder, DexEncodedMethod[] directMethods, DexEncodedMethod[] virtualMethods) {
     this.holder = holder;
     if (directMethods.length + virtualMethods.length > ARRAY_BACKING_THRESHOLD) {
@@ -36,6 +37,14 @@
     backing.setVirtualMethods(virtualMethods);
   }
 
+  public static MethodCollection create(
+      DexClass holder, DexEncodedMethod[] directMethods, DexEncodedMethod[] virtualMethods) {
+    if (InternalOptions.USE_METHOD_COLLECTION_CONCURRENCY_CHECKED) {
+      return new MethodCollectionConcurrencyChecked(holder, directMethods, virtualMethods);
+    }
+    return new MethodCollection(holder, directMethods, virtualMethods);
+  }
+
   private void resetCaches() {
     resetDirectMethodCaches();
     resetVirtualMethodCaches();
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollectionConcurrencyChecked.java b/src/main/java/com/android/tools/r8/graph/MethodCollectionConcurrencyChecked.java
new file mode 100644
index 0000000..66be012
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollectionConcurrencyChecked.java
@@ -0,0 +1,338 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.Collection;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+public class MethodCollectionConcurrencyChecked extends MethodCollection {
+  private AtomicInteger readCount = new AtomicInteger();
+  private AtomicInteger writeCount = new AtomicInteger();
+
+  MethodCollectionConcurrencyChecked(
+      DexClass holder, DexEncodedMethod[] directMethods, DexEncodedMethod[] virtualMethods) {
+    super(holder, directMethods, virtualMethods);
+  }
+
+  private boolean assertReadEntry() {
+    assert writeCount.get() == 0;
+    assert readCount.incrementAndGet() >= 1;
+    return true;
+  }
+
+  private boolean assertReadExit() {
+    assert readCount.decrementAndGet() >= 0;
+    assert writeCount.get() == 0;
+    return true;
+  }
+
+  private boolean assertWriteEntry() {
+    assert readCount.get() == 0;
+    assert writeCount.incrementAndGet() == 1;
+    return true;
+  }
+
+  private boolean assertWriteExit() {
+    assert writeCount.decrementAndGet() == 0;
+    assert readCount.get() == 0;
+    return true;
+  }
+
+  @Override
+  public boolean hasDirectMethods(Predicate<DexEncodedMethod> predicate) {
+    assert assertReadEntry();
+    boolean result = super.getDirectMethod(predicate) != null;
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public boolean hasVirtualMethods(Predicate<DexEncodedMethod> predicate) {
+    assert assertReadEntry();
+    boolean result = super.getVirtualMethod(predicate) != null;
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public int numberOfDirectMethods() {
+    assert assertReadEntry();
+    int result = super.numberOfDirectMethods();
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public int numberOfVirtualMethods() {
+    assert assertReadEntry();
+    int result = super.numberOfVirtualMethods();
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public int size() {
+    assert assertReadEntry();
+    int result = super.size();
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public TraversalContinuation traverse(Function<DexEncodedMethod, TraversalContinuation> fn) {
+    assert assertReadEntry();
+    TraversalContinuation result = super.traverse(fn);
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public void forEachMethodMatching(
+      Predicate<DexEncodedMethod> predicate, Consumer<DexEncodedMethod> consumer) {
+    assert assertReadEntry();
+    super.forEachMethodMatching(predicate, consumer);
+    assert assertReadExit();
+  }
+
+  @Override
+  public void forEachDirectMethodMatching(
+      Predicate<DexEncodedMethod> predicate, Consumer<DexEncodedMethod> consumer) {
+    assert assertReadEntry();
+    super.forEachDirectMethodMatching(predicate, consumer);
+    assert assertReadExit();
+  }
+
+  @Override
+  public void forEachVirtualMethodMatching(
+      Predicate<DexEncodedMethod> predicate, Consumer<DexEncodedMethod> consumer) {
+    assert assertReadEntry();
+    super.forEachVirtualMethodMatching(predicate, consumer);
+    assert assertReadExit();
+  }
+
+  @Override
+  public Iterable<DexEncodedMethod> methods() {
+    // TODO(sgjesse): Maybe wrap in an iterator that checks a modification counter.
+    return super.methods();
+  }
+
+  @Override
+  public Iterable<DexEncodedMethod> directMethods() {
+    // TODO(sgjesse): Maybe wrap in an iterator that checks a modification counter.
+    return super.directMethods();
+  }
+
+  @Override
+  public Iterable<DexEncodedMethod> virtualMethods() {
+    // TODO(sgjesse): Maybe wrap in an iterator that checks a modification counter.
+    return super.virtualMethods();
+  }
+
+  @Override
+  public DexEncodedMethod getMethod(DexMethod method) {
+    assert assertReadEntry();
+    DexEncodedMethod result = super.getMethod(method);
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public DexEncodedMethod getMethod(Predicate<DexEncodedMethod> predicate) {
+    assert assertReadEntry();
+    DexEncodedMethod result = super.getMethod(predicate);
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public DexEncodedMethod getDirectMethod(DexMethod method) {
+    assert assertReadEntry();
+    DexEncodedMethod result = super.getDirectMethod(method);
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public DexEncodedMethod getDirectMethod(Predicate<DexEncodedMethod> predicate) {
+    assert assertReadEntry();
+    DexEncodedMethod result = super.getDirectMethod(predicate);
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public DexEncodedMethod getVirtualMethod(DexMethod method) {
+    assert assertReadEntry();
+    DexEncodedMethod result = super.getVirtualMethod(method);
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public DexEncodedMethod getVirtualMethod(Predicate<DexEncodedMethod> predicate) {
+    assert assertReadEntry();
+    DexEncodedMethod result = super.getVirtualMethod(predicate);
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public void addMethod(DexEncodedMethod method) {
+    assert assertWriteEntry();
+    super.addMethod(method);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void addVirtualMethod(DexEncodedMethod virtualMethod) {
+    assert assertWriteEntry();
+    super.addVirtualMethod(virtualMethod);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void addDirectMethod(DexEncodedMethod directMethod) {
+    assert assertWriteEntry();
+    super.addDirectMethod(directMethod);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public DexEncodedMethod replaceDirectMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    assert assertWriteEntry();
+    DexEncodedMethod result = super.replaceDirectMethod(method, replacement);
+    assert assertWriteExit();
+    return result;
+  }
+
+  @Override
+  public DexEncodedMethod replaceVirtualMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    assert assertWriteEntry();
+    DexEncodedMethod result = super.replaceVirtualMethod(method, replacement);
+    assert assertWriteExit();
+    return result;
+  }
+
+  @Override
+  public void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    assert assertWriteEntry();
+    super.replaceMethods(replacement);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    assert assertWriteEntry();
+    super.replaceDirectMethods(replacement);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void replaceVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    assert assertWriteEntry();
+    super.replaceVirtualMethods(replacement);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void replaceAllDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    assert assertWriteEntry();
+    super.replaceAllDirectMethods(replacement);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void replaceAllVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    assert assertWriteEntry();
+    super.replaceAllVirtualMethods(replacement);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public DexEncodedMethod replaceDirectMethodWithVirtualMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    assert assertWriteEntry();
+    DexEncodedMethod result = super.replaceDirectMethodWithVirtualMethod(method, replacement);
+    assert assertWriteExit();
+    return result;
+  }
+
+  @Override
+  public void addDirectMethods(Collection<DexEncodedMethod> methods) {
+    assert assertWriteEntry();
+    super.addDirectMethods(methods);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void clearDirectMethods() {
+    assert assertWriteEntry();
+    super.clearDirectMethods();
+    assert assertWriteExit();
+  }
+
+  @Override
+  public DexEncodedMethod removeMethod(DexMethod method) {
+    assert assertWriteEntry();
+    DexEncodedMethod result = super.removeMethod(method);
+    assert assertWriteExit();
+    return result;
+  }
+
+  @Override
+  public void removeMethods(Set<DexEncodedMethod> methods) {
+    assert assertWriteEntry();
+    super.removeMethods(methods);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void setDirectMethods(DexEncodedMethod[] methods) {
+    assert assertWriteEntry();
+    super.setDirectMethods(methods);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void addVirtualMethods(Collection<DexEncodedMethod> methods) {
+    assert assertWriteEntry();
+    super.addVirtualMethods(methods);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void clearVirtualMethods() {
+    assert assertWriteEntry();
+    super.clearVirtualMethods();
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void setVirtualMethods(DexEncodedMethod[] methods) {
+    assert assertWriteEntry();
+    super.setVirtualMethods(methods);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) {
+    assert assertWriteEntry();
+    super.virtualizeMethods(privateInstanceMethods);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void useSortedBacking() {
+    assert assertWriteEntry();
+    super.useSortedBacking();
+    assert assertWriteExit();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java b/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
index 81724fb..1b49b31 100644
--- a/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap;
 import java.util.Collections;
 import java.util.Map;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 /**
@@ -33,7 +34,7 @@
   protected static final Map<DexType, DexType> EMPTY_TYPE_MAP = Collections.emptyMap();
 
   protected final BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap;
-  protected final Map<DexMethod, DexMethod> methodMap;
+  protected final Function<DexMethod, DexMethod> methodMap;
   protected final Map<DexType, DexType> typeMap;
 
   // Map that stores the new signature of methods that have been affected by class merging, unused
@@ -84,11 +85,20 @@
       Map<DexMethod, DexMethod> methodMap,
       Map<DexType, DexType> typeMap,
       BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> newMethodSignatures) {
-    super(appView);
+    this(appView, fieldMap, methodMap::get, typeMap, newMethodSignatures);
     assert !typeMap.isEmpty()
         || !methodMap.isEmpty()
         || !fieldMap.isEmpty()
         || isLegitimateToHaveEmptyMappings();
+  }
+
+  public NestedGraphLens(
+      AppView<?> appView,
+      BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
+      Function<DexMethod, DexMethod> methodMap,
+      Map<DexType, DexType> typeMap,
+      BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> newMethodSignatures) {
+    super(appView);
     this.fieldMap = fieldMap;
     this.methodMap = methodMap;
     this.typeMap = typeMap;
@@ -199,7 +209,7 @@
     } else {
       // TODO(b/168282032): We should always have the rebound reference, so this should become
       //  unreachable.
-      DexMethod newMethod = methodMap.get(previous.getReference());
+      DexMethod newMethod = methodMap.apply(previous.getReference());
       if (newMethod == null) {
         return previous;
       }
@@ -306,13 +316,6 @@
                 .append(" -> ")
                 .append(to.getTypeName())
                 .append(System.lineSeparator()));
-    methodMap.forEach(
-        (from, to) ->
-            builder
-                .append(from.toSourceString())
-                .append(" -> ")
-                .append(to.toSourceString())
-                .append(System.lineSeparator()));
     fieldMap.forEachManyToOneMapping(
         (fromSet, to) -> {
           builder.append(
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index 254f5a4..946bdbc 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -376,7 +376,7 @@
     }
 
     public void injectInterfaces(
-        DexDefinitionSupplier definitions, DexProgramClass clazz, List<DexClass> newInterfaces) {
+        DexDefinitionSupplier definitions, DexProgramClass clazz, Set<DexClass> newInterfaces) {
       for (DexClass newInterface : newInterfaces) {
         populateInstantiatedHierarchy(definitions, newInterface.type, clazz);
       }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
index 28d6be7..c9788bd 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
@@ -8,6 +8,8 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter.Mode;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -30,33 +32,33 @@
     converter.registerCallbackIfRequired(method);
   }
 
-  private void traceInvoke(DexMethod invokedMethod) {
-    converter.registerWrappersForLibraryInvokeIfRequired(invokedMethod);
+  private void traceInvoke(DexMethod invokedMethod, Type invokeType, ProgramMethod context) {
+    converter.registerWrappersForLibraryInvokeIfRequired(invokedMethod, invokeType, context);
   }
 
   @Override
   public void traceInvokeStatic(DexMethod invokedMethod, ProgramMethod context) {
-    this.traceInvoke(invokedMethod);
+    this.traceInvoke(invokedMethod, Type.STATIC, context);
   }
 
   @Override
   public void traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context) {
-    this.traceInvoke(invokedMethod);
+    this.traceInvoke(invokedMethod, Type.DIRECT, context);
   }
 
   @Override
   public void traceInvokeInterface(DexMethod invokedMethod, ProgramMethod context) {
-    this.traceInvoke(invokedMethod);
+    this.traceInvoke(invokedMethod, Type.INTERFACE, context);
   }
 
   @Override
   public void traceInvokeSuper(DexMethod invokedMethod, ProgramMethod context) {
-    this.traceInvoke(invokedMethod);
+    this.traceInvoke(invokedMethod, Type.SUPER, context);
   }
 
   @Override
   public void traceInvokeVirtual(DexMethod invokedMethod, ProgramMethod context) {
-    this.traceInvoke(invokedMethod);
+    this.traceInvoke(invokedMethod, Invoke.Type.VIRTUAL, context);
   }
 
   public ProgramMethodSet generateCallbackMethods() {
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 db0178c..7483522 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -24,6 +24,7 @@
 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.ProgramMember;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
 import com.android.tools.r8.horizontalclassmerging.code.ClassInitializerMerger;
@@ -325,6 +326,8 @@
     mergeInterfaces();
     mergeFields();
     mergeMethods(syntheticArgumentClass, syntheticInitializerConverterBuilder);
+    group.getTarget().clearClassSignature();
+    group.getTarget().forEachProgramMember(ProgramMember::clearGenericSignature);
   }
 
   public static class Builder {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index 89dcd0a..eea0202 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.horizontalclassmerging.policies.NoDifferentApiReferenceLevel;
 import com.android.tools.r8.horizontalclassmerging.policies.NoDirectRuntimeTypeChecks;
 import com.android.tools.r8.horizontalclassmerging.policies.NoEnums;
+import com.android.tools.r8.horizontalclassmerging.policies.NoFailedResolutionTargets;
 import com.android.tools.r8.horizontalclassmerging.policies.NoIllegalInlining;
 import com.android.tools.r8.horizontalclassmerging.policies.NoIndirectRuntimeTypeChecks;
 import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses;
@@ -116,6 +117,7 @@
         new NoAnnotationClasses(),
         new NoDirectRuntimeTypeChecks(appView, mode, runtimeTypeCheckInfo),
         new NoEnums(appView),
+        new NoFailedResolutionTargets(appView),
         new NoInterfaces(appView, mode),
         new NoInnerClasses(),
         new NoInstanceFieldAnnotations(),
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoFailedResolutionTargets.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoFailedResolutionTargets.java
new file mode 100644
index 0000000..65af78c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoFailedResolutionTargets.java
@@ -0,0 +1,37 @@
+// 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+// TODO(b/192821424): Can be removed if handled.
+public class NoFailedResolutionTargets extends SingleClassPolicy {
+
+  private final Set<DexType> failedResolutionHolders;
+
+  public NoFailedResolutionTargets(AppView<AppInfoWithLiveness> appView) {
+    failedResolutionHolders = Sets.newIdentityHashSet();
+    for (DexMethod method : appView.appInfo().getFailedMethodResolutionTargets()) {
+      failedResolutionHolders.add(method.holder);
+    }
+  }
+
+  @Override
+  public String getName() {
+    return "NoFailedResolutionTargets";
+  }
+
+  @Override
+  public boolean canMerge(DexProgramClass program) {
+    return !failedResolutionHolders.contains(program.getType());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanFalseSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanFalseSimpleInliningConstraint.java
deleted file mode 100644
index 2c88a60..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanFalseSimpleInliningConstraint.java
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.analysis.inlining;
-
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import it.unimi.dsi.fastutil.ints.IntList;
-
-/** Constraint that is satisfied if a specific argument is always false. */
-public class BooleanFalseSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
-
-  private BooleanFalseSimpleInliningConstraint(int argumentIndex) {
-    super(argumentIndex);
-  }
-
-  static BooleanFalseSimpleInliningConstraint create(
-      int argumentIndex, SimpleInliningConstraintFactory witness) {
-    assert witness != null;
-    return new BooleanFalseSimpleInliningConstraint(argumentIndex);
-  }
-
-  @Override
-  public boolean isBooleanFalse() {
-    return true;
-  }
-
-  @Override
-  public boolean isSatisfied(InvokeMethod invoke) {
-    Value argument = getArgument(invoke);
-    assert argument.getType().isInt();
-    return argument.isConstBoolean(false);
-  }
-
-  @Override
-  public SimpleInliningConstraint fixupAfterRemovingThisParameter(
-      SimpleInliningConstraintFactory factory) {
-    assert getArgumentIndex() > 0;
-    return factory.createBooleanFalseConstraint(getArgumentIndex() - 1);
-  }
-
-  @Override
-  public SimpleInliningConstraint rewrittenWithUnboxedArguments(
-      IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
-    return this;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanTrueSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanTrueSimpleInliningConstraint.java
deleted file mode 100644
index 46c29b0..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanTrueSimpleInliningConstraint.java
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.analysis.inlining;
-
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import it.unimi.dsi.fastutil.ints.IntList;
-
-/** Constraint that is satisfied if a specific argument is always true. */
-public class BooleanTrueSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
-
-  private BooleanTrueSimpleInliningConstraint(int argumentIndex) {
-    super(argumentIndex);
-  }
-
-  static BooleanTrueSimpleInliningConstraint create(
-      int argumentIndex, SimpleInliningConstraintFactory witness) {
-    assert witness != null;
-    return new BooleanTrueSimpleInliningConstraint(argumentIndex);
-  }
-
-  @Override
-  public boolean isBooleanTrue() {
-    return true;
-  }
-
-  @Override
-  public boolean isSatisfied(InvokeMethod invoke) {
-    Value argument = getArgument(invoke);
-    assert argument.getType().isInt();
-    return argument.isConstBoolean(true);
-  }
-
-  @Override
-  public SimpleInliningConstraint fixupAfterRemovingThisParameter(
-      SimpleInliningConstraintFactory factory) {
-    assert getArgumentIndex() > 0;
-    return factory.createBooleanTrueConstraint(getArgumentIndex() - 1);
-  }
-
-  @Override
-  public SimpleInliningConstraint rewrittenWithUnboxedArguments(
-      IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
-    return this;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/EqualToBooleanSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/EqualToBooleanSimpleInliningConstraint.java
new file mode 100644
index 0000000..dae3231
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/EqualToBooleanSimpleInliningConstraint.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.inlining;
+
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import it.unimi.dsi.fastutil.ints.IntList;
+
+/** Constraint that is satisfied if a specific argument is always true. */
+public class EqualToBooleanSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
+
+  private final boolean value;
+
+  private EqualToBooleanSimpleInliningConstraint(int argumentIndex, boolean value) {
+    super(argumentIndex);
+    this.value = value;
+  }
+
+  static EqualToBooleanSimpleInliningConstraint create(
+      int argumentIndex, boolean value, SimpleInliningConstraintFactory witness) {
+    assert witness != null;
+    return new EqualToBooleanSimpleInliningConstraint(argumentIndex, value);
+  }
+
+  @Override
+  public boolean isSatisfied(InvokeMethod invoke) {
+    Value argumentRoot = getArgument(invoke).getAliasedValue();
+    return argumentRoot.isDefinedByInstructionSatisfying(Instruction::isConstNumber)
+        && argumentRoot.getDefinition().asConstNumber().getBooleanValue() == value;
+  }
+
+  @Override
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(
+      IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
+    return this;
+  }
+
+  @Override
+  SimpleInliningArgumentConstraint withArgumentIndex(
+      int argumentIndex, SimpleInliningConstraintFactory factory) {
+    return factory.createEqualToBooleanConstraint(argumentIndex, value);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/EqualToNumberSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/EqualToNumberSimpleInliningConstraint.java
index a321670..236e57b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/EqualToNumberSimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/EqualToNumberSimpleInliningConstraint.java
@@ -4,18 +4,10 @@
 
 package com.android.tools.r8.ir.analysis.inlining;
 
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import it.unimi.dsi.fastutil.ints.IntList;
-
-public class EqualToNumberSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
-
-  private final long rawValue;
+public class EqualToNumberSimpleInliningConstraint extends NumberSimpleInliningConstraint {
 
   private EqualToNumberSimpleInliningConstraint(int argumentIndex, long rawValue) {
-    super(argumentIndex);
-    this.rawValue = rawValue;
+    super(argumentIndex, rawValue);
   }
 
   static EqualToNumberSimpleInliningConstraint create(
@@ -25,22 +17,13 @@
   }
 
   @Override
-  public boolean isSatisfied(InvokeMethod invoke) {
-    Value argumentRoot = getArgument(invoke).getAliasedValue();
-    return argumentRoot.isDefinedByInstructionSatisfying(Instruction::isConstNumber)
-        && argumentRoot.getDefinition().asConstNumber().getRawValue() == rawValue;
+  boolean test(long argumentValue) {
+    return argumentValue == getRawValue();
   }
 
   @Override
-  public SimpleInliningConstraint fixupAfterRemovingThisParameter(
-      SimpleInliningConstraintFactory factory) {
-    assert getArgumentIndex() > 0;
-    return factory.createNumberConstraint(getArgumentIndex() - 1, rawValue);
-  }
-
-  @Override
-  public SimpleInliningConstraint rewrittenWithUnboxedArguments(
-      IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
-    return this;
+  SimpleInliningArgumentConstraint withArgumentIndex(
+      int argumentIndex, SimpleInliningConstraintFactory factory) {
+    return factory.createEqualToNumberConstraint(argumentIndex, getRawValue());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotEqualToNumberSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotEqualToNumberSimpleInliningConstraint.java
index 4739231..5f83db7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotEqualToNumberSimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotEqualToNumberSimpleInliningConstraint.java
@@ -4,18 +4,10 @@
 
 package com.android.tools.r8.ir.analysis.inlining;
 
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import it.unimi.dsi.fastutil.ints.IntList;
-
-public class NotEqualToNumberSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
-
-  private final long rawValue;
+public class NotEqualToNumberSimpleInliningConstraint extends NumberSimpleInliningConstraint {
 
   private NotEqualToNumberSimpleInliningConstraint(int argumentIndex, long rawValue) {
-    super(argumentIndex);
-    this.rawValue = rawValue;
+    super(argumentIndex, rawValue);
   }
 
   static NotEqualToNumberSimpleInliningConstraint create(
@@ -25,22 +17,13 @@
   }
 
   @Override
-  public boolean isSatisfied(InvokeMethod invoke) {
-    Value argumentRoot = getArgument(invoke).getAliasedValue();
-    return argumentRoot.isDefinedByInstructionSatisfying(Instruction::isConstNumber)
-        && argumentRoot.getDefinition().asConstNumber().getRawValue() != rawValue;
+  boolean test(long argumentValue) {
+    return argumentValue != getRawValue();
   }
 
   @Override
-  public SimpleInliningConstraint fixupAfterRemovingThisParameter(
-      SimpleInliningConstraintFactory factory) {
-    assert getArgumentIndex() > 0;
-    return factory.createNotNumberConstraint(getArgumentIndex() - 1, rawValue);
-  }
-
-  @Override
-  public SimpleInliningConstraint rewrittenWithUnboxedArguments(
-      IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
-    return this;
+  SimpleInliningArgumentConstraint withArgumentIndex(
+      int argumentIndex, SimpleInliningConstraintFactory factory) {
+    return factory.createNotEqualToNumberConstraint(argumentIndex, getRawValue());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotNullSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotNullSimpleInliningConstraint.java
deleted file mode 100644
index 00288f8..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotNullSimpleInliningConstraint.java
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.analysis.inlining;
-
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import it.unimi.dsi.fastutil.ints.IntList;
-
-/** Constraint that is satisfied if a specific argument is always non-null. */
-public class NotNullSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
-
-  private NotNullSimpleInliningConstraint(int argumentIndex) {
-    super(argumentIndex);
-  }
-
-  static NotNullSimpleInliningConstraint create(
-      int argumentIndex, SimpleInliningConstraintFactory witness) {
-    assert witness != null;
-    return new NotNullSimpleInliningConstraint(argumentIndex);
-  }
-
-  @Override
-  public boolean isNotNull() {
-    return true;
-  }
-
-  @Override
-  public boolean isSatisfied(InvokeMethod invoke) {
-    Value argument = getArgument(invoke);
-    assert argument.getType().isReferenceType() : invoke;
-    return argument.isNeverNull();
-  }
-
-  @Override
-  public SimpleInliningConstraint fixupAfterRemovingThisParameter(
-      SimpleInliningConstraintFactory factory) {
-    assert getArgumentIndex() > 0;
-    return factory.createNotNullConstraint(getArgumentIndex() - 1);
-  }
-
-  @Override
-  public SimpleInliningConstraint rewrittenWithUnboxedArguments(
-      IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
-    if (unboxedArgumentIndices.contains(getArgumentIndex())) {
-      return factory.createNotNumberConstraint(getArgumentIndex(), 0);
-    }
-    return this;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java
index 5a0d3cf..13cc5b9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java
@@ -4,6 +4,9 @@
 
 package com.android.tools.r8.ir.analysis.inlining;
 
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
 import it.unimi.dsi.fastutil.ints.IntList;
@@ -11,45 +14,52 @@
 /** Constraint that is satisfied if a specific argument is always null. */
 public class NullSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
 
-  private NullSimpleInliningConstraint(int argumentIndex) {
+  private final Nullability nullability;
+
+  private NullSimpleInliningConstraint(int argumentIndex, Nullability nullability) {
     super(argumentIndex);
+    assert nullability.isDefinitelyNull() || nullability.isDefinitelyNotNull();
+    this.nullability = nullability;
   }
 
   static NullSimpleInliningConstraint create(
-      int argumentIndex, SimpleInliningConstraintFactory witness) {
+      int argumentIndex, Nullability nullability, SimpleInliningConstraintFactory witness) {
     assert witness != null;
-    return new NullSimpleInliningConstraint(argumentIndex);
+    return new NullSimpleInliningConstraint(argumentIndex, nullability);
   }
 
   @Override
-  public boolean isNull() {
-    return true;
-  }
+  public final boolean isSatisfied(InvokeMethod invoke) {
+    Value argument = getArgument(invoke);
+    TypeElement argumentType = argument.getType();
+    assert argumentType.isReferenceType();
 
-  @Override
-  public boolean isSatisfied(InvokeMethod invoke) {
+    if (argumentType.nullability() == nullability) {
+      return true;
+    }
+
     // Take the root value to also deal with the following case, which may happen in dead code,
     // where v1 is actually guaranteed to be null, despite the value's type being non-null:
     //   v0 <- ConstNumber 0
     //   v1 <- AssumeNotNull v0
-    Value argumentRoot = getArgument(invoke).getAliasedValue();
-    assert argumentRoot.getType().isReferenceType();
-    return argumentRoot.getType().isDefinitelyNull();
-  }
-
-  @Override
-  public SimpleInliningConstraint fixupAfterRemovingThisParameter(
-      SimpleInliningConstraintFactory factory) {
-    assert getArgumentIndex() > 0;
-    return factory.createNullConstraint(getArgumentIndex() - 1);
+    return argument.isDefinedByInstructionSatisfying(Instruction::isAssume)
+        && argument.getAliasedValue().getType().nullability() == nullability;
   }
 
   @Override
   public SimpleInliningConstraint rewrittenWithUnboxedArguments(
       IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
     if (unboxedArgumentIndices.contains(getArgumentIndex())) {
-      return factory.createNumberConstraint(getArgumentIndex(), 0);
+      return nullability.isDefinitelyNull()
+          ? factory.createEqualToNumberConstraint(getArgumentIndex(), 0)
+          : factory.createNotEqualToNumberConstraint(getArgumentIndex(), 0);
     }
     return this;
   }
+
+  @Override
+  SimpleInliningArgumentConstraint withArgumentIndex(
+      int argumentIndex, SimpleInliningConstraintFactory factory) {
+    return factory.createNullConstraint(argumentIndex, nullability);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NumberSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NumberSimpleInliningConstraint.java
new file mode 100644
index 0000000..4f6ee2c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NumberSimpleInliningConstraint.java
@@ -0,0 +1,39 @@
+// 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.inlining;
+
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import it.unimi.dsi.fastutil.ints.IntList;
+
+public abstract class NumberSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
+
+  private final long rawValue;
+
+  NumberSimpleInliningConstraint(int argumentIndex, long rawValue) {
+    super(argumentIndex);
+    this.rawValue = rawValue;
+  }
+
+  long getRawValue() {
+    return rawValue;
+  }
+
+  @Override
+  public final boolean isSatisfied(InvokeMethod invoke) {
+    Value argumentRoot = getArgument(invoke).getAliasedValue();
+    return argumentRoot.isDefinedByInstructionSatisfying(Instruction::isConstNumber)
+        && test(argumentRoot.getDefinition().asConstNumber().getRawValue());
+  }
+
+  abstract boolean test(long argumentValue);
+
+  @Override
+  public final SimpleInliningConstraint rewrittenWithUnboxedArguments(
+      IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningArgumentConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningArgumentConstraint.java
index 749d6f4..78bf329 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningArgumentConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningArgumentConstraint.java
@@ -15,6 +15,13 @@
     this.argumentIndex = argumentIndex;
   }
 
+  @Override
+  public final SimpleInliningConstraint fixupAfterRemovingThisParameter(
+      SimpleInliningConstraintFactory factory) {
+    assert getArgumentIndex() > 0;
+    return withArgumentIndex(getArgumentIndex() - 1, factory);
+  }
+
   Value getArgument(InvokeMethod invoke) {
     return invoke.getArgument(argumentIndex);
   }
@@ -27,4 +34,7 @@
   public boolean isArgumentConstraint() {
     return true;
   }
+
+  abstract SimpleInliningArgumentConstraint withArgumentIndex(
+      int argumentIndex, SimpleInliningConstraintFactory factory);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java
index 82c3f7d..c9978cb 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java
@@ -19,14 +19,6 @@
     return false;
   }
 
-  public boolean isBooleanFalse() {
-    return false;
-  }
-
-  public boolean isBooleanTrue() {
-    return false;
-  }
-
   public boolean isConjunction() {
     return false;
   }
@@ -47,14 +39,6 @@
     return false;
   }
 
-  public boolean isNotNull() {
-    return false;
-  }
-
-  public boolean isNull() {
-    return false;
-  }
-
   public abstract boolean isSatisfied(InvokeMethod invoke);
 
   public final SimpleInliningConstraint meet(SimpleInliningConstraint other) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java
index 6d79b82..3d74f82 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java
@@ -173,15 +173,15 @@
       case EQ:
         if (isZeroTest) {
           if (argumentType.isReferenceType()) {
-            return factory.createNullConstraint(argumentIndex);
+            return factory.createEqualToNullConstraint(argumentIndex);
           }
           if (argumentType.isBooleanType()) {
-            return factory.createBooleanFalseConstraint(argumentIndex);
+            return factory.createEqualToFalseConstraint(argumentIndex);
           }
         } else if (argumentType.isPrimitiveType()) {
           OptionalLong rawValue = getRawNumberValue(otherOperand);
           if (rawValue.isPresent()) {
-            return factory.createNumberConstraint(argumentIndex, rawValue.getAsLong());
+            return factory.createEqualToNumberConstraint(argumentIndex, rawValue.getAsLong());
           }
         }
         return NeverSimpleInliningConstraint.getInstance();
@@ -189,15 +189,15 @@
       case NE:
         if (isZeroTest) {
           if (argumentType.isReferenceType()) {
-            return factory.createNotNullConstraint(argumentIndex);
+            return factory.createNotEqualToNullConstraint(argumentIndex);
           }
           if (argumentType.isBooleanType()) {
-            return factory.createBooleanTrueConstraint(argumentIndex);
+            return factory.createEqualToTrueConstraint(argumentIndex);
           }
         } else if (argumentType.isPrimitiveType()) {
           OptionalLong rawValue = getRawNumberValue(otherOperand);
           if (rawValue.isPresent()) {
-            return factory.createNotNumberConstraint(argumentIndex, rawValue.getAsLong());
+            return factory.createNotEqualToNumberConstraint(argumentIndex, rawValue.getAsLong());
           }
         }
         return NeverSimpleInliningConstraint.getInstance();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintFactory.java
index ed3cd55..5b60ccd 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintFactory.java
@@ -4,6 +4,10 @@
 
 package com.android.tools.r8.ir.analysis.inlining;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNull;
+
+import com.android.tools.r8.ir.analysis.type.Nullability;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Supplier;
@@ -11,78 +15,81 @@
 public class SimpleInliningConstraintFactory {
 
   // Immutable argument constraints for low argument indices to avoid overhead of ConcurrentHashMap.
-  private final BooleanFalseSimpleInliningConstraint[] lowBooleanFalseConstraints =
-      new BooleanFalseSimpleInliningConstraint[5];
-  private final BooleanTrueSimpleInliningConstraint[] lowBooleanTrueConstraints =
-      new BooleanTrueSimpleInliningConstraint[5];
-  private final NotNullSimpleInliningConstraint[] lowNotNullConstraints =
-      new NotNullSimpleInliningConstraint[5];
-  private final NullSimpleInliningConstraint[] lowNullConstraints =
+  private final EqualToBooleanSimpleInliningConstraint[] lowEqualToFalseConstraints =
+      new EqualToBooleanSimpleInliningConstraint[5];
+  private final EqualToBooleanSimpleInliningConstraint[] lowEqualToTrueConstraints =
+      new EqualToBooleanSimpleInliningConstraint[5];
+  private final NullSimpleInliningConstraint[] lowNotEqualToNullConstraints =
+      new NullSimpleInliningConstraint[5];
+  private final NullSimpleInliningConstraint[] lowEqualToNullConstraints =
       new NullSimpleInliningConstraint[5];
 
   // Argument constraints for high argument indices.
-  private final Map<Integer, BooleanFalseSimpleInliningConstraint> highBooleanFalseConstraints =
+  private final Map<Integer, EqualToBooleanSimpleInliningConstraint> highEqualToFalseConstraints =
       new ConcurrentHashMap<>();
-  private final Map<Integer, BooleanTrueSimpleInliningConstraint> highBooleanTrueConstraints =
+  private final Map<Integer, EqualToBooleanSimpleInliningConstraint> highEqualToTrueConstraints =
       new ConcurrentHashMap<>();
-  private final Map<Integer, NotNullSimpleInliningConstraint> highNotNullConstraints =
+  private final Map<Integer, NullSimpleInliningConstraint> highNotEqualToNullConstraints =
       new ConcurrentHashMap<>();
-  private final Map<Integer, NullSimpleInliningConstraint> highNullConstraints =
+  private final Map<Integer, NullSimpleInliningConstraint> highEqualToNullConstraints =
       new ConcurrentHashMap<>();
 
   public SimpleInliningConstraintFactory() {
-    for (int i = 0; i < lowBooleanFalseConstraints.length; i++) {
-      lowBooleanFalseConstraints[i] = BooleanFalseSimpleInliningConstraint.create(i, this);
+    for (int i = 0; i < lowEqualToFalseConstraints.length; i++) {
+      lowEqualToFalseConstraints[i] = EqualToBooleanSimpleInliningConstraint.create(i, false, this);
     }
-    for (int i = 0; i < lowBooleanTrueConstraints.length; i++) {
-      lowBooleanTrueConstraints[i] = BooleanTrueSimpleInliningConstraint.create(i, this);
+    for (int i = 0; i < lowEqualToTrueConstraints.length; i++) {
+      lowEqualToTrueConstraints[i] = EqualToBooleanSimpleInliningConstraint.create(i, true, this);
     }
-    for (int i = 0; i < lowNotNullConstraints.length; i++) {
-      lowNotNullConstraints[i] = NotNullSimpleInliningConstraint.create(i, this);
+    for (int i = 0; i < lowNotEqualToNullConstraints.length; i++) {
+      lowNotEqualToNullConstraints[i] =
+          NullSimpleInliningConstraint.create(i, definitelyNotNull(), this);
     }
-    for (int i = 0; i < lowNullConstraints.length; i++) {
-      lowNullConstraints[i] = NullSimpleInliningConstraint.create(i, this);
+    for (int i = 0; i < lowEqualToNullConstraints.length; i++) {
+      lowEqualToNullConstraints[i] = NullSimpleInliningConstraint.create(i, definitelyNull(), this);
     }
   }
 
-  public BooleanFalseSimpleInliningConstraint createBooleanFalseConstraint(int argumentIndex) {
-    return createArgumentConstraint(
-        argumentIndex,
-        lowBooleanFalseConstraints,
-        highBooleanFalseConstraints,
-        () -> BooleanFalseSimpleInliningConstraint.create(argumentIndex, this));
+  public EqualToBooleanSimpleInliningConstraint createEqualToFalseConstraint(int argumentIndex) {
+    return createEqualToBooleanConstraint(argumentIndex, false);
   }
 
-  public BooleanTrueSimpleInliningConstraint createBooleanTrueConstraint(int argumentIndex) {
-    return createArgumentConstraint(
-        argumentIndex,
-        lowBooleanTrueConstraints,
-        highBooleanTrueConstraints,
-        () -> BooleanTrueSimpleInliningConstraint.create(argumentIndex, this));
+  public EqualToBooleanSimpleInliningConstraint createEqualToTrueConstraint(int argumentIndex) {
+    return createEqualToBooleanConstraint(argumentIndex, true);
   }
 
-  public NotNullSimpleInliningConstraint createNotNullConstraint(int argumentIndex) {
+  public EqualToBooleanSimpleInliningConstraint createEqualToBooleanConstraint(
+      int argumentIndex, boolean value) {
     return createArgumentConstraint(
         argumentIndex,
-        lowNotNullConstraints,
-        highNotNullConstraints,
-        () -> NotNullSimpleInliningConstraint.create(argumentIndex, this));
+        value ? lowEqualToTrueConstraints : lowEqualToFalseConstraints,
+        value ? highEqualToTrueConstraints : highEqualToFalseConstraints,
+        () -> EqualToBooleanSimpleInliningConstraint.create(argumentIndex, value, this));
   }
 
-  public NullSimpleInliningConstraint createNullConstraint(int argumentIndex) {
+  public NullSimpleInliningConstraint createEqualToNullConstraint(int argumentIndex) {
+    return createNullConstraint(argumentIndex, definitelyNull());
+  }
+
+  public NullSimpleInliningConstraint createNotEqualToNullConstraint(int argumentIndex) {
+    return createNullConstraint(argumentIndex, definitelyNotNull());
+  }
+
+  public NullSimpleInliningConstraint createNullConstraint(
+      int argumentIndex, Nullability nullability) {
     return createArgumentConstraint(
         argumentIndex,
-        lowNullConstraints,
-        highNullConstraints,
-        () -> NullSimpleInliningConstraint.create(argumentIndex, this));
+        nullability.isDefinitelyNull() ? lowEqualToNullConstraints : lowNotEqualToNullConstraints,
+        nullability.isDefinitelyNull() ? highEqualToNullConstraints : highNotEqualToNullConstraints,
+        () -> NullSimpleInliningConstraint.create(argumentIndex, nullability, this));
   }
 
-  public NotEqualToNumberSimpleInliningConstraint createNotNumberConstraint(
+  public NotEqualToNumberSimpleInliningConstraint createNotEqualToNumberConstraint(
       int argumentIndex, long rawValue) {
     return NotEqualToNumberSimpleInliningConstraint.create(argumentIndex, rawValue, this);
   }
 
-  public EqualToNumberSimpleInliningConstraint createNumberConstraint(
+  public EqualToNumberSimpleInliningConstraint createEqualToNumberConstraint(
       int argumentIndex, long rawValue) {
     return EqualToNumberSimpleInliningConstraint.create(argumentIndex, rawValue, this);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index fbca901..e78ade7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -32,6 +32,10 @@
     this.knownToBeBoolean = knownToBeBoolean;
   }
 
+  public static Builder builder() {
+    return new Builder();
+  }
+
   public int getIndex() {
     assert verifyIndex();
     return index;
@@ -141,4 +145,25 @@
   public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
     return false;
   }
+
+  public static class Builder extends BuilderBase<Builder, Argument> {
+
+    private int index;
+    private boolean knownToBeBoolean;
+
+    public Builder setIndex(int index) {
+      this.index = index;
+      return this;
+    }
+
+    @Override
+    public Argument build() {
+      return amend(new Argument(outValue, index, knownToBeBoolean));
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index a6a4704..e06df9e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -63,12 +63,14 @@
 
   private Int2ReferenceMap<DebugLocalInfo> localsAtEntry;
 
-  public boolean consistentBlockInstructions(boolean argumentsAllowed, boolean debug) {
+  public boolean consistentBlockInstructions(boolean argumentsAllowed, boolean debug, boolean ssa) {
     for (Instruction instruction : getInstructions()) {
       assert instruction.verifyValidPositionInfo(debug);
       assert instruction.getBlock() == this;
       assert !instruction.isArgument() || argumentsAllowed;
       assert !instruction.isDebugLocalRead() || !instruction.getDebugValues().isEmpty();
+      assert !instruction.isInitClass()
+          || consistentInitClassInstruction(instruction.asInitClass(), ssa);
       if (instruction.isMoveException()) {
         assert instruction == entry();
         for (BasicBlock pred : getPredecessors()) {
@@ -83,6 +85,17 @@
     return true;
   }
 
+  public boolean consistentInitClassInstruction(InitClass initClass, boolean ssa) {
+    if (!ssa) {
+      return true;
+    }
+    assert initClass.hasOutValue();
+    assert !initClass.outValue().hasDebugUsers();
+    assert !initClass.outValue().hasPhiUsers();
+    assert initClass.outValue().uniqueUsers().stream().allMatch(Instruction::isPop);
+    return true;
+  }
+
   public boolean verifyTypes(AppView<?> appView, VerifyTypesHelper verifyTypesHelper) {
     assert instructions.stream()
         .allMatch(instruction -> instruction.verifyTypes(appView, verifyTypesHelper));
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index b02559a..b5e4027 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -18,12 +18,14 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
 
 public class CheckCast extends Instruction {
 
@@ -42,6 +44,27 @@
     return new Builder();
   }
 
+  public boolean isRefiningStaticType(InternalOptions options) {
+    TypeElement inType = object().getType();
+    if (inType.isNullType()) {
+      // If the in-value is `null` and the cast-type is a float-array type, then trivial check-cast
+      // elimination may lead to verification errors. See b/123269162.
+      if (options.canHaveArtCheckCastVerifierBug()
+          && getType().isArrayType()
+          && getType().toBaseType(options.dexItemFactory()).isFloatType()) {
+        return true;
+      }
+      return false;
+    }
+    if (!inType.isClassType()) {
+      // Conservatively return true.
+      assert inType.isArrayType();
+      return true;
+    }
+    ClassTypeElement inClassType = inType.asClassType();
+    return type != inClassType.getClassType();
+  }
+
   @Override
   public int opcode() {
     return Opcodes.CHECK_CAST;
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 202df23..0447c1a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
+import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
 import com.android.tools.r8.ir.analysis.TypeChecker;
 import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -577,7 +577,8 @@
   }
 
   public boolean isConsistentSSABeforeTypesAreCorrect() {
-    assert isConsistentGraph();
+    assert isConsistentGraph(true);
+    assert consistentBlockInstructions(true);
     assert consistentDefUseChains();
     assert validThrowingInstructions();
     assert noCriticalEdges();
@@ -585,21 +586,20 @@
     return true;
   }
 
-  public boolean hasNoVerticallyMergedClasses(
-      AppView<? extends AppInfoWithClassHierarchy> appView) {
-    VerticallyMergedClasses verticallyMergedClasses = appView.verticallyMergedClasses();
-    if (verticallyMergedClasses == null) {
+  public boolean hasNoMergedClasses(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    MergedClassesCollection mergedClasses = appView.allMergedClasses();
+    if (mergedClasses == null) {
       return true;
     }
     for (Instruction instruction : instructions()) {
       if (instruction.outValue != null && instruction.outValue.getType().isClassType()) {
         ClassTypeElement classTypeLattice = instruction.outValue.getType().asClassType();
-        assert !verticallyMergedClasses.hasBeenMergedIntoSubtype(classTypeLattice.getClassType());
+        assert !mergedClasses.hasBeenMergedIntoDifferentType(classTypeLattice.getClassType());
         assert !classTypeLattice
             .getInterfaces()
             .anyMatch(
                 (itf, isKnown) -> {
-                  assert !verticallyMergedClasses.hasBeenMergedIntoSubtype(itf);
+                  assert !mergedClasses.hasBeenMergedIntoDifferentType(itf);
                   return false;
                 });
       }
@@ -608,11 +608,15 @@
   }
 
   public boolean isConsistentGraph() {
+    return isConsistentGraph(false);
+  }
+
+  public boolean isConsistentGraph(boolean ssa) {
     assert noColorsInUse();
     assert consistentBlockNumbering();
     assert consistentPredecessorSuccessors();
     assert consistentCatchHandlers();
-    assert consistentBlockInstructions();
+    assert consistentBlockInstructions(ssa);
     assert consistentMetadata();
     assert !allThrowingInstructionsHavePositions || computeAllThrowingInstructionsHavePositions();
     return true;
@@ -804,12 +808,13 @@
     return true;
   }
 
-  private boolean consistentBlockInstructions() {
+  private boolean consistentBlockInstructions(boolean ssa) {
     boolean argumentsAllowed = true;
     for (BasicBlock block : blocks) {
       assert block.consistentBlockInstructions(
           argumentsAllowed,
-          options.debug || method().getOptimizationInfo().isReachabilitySensitive());
+          options.debug || method().getOptimizationInfo().isReachabilitySensitive(),
+          ssa);
       argumentsAllowed = false;
     }
     return true;
@@ -1300,6 +1305,10 @@
     }
   }
 
+  public LinkedList<BasicBlock> getBlocks() {
+    return blocks;
+  }
+
   /**
    * Returns the set of blocks that are reachable from the given source. The source itself is only
    * included if there is a path from the given block to itself.
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 5517fb8..0332c6e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -102,6 +102,10 @@
     return inValues().get(index);
   }
 
+  public Value getFirstOperand() {
+    return getOperand(0);
+  }
+
   public List<Value> inValues() {
     return inValues;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index e2f81a9..e28ee89 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -51,6 +51,10 @@
     return returnValue().getType();
   }
 
+  public boolean hasReturnValue() {
+    return !isReturnVoid();
+  }
+
   public Value returnValue() {
     assert !isReturnVoid();
     return inValues.get(0);
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 0266f4b..514002e 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,8 +13,6 @@
 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.CfPostProcessingDesugaringEventConsumer;
-import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer.D8CfPostProcessingDesugaringEventConsumer;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -77,6 +75,11 @@
       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(
@@ -111,13 +114,6 @@
 
       classes = deferred;
     }
-
-    D8CfPostProcessingDesugaringEventConsumer eventConsumer =
-        CfPostProcessingDesugaringEventConsumer.createForD8(methodProcessor, appView);
-    methodProcessor.newWave();
-    converter.postProcessDesugaring(eventConsumer);
-    methodProcessor.awaitMethodProcessing();
-    eventConsumer.finalizeDesugaring();
   }
 
   abstract void convertClass(
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 b73d9d7..b6b40f8 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
@@ -51,10 +51,12 @@
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.D8CfInstructionDesugaringEventConsumer;
 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;
 import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter.Mode;
-import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceProcessor;
+import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceApplicationRewriter;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor;
 import com.android.tools.r8.ir.desugar.lambda.LambdaDeserializationMethodRemover;
@@ -93,7 +95,6 @@
 import com.android.tools.r8.ir.optimize.string.StringOptimizer;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
-import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.IdentifierNameStringMarker;
 import com.android.tools.r8.position.MethodPosition;
@@ -112,7 +113,6 @@
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.base.Suppliers;
-import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -226,12 +226,8 @@
       assert options.desugarState.isOn();
       this.instructionDesugaring = CfInstructionDesugaringCollection.create(appView);
       this.classDesugaring = instructionDesugaring.createClassDesugaringCollection();
-      this.interfaceMethodRewriter =
-          options.desugaredLibraryConfiguration.getEmulateLibraryInterface().isEmpty()
-              ? null
-              : new InterfaceMethodRewriter(appView, this);
-      this.desugaredLibraryAPIConverter =
-          new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS);
+      this.interfaceMethodRewriter = null;
+      this.desugaredLibraryAPIConverter = null;
       this.covariantReturnTypeAnnotationTransformer = null;
       this.dynamicTypeOptimization = null;
       this.classInliner = null;
@@ -259,7 +255,7 @@
             : CfInstructionDesugaringCollection.create(appView);
     this.classDesugaring = instructionDesugaring.createClassDesugaringCollection();
     this.interfaceMethodRewriter =
-        options.isInterfaceMethodDesugaringEnabled()
+        options.isInterfaceMethodDesugaringEnabled() && appView.enableWholeProgramOptimizations()
             ? new InterfaceMethodRewriter(appView, this)
             : null;
     this.covariantReturnTypeAnnotationTransformer =
@@ -323,10 +319,7 @@
       this.identifierNameStringMarker = null;
       this.devirtualizer = null;
       this.typeChecker = null;
-      this.desugaredLibraryAPIConverter =
-          appView.rewritePrefix.isRewriting()
-              ? new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS)
-              : null;
+      this.desugaredLibraryAPIConverter = null;
       this.serviceLoaderRewriter = null;
       this.methodOptimizationInfoCollector = null;
       this.enumValueOptimizer = null;
@@ -367,9 +360,11 @@
         D8NestBasedAccessDesugaring::clearNestAttributes);
   }
 
-  void postProcessDesugaring(CfPostProcessingDesugaringEventConsumer eventConsumer) {
-    CfPostProcessingDesugaringCollection.create(appView, instructionDesugaring.getRetargetingInfo())
-        .postProcessingDesugaring(eventConsumer);
+  public void ensureWrappersForL8(
+      D8CfInstructionDesugaringEventConsumer instructionDesugaringEventConsumer) {
+    assert appView.options().isDesugaredLibraryCompilation();
+    instructionDesugaring.withDesugaredLibraryAPIConverter(
+        converter -> converter.ensureWrappersForL8(instructionDesugaringEventConsumer));
   }
 
   private void staticizeClasses(
@@ -394,22 +389,15 @@
     }
   }
 
-  private void runInterfaceDesugaringProcessors(
+  private void runInterfaceDesugaringProcessorsForR8(
       Flavor includeAllResources, ExecutorService executorService) throws ExecutionException {
     assert !appView.getSyntheticItems().hasPendingSyntheticClasses();
     if (interfaceMethodRewriter != null) {
-      interfaceMethodRewriter.runInterfaceDesugaringProcessors(
+      interfaceMethodRewriter.runInterfaceDesugaringProcessorsForR8(
           this, includeAllResources, executorService);
     }
   }
 
-  private void synthesizeEnumUnboxingUtilityMethods(ExecutorService executorService)
-      throws ExecutionException {
-    if (enumUnboxer != null) {
-      enumUnboxer.synthesizeUtilityMethods(this, executorService);
-    }
-  }
-
   private void processCovariantReturnTypeAnnotations(Builder<?> builder) {
     if (covariantReturnTypeAnnotationTransformer != null) {
       covariantReturnTypeAnnotationTransformer.process(builder);
@@ -422,30 +410,28 @@
     workaroundAbstractMethodOnNonAbstractClassVerificationBug(
         executor, OptimizationFeedbackIgnore.getInstance());
     DexApplication application = appView.appInfo().app();
+    D8MethodProcessor methodProcessor = new D8MethodProcessor(this, executor);
+
     timing.begin("IR conversion");
 
-    convertClasses(executor);
+    convertClasses(methodProcessor, executor);
 
     reportNestDesugarDependencies();
     clearNestAttributes();
 
-    if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
-      appView.setAppInfo(
-          new AppInfo(
-              appView.appInfo().getSyntheticItems().commit(application),
-              appView.appInfo().getMainDexInfo()));
-      application = appView.appInfo().app();
-    }
+    application = commitPendingSyntheticItems(appView, application);
+
+    postProcessingDesugaringForD8(methodProcessor, executor);
+
+    application = commitPendingSyntheticItems(appView, application);
 
     // Build a new application with jumbo string info,
     Builder<?> builder = application.builder().setHighestSortingString(highestSortingString);
 
-    runInterfaceDesugaringProcessors(ExcludeDexResources, executor);
     if (appView.options().isDesugaredLibraryCompilation()) {
-      EmulatedInterfaceProcessor.filterEmulatedInterfaceSubInterfaces(appView, builder);
+      new EmulatedInterfaceApplicationRewriter(appView).rewriteApplication(builder);
     }
     processCovariantReturnTypeAnnotations(builder);
-    generateDesugaredLibraryAPIWrappers(builder, executor);
 
     timing.end();
 
@@ -456,8 +442,33 @@
             appView.appInfo().getMainDexInfo()));
   }
 
-  private void convertClasses(ExecutorService executorService) throws ExecutionException {
-    D8MethodProcessor methodProcessor = new D8MethodProcessor(this, executorService);
+  private DexApplication commitPendingSyntheticItems(
+      AppView<AppInfo> appView, DexApplication application) {
+    if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
+      appView.setAppInfo(
+          new AppInfo(
+              appView.appInfo().getSyntheticItems().commit(application),
+              appView.appInfo().getMainDexInfo()));
+      application = appView.appInfo().app();
+    }
+    return application;
+  }
+
+  private void postProcessingDesugaringForD8(
+      D8MethodProcessor methodProcessor, ExecutorService executorService)
+      throws ExecutionException {
+    D8CfPostProcessingDesugaringEventConsumer eventConsumer =
+        CfPostProcessingDesugaringEventConsumer.createForD8(methodProcessor);
+    InterfaceMethodProcessorFacade interfaceDesugaring =
+        instructionDesugaring.getInterfaceMethodPostProcessingDesugaring(ExcludeDexResources);
+    CfPostProcessingDesugaringCollection.create(
+            appView, interfaceDesugaring, instructionDesugaring.getRetargetingInfo())
+        .postProcessingDesugaring(eventConsumer, executorService);
+    eventConsumer.finalizeDesugaring();
+  }
+
+  private void convertClasses(D8MethodProcessor methodProcessor, ExecutorService executorService)
+      throws ExecutionException {
     ClassConverterResult classConverterResult =
         ClassConverter.create(appView, this, methodProcessor).convertClasses(executorService);
 
@@ -470,6 +481,9 @@
 
     rewriteEnclosingLambdaMethodAttributes(
         appView, classConverterResult.getForcefullyMovedLambdaMethods());
+
+    instructionDesugaring.withDesugaredLibraryAPIConverter(
+        DesugaredLibraryAPIConverter::generateTrackingWarnings);
   }
 
   public void desugarClassesForD8(
@@ -564,35 +578,14 @@
     }
   }
 
-  private boolean needsIRConversion(ProgramMethod method) {
+  private boolean needsIRConversion() {
     if (appView.enableWholeProgramOptimizations()) {
       return true;
     }
     if (options.testing.forceIRForCfToCfDesugar) {
       return true;
     }
-    if (options.isDesugaredLibraryCompilation()) {
-      return true;
-    }
-    if (!options.cfToCfDesugar) {
-      return true;
-    }
-    if (desugaredLibraryAPIConverter != null
-        && desugaredLibraryAPIConverter.shouldRegisterCallback(method)) {
-      return true;
-    }
-    if (method.getDefinition().getCode() instanceof SynthesizedCode) {
-      // SynthesizedCode needs IR to generate the code.
-      return true;
-    } else {
-      NeedsIRDesugarUseRegistry useRegistry =
-          new NeedsIRDesugarUseRegistry(
-              method,
-              appView,
-              desugaredLibraryAPIConverter);
-      method.registerCodeReferences(useRegistry);
-      return useRegistry.needsDesugaring();
-    }
+    return !options.cfToCfDesugar;
   }
 
   private void checkPrefixMerging(ProgramMethod method) {
@@ -760,8 +753,7 @@
     timing.end();
 
     if (enumUnboxer != null) {
-      // TODO(b/190098858): Uncomment when methods are synthesized on-the-fly.
-      // enumUnboxer.unsetRewriter();
+      enumUnboxer.unsetRewriter();
     }
 
     // All the code that should be impacted by the lenses inserted between phase 1 and phase 2
@@ -794,15 +786,9 @@
 
     printPhase("Interface method desugaring");
     finalizeInterfaceMethodRewritingThroughIR(executorService);
-    runInterfaceDesugaringProcessors(IncludeAllResources, executorService);
+    runInterfaceDesugaringProcessorsForR8(IncludeAllResources, executorService);
     feedback.updateVisibleOptimizationInfo();
 
-    printPhase("Utility classes synthesis");
-    synthesizeEnumUnboxingUtilityMethods(executorService);
-
-    printPhase("Desugared library API Conversion finalization");
-    generateDesugaredLibraryAPIWrappers(builder, executorService);
-
     if (serviceLoaderRewriter != null) {
       processSynthesizedServiceLoaderMethods(
           serviceLoaderRewriter.getServiceLoadMethods(), executorService);
@@ -966,14 +952,6 @@
     removeDeadCodeAndFinalizeIR(code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
   }
 
-  private void generateDesugaredLibraryAPIWrappers(
-      DexApplication.Builder<?> builder, ExecutorService executorService)
-      throws ExecutionException {
-    if (desugaredLibraryAPIConverter != null) {
-      desugaredLibraryAPIConverter.finalizeWrappers(builder, this, executorService);
-    }
-  }
-
   private void clearDexMethodCompilationState() {
     appView.appInfo().classes().forEach(this::clearDexMethodCompilationState);
   }
@@ -1055,16 +1033,6 @@
     return options.useSmaliSyntax ? method.toSmaliString(null) : method.codeToString();
   }
 
-  List<CodeOptimization> getOptimizationsForPrimaryIRProcessing() {
-    // TODO(b/140766440): Remove unnecessary steps once all sub steps are converted.
-    return ImmutableList.of(this::optimize);
-  }
-
-  List<CodeOptimization> getOptimizationsForPostIRProcessing() {
-    // TODO(b/140766440): Remove unnecessary steps once all sub steps are converted.
-    return ImmutableList.of(this::optimize);
-  }
-
   // TODO(b/140766440): Make this receive a list of CodeOptimizations to conduct.
   public Timing processDesugaredMethod(
       ProgramMethod method,
@@ -1151,7 +1119,7 @@
       options.testing.hookInIrConversion.run();
     }
 
-    if (!needsIRConversion(method) || options.skipIR) {
+    if (!needsIRConversion() || options.skipIR) {
       feedback.markProcessed(method.getDefinition(), ConstraintWithTarget.NEVER);
       return Timing.empty();
     }
@@ -1509,10 +1477,9 @@
 
     previous = printMethod(code, "IR after interface method rewriting (SSA)", previous);
 
-    // This pass has to be after interfaceMethodRewriter and BackportedMethodRewriter.
     if (desugaredLibraryAPIConverter != null
-        && (!appView.enableWholeProgramOptimizations()
-            || methodProcessor.isPrimaryMethodProcessor())) {
+        && appView.enableWholeProgramOptimizations()
+        && methodProcessor.isPrimaryMethodProcessor()) {
       timing.begin("Desugar library API");
       desugaredLibraryAPIConverter.desugar(code);
       timing.end();
@@ -1664,7 +1631,8 @@
       timing.end();
     }
 
-    if (appView.appInfo().withLiveness().isPinned(code.method().getReference())) {
+    if (appView.appInfo().withLiveness().isPinned(code.context().getReference())
+        || !appView.options().isOptimizing()) {
       return;
     }
 
@@ -1691,7 +1659,13 @@
           .recordStaticValues(method.getHolder(), staticFieldValues);
     }
     methodOptimizationInfoCollector.collectMethodOptimizationInfo(
-        method, code, feedback, dynamicTypeOptimization, instanceFieldInitializationInfos, timing);
+        method,
+        code,
+        feedback,
+        dynamicTypeOptimization,
+        instanceFieldInitializationInfos,
+        methodProcessor,
+        timing);
   }
 
   public void removeDeadCodeAndFinalizeIR(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 0cf836d..837d5fc 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -681,7 +681,7 @@
       new DestructivePhiTypeUpdater(appView).recomputeAndPropagateTypes(code, affectedPhis);
     }
     assert code.isConsistentSSABeforeTypesAreCorrect();
-    assert code.hasNoVerticallyMergedClasses(appView);
+    assert code.hasNoMergedClasses(appView);
   }
 
   private DexField rewriteFieldReference(FieldLookupResult lookup, ProgramMethod context) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 9dfe2e3..13b7140 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+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.InstanceInitializerInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -62,6 +63,9 @@
   void setClassInlinerMethodConstraint(
       ProgramMethod method, ClassInlinerMethodConstraint classInlinerConstraint);
 
+  void setEnumUnboxerMethodClassification(
+      ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification);
+
   void setInstanceInitializerInfoCollection(
       DexEncodedMethod method, InstanceInitializerInfoCollection instanceInitializerInfoCollection);
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
deleted file mode 100644
index 0d1c587..0000000
--- a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.conversion;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
-
-class NeedsIRDesugarUseRegistry extends UseRegistry {
-
-  private boolean needsDesugaring = false;
-  private final ProgramMethod context;
-  private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
-
-  public NeedsIRDesugarUseRegistry(
-      ProgramMethod method,
-      AppView<?> appView,
-      DesugaredLibraryAPIConverter desugaredLibraryAPIConverter) {
-    super(appView.dexItemFactory());
-    this.context = method;
-    this.desugaredLibraryAPIConverter = desugaredLibraryAPIConverter;
-  }
-
-  public boolean needsDesugaring() {
-    return needsDesugaring;
-  }
-
-  @Override
-  public void registerInitClass(DexType type) {
-    if (!needsDesugaring
-        && desugaredLibraryAPIConverter != null
-        && desugaredLibraryAPIConverter.canConvert(type)) {
-      needsDesugaring = true;
-    }
-  }
-
-  @Override
-  public void registerInvokeVirtual(DexMethod method) {
-    registerDesugaredLibraryAPIConverter(method);
-  }
-
-  @Override
-  public void registerInvokeDirect(DexMethod method) {
-    registerDesugaredLibraryAPIConverter(method);
-  }
-
-  private void registerDesugaredLibraryAPIConverter(DexMethod method) {
-    if (!needsDesugaring) {
-      needsDesugaring =
-          desugaredLibraryAPIConverter != null
-              && desugaredLibraryAPIConverter.shouldRewriteInvoke(method);
-    }
-  }
-
-  @Override
-  public void registerInvokeStatic(DexMethod method) {
-    registerDesugaredLibraryAPIConverter(method);
-  }
-
-  @Override
-  public void registerInvokeInterface(DexMethod method) {
-    registerDesugaredLibraryAPIConverter(method);
-  }
-
-  @Override
-  public void registerInvokeStatic(DexMethod method, boolean itf) {
-    registerInvokeStatic(method);
-  }
-
-  @Override
-  public void registerCallSite(DexCallSite callSite) {
-    super.registerCallSite(callSite);
-    needsDesugaring = true;
-  }
-
-  @Override
-  public void registerInvokeSuper(DexMethod method) {
-    registerDesugaredLibraryAPIConverter(method);
-  }
-
-  @Override
-  public void registerInstanceFieldRead(DexField field) {}
-
-  @Override
-  public void registerInstanceFieldWrite(DexField field) {}
-
-  @Override
-  public void registerNewInstance(DexType type) {}
-
-  @Override
-  public void registerStaticFieldRead(DexField field) {}
-
-  @Override
-  public void registerStaticFieldWrite(DexField field) {}
-
-  @Override
-  public void registerTypeReference(DexType type) {}
-
-  @Override
-  public void registerInstanceOf(DexType type) {}
-}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
index e40cae1..1ca91a7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
@@ -25,6 +25,10 @@
     this.wave = wave;
   }
 
+  public static Builder builder(ProcessorContext processorContext) {
+    return new Builder(processorContext);
+  }
+
   public static OneTimeMethodProcessor create(ProgramMethod methodToProcess, AppView<?> appView) {
     return create(SortedProgramMethodSet.create(methodToProcess), appView);
   }
@@ -78,4 +82,23 @@
       prepareForWaveExtensionProcessing();
     }
   }
+
+  public static class Builder {
+
+    private final SortedProgramMethodSet methodsToProcess = SortedProgramMethodSet.create();
+    private final ProcessorContext processorContext;
+
+    Builder(ProcessorContext processorContext) {
+      this.processorContext = processorContext;
+    }
+
+    public Builder add(ProgramMethod methodToProcess) {
+      methodsToProcess.add(methodToProcess);
+      return this;
+    }
+
+    public OneTimeMethodProcessor build() {
+      return create(methodsToProcess, processorContext);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
index 2d84083..cd9a9ec 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
@@ -7,9 +7,13 @@
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.utils.ThrowingConsumer;
+import java.util.function.Consumer;
 
 /**
  * Abstracts a collection of low-level desugarings (i.e., mappings from class-file instructions to
@@ -56,5 +60,11 @@
   public abstract <T extends Throwable> void withD8NestBasedAccessDesugaring(
       ThrowingConsumer<D8NestBasedAccessDesugaring, T> consumer) throws T;
 
+  public abstract InterfaceMethodProcessorFacade getInterfaceMethodPostProcessingDesugaring(
+      Flavor flavor);
+
   public abstract RetargetingInfo getRetargetingInfo();
+
+  public abstract void withDesugaredLibraryAPIConverter(
+      Consumer<DesugaredLibraryAPIConverter> consumer);
 }
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 f05c16b..cb9aeb9 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
@@ -15,6 +15,7 @@
 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.invokespecial.InvokeSpecialBridgeInfo;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaringEventConsumer;
@@ -25,6 +26,7 @@
 import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.twr.TwrCloseResourceDesugaringEventConsumer;
 import com.android.tools.r8.shaking.Enqueuer.SyntheticAdditions;
+import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -33,6 +35,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
@@ -48,7 +51,8 @@
         RecordDesugaringEventConsumer,
         TwrCloseResourceDesugaringEventConsumer,
         InterfaceMethodDesugaringEventConsumer,
-        DesugaredLibraryRetargeterInstructionEventConsumer {
+        DesugaredLibraryRetargeterInstructionEventConsumer,
+        DesugaredLibraryAPIConverterEventConsumer {
 
   public static D8CfInstructionDesugaringEventConsumer createForD8(
       D8MethodProcessor methodProcessor) {
@@ -68,6 +72,26 @@
     return new CfInstructionDesugaringEventConsumer() {
 
       @Override
+      public void acceptWrapperProgramClass(DexProgramClass clazz) {
+        assert false;
+      }
+
+      @Override
+      public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
+        assert false;
+      }
+
+      @Override
+      public void acceptAPIConversion(ProgramMethod method) {
+        assert false;
+      }
+
+      @Override
+      public void acceptSuperAPIConversion(ProgramMethod method) {
+        assert false;
+      }
+
+      @Override
       public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
         assert false;
       }
@@ -147,6 +171,8 @@
 
     private final Map<DexReference, InvokeSpecialBridgeInfo> pendingInvokeSpecialBridges =
         new LinkedHashMap<>();
+    private final Map<DexProgramClass, SortedProgramMethodSet> pendingSuperAPIConversions =
+        new ConcurrentHashMap<>();
     private final List<LambdaClass> synthesizedLambdaClasses = new ArrayList<>();
 
     private D8CfInstructionDesugaringEventConsumer(D8MethodProcessor methodProcessor) {
@@ -229,14 +255,48 @@
       methodProcessor.scheduleDesugaredMethodForProcessing(method);
     }
 
+    @Override
+    public void acceptWrapperProgramClass(DexProgramClass clazz) {
+      methodProcessor.scheduleDesugaredMethodsForProcessing(clazz.programMethods());
+    }
+
+    @Override
+    public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptAPIConversion(ProgramMethod method) {
+      methodProcessor.scheduleDesugaredMethodForProcessing(method);
+    }
+
+    @Override
+    public void acceptSuperAPIConversion(ProgramMethod method) {
+      SortedProgramMethodSet superAPIConversions =
+          pendingSuperAPIConversions.computeIfAbsent(
+              method.getHolder(), ignored -> SortedProgramMethodSet.createConcurrent());
+      superAPIConversions.add(method);
+    }
+
     public List<ProgramMethod> finalizeDesugaring(
         AppView<?> appView, ClassConverterResult.Builder classConverterResultBuilder) {
       List<ProgramMethod> needsProcessing = new ArrayList<>();
       finalizeInvokeSpecialDesugaring(appView, needsProcessing::add);
       finalizeLambdaDesugaring(classConverterResultBuilder, needsProcessing::add);
+      finalizeSuperAPIConversionDesugaring(needsProcessing::add);
       return needsProcessing;
     }
 
+    private void finalizeSuperAPIConversionDesugaring(Consumer<ProgramMethod> needsProcessing) {
+      for (SortedProgramMethodSet superAPIConversions : pendingSuperAPIConversions.values()) {
+        for (ProgramMethod superAPIConversion : superAPIConversions) {
+          superAPIConversion.getHolder().addDirectMethod(superAPIConversion.getDefinition());
+          needsProcessing.accept(superAPIConversion);
+        }
+      }
+      pendingSuperAPIConversions.clear();
+    }
+
     private void finalizeInvokeSpecialDesugaring(
         AppView<?> appView, Consumer<ProgramMethod> needsProcessing) {
       // Fixup the code of the new private methods have that been synthesized.
@@ -348,6 +408,30 @@
     }
 
     @Override
+    public void acceptWrapperProgramClass(DexProgramClass clazz) {
+      // TODO(b/189912077): There should be nothing to do.
+      assert false;
+    }
+
+    @Override
+    public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
+      // TODO(b/189912077): Should be added to live non program types.
+      assert false;
+    }
+
+    @Override
+    public void acceptAPIConversion(ProgramMethod method) {
+      // TODO(b/189912077): There should be nothing to do.
+      assert false;
+    }
+
+    @Override
+    public void acceptSuperAPIConversion(ProgramMethod method) {
+      // TODO(b/189912077): Manage pending conversions.
+      assert false;
+    }
+
+    @Override
     public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
       // Intentionally empty. The backported method will be hit by the tracing in R8 as if it was
       // present in the input code, and thus nothing needs to be done.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaring.java
index 2a2de5f..38a0413 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaring.java
@@ -3,7 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.desugar;
 
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
 public interface CfPostProcessingDesugaring {
 
-  void postProcessingDesugaring(CfPostProcessingDesugaringEventConsumer eventConsumer);
+  void postProcessingDesugaring(
+      CfPostProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService)
+      throws ExecutionException;
 }
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 f659ad8..367e0ca 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
@@ -4,17 +4,24 @@
 package com.android.tools.r8.ir.desugar;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterPostProcessor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
-import java.util.Collections;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
+import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 
 public abstract class CfPostProcessingDesugaringCollection {
 
   public static CfPostProcessingDesugaringCollection create(
-      AppView<?> appView, RetargetingInfo retargetingInfo) {
+      AppView<?> appView,
+      InterfaceMethodProcessorFacade interfaceMethodProcessorFacade,
+      RetargetingInfo retargetingInfo) {
     if (appView.options().desugarState.isOn()) {
-      return NonEmptyCfPostProcessingDesugaringCollection.create(appView, retargetingInfo);
+      return NonEmptyCfPostProcessingDesugaringCollection.create(
+          appView, interfaceMethodProcessorFacade, retargetingInfo);
     }
     return empty();
   }
@@ -24,7 +31,8 @@
   }
 
   public abstract void postProcessingDesugaring(
-      CfPostProcessingDesugaringEventConsumer eventConsumer);
+      CfPostProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService)
+      throws ExecutionException;
 
   public static class NonEmptyCfPostProcessingDesugaringCollection
       extends CfPostProcessingDesugaringCollection {
@@ -37,19 +45,37 @@
     }
 
     public static CfPostProcessingDesugaringCollection create(
-        AppView<?> appView, RetargetingInfo retargetingInfo) {
-      if (appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
+        AppView<?> appView,
+        InterfaceMethodProcessorFacade interfaceMethodProcessorFacade,
+        RetargetingInfo retargetingInfo) {
+      ArrayList<CfPostProcessingDesugaring> desugarings = new ArrayList<>();
+      if (!appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
+        desugarings.add(new DesugaredLibraryRetargeterPostProcessor(appView, retargetingInfo));
+      }
+      if (interfaceMethodProcessorFacade != null) {
+        desugarings.add(interfaceMethodProcessorFacade);
+      }
+      DesugaredLibraryAPIConverter desugaredLibraryAPIConverter =
+          appView.rewritePrefix.isRewriting() && !appView.enableWholeProgramOptimizations()
+              ? new DesugaredLibraryAPIConverter(appView, null)
+              : null;
+      // At this point the desugaredLibraryAPIConverter is required to be last to generate
+      // call-backs on the forwarding methods.
+      if (desugaredLibraryAPIConverter != null) {
+        desugarings.add(desugaredLibraryAPIConverter);
+      }
+      if (desugarings.isEmpty()) {
         return empty();
       }
-      return new NonEmptyCfPostProcessingDesugaringCollection(
-          Collections.singletonList(
-              new DesugaredLibraryRetargeterPostProcessor(appView, retargetingInfo)));
+      return new NonEmptyCfPostProcessingDesugaringCollection(desugarings);
     }
 
     @Override
-    public void postProcessingDesugaring(CfPostProcessingDesugaringEventConsumer eventConsumer) {
+    public void postProcessingDesugaring(
+        CfPostProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService)
+        throws ExecutionException {
       for (CfPostProcessingDesugaring desugaring : desugarings) {
-        desugaring.postProcessingDesugaring(eventConsumer);
+        desugaring.postProcessingDesugaring(eventConsumer, executorService);
       }
     }
   }
@@ -67,7 +93,9 @@
     }
 
     @Override
-    public void postProcessingDesugaring(CfPostProcessingDesugaringEventConsumer eventConsumer) {
+    public void postProcessingDesugaring(
+        CfPostProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService)
+        throws ExecutionException {
       // Intentionally empty.
     }
   }
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 ec4f197..f5af1f1 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
@@ -10,9 +10,12 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.D8MethodProcessor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
+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.itf.InterfaceProcessingDesugaringEventConsumer;
 import com.android.tools.r8.shaking.Enqueuer.SyntheticAdditions;
-import java.util.function.Consumer;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import java.util.concurrent.ExecutionException;
 
 /**
  * Specialized Event consumer for desugaring finalization. During finalization, it is not possible
@@ -20,40 +23,36 @@
  * explicit calls must be done here.
  */
 public abstract class CfPostProcessingDesugaringEventConsumer
-    implements DesugaredLibraryRetargeterPostProcessingEventConsumer {
-  protected DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
-
-  protected CfPostProcessingDesugaringEventConsumer(AppView<?> appView) {
-    this.desugaredLibraryAPIConverter = new DesugaredLibraryAPIConverter(appView, null);
-  }
+    implements DesugaredLibraryRetargeterPostProcessingEventConsumer,
+        InterfaceProcessingDesugaringEventConsumer,
+        DesugaredLibraryAPIConverterPostProcessingEventConsumer {
 
   public static D8CfPostProcessingDesugaringEventConsumer createForD8(
-      D8MethodProcessor methodProcessor, AppView<?> appView) {
-    return new D8CfPostProcessingDesugaringEventConsumer(methodProcessor, appView);
+      D8MethodProcessor methodProcessor) {
+    return new D8CfPostProcessingDesugaringEventConsumer(methodProcessor);
   }
 
   public static R8PostProcessingDesugaringEventConsumer createForR8(
-      AppView<?> appView, Consumer<ProgramMethod> methodConsumer, SyntheticAdditions additions) {
-    return new R8PostProcessingDesugaringEventConsumer(appView, methodConsumer, additions);
+      AppView<?> appView, SyntheticAdditions additions) {
+    return new R8PostProcessingDesugaringEventConsumer(appView, additions);
   }
 
-  public void finalizeDesugaring() {
-    desugaredLibraryAPIConverter.generateTrackingWarnings();
-  }
+  public abstract void finalizeDesugaring() throws ExecutionException;
 
   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.
+    private final ProgramMethodSet methodsToReprocess = ProgramMethodSet.createConcurrent();
 
-    private D8CfPostProcessingDesugaringEventConsumer(
-        D8MethodProcessor methodProcessor, AppView<?> appView) {
-      super(appView);
+    private D8CfPostProcessingDesugaringEventConsumer(D8MethodProcessor methodProcessor) {
       this.methodProcessor = methodProcessor;
     }
 
     @Override
     public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
-      methodProcessor.scheduleDesugaredMethodsForProcessing(clazz.programMethods());
+      methodsToReprocess.addAll(clazz.programMethods());
     }
 
     @Override
@@ -68,27 +67,55 @@
 
     @Override
     public void acceptForwardingMethod(ProgramMethod method) {
-      methodProcessor.scheduleDesugaredMethodForProcessing(method);
-      // TODO(b/189912077): Uncomment when API conversion is performed cf to cf in D8.
-      // desugaredLibraryAPIConverter.generateCallbackIfRequired(method);
+      methodsToReprocess.add(method);
+    }
+
+    @Override
+    public void acceptCompanionClassClinit(ProgramMethod method) {
+      methodsToReprocess.add(method);
+    }
+
+    @Override
+    public void acceptEmulatedInterfaceMethod(ProgramMethod method) {
+      methodsToReprocess.add(method);
+    }
+
+    @Override
+    public void acceptAPIConversionCallback(ProgramMethod method) {
+      methodsToReprocess.add(method);
+    }
+
+    @Override
+    public void finalizeDesugaring() throws ExecutionException {
+      assert methodProcessor.verifyNoPendingMethodProcessing();
+      methodProcessor.newWave();
+      methodProcessor.scheduleDesugaredMethodsForProcessing(methodsToReprocess);
+      methodProcessor.awaitMethodProcessing();
     }
   }
 
   public static class R8PostProcessingDesugaringEventConsumer
       extends CfPostProcessingDesugaringEventConsumer {
-    private final Consumer<ProgramMethod> methodConsumer;
     private final SyntheticAdditions additions;
 
-    protected R8PostProcessingDesugaringEventConsumer(
-        AppView<?> appView, Consumer<ProgramMethod> methodConsumer, SyntheticAdditions additions) {
-      super(appView);
-      this.methodConsumer = methodConsumer;
+    // The desugaredLibraryAPIConverter is required here because call-backs need to be generated
+    // once forwarding methods are generated. We should be able to remove it once the interface
+    // method desugaring and the API converter are moved cf to cf in R8.
+    private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
+
+    R8PostProcessingDesugaringEventConsumer(AppView<?> appView, SyntheticAdditions additions) {
+      this.desugaredLibraryAPIConverter = new DesugaredLibraryAPIConverter(appView, null);
       this.additions = additions;
     }
 
     @Override
+    public void finalizeDesugaring() throws ExecutionException {
+      desugaredLibraryAPIConverter.generateTrackingWarnings();
+    }
+
+    @Override
     public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
-      clazz.programMethods().forEach(methodConsumer);
+      additions.addLiveMethods(clazz.programMethods());
     }
 
     @Override
@@ -103,11 +130,23 @@
 
     @Override
     public void acceptForwardingMethod(ProgramMethod method) {
-      methodConsumer.accept(method);
-      ProgramMethod callback = desugaredLibraryAPIConverter.generateCallbackIfRequired(method);
-      if (callback != null) {
-        methodConsumer.accept(callback);
-      }
+      additions.addLiveMethod(method);
+      desugaredLibraryAPIConverter.generateCallbackIfRequired(method, this);
+    }
+
+    @Override
+    public void acceptCompanionClassClinit(ProgramMethod method) {
+      assert false : "TODO(b/183998768): Support Interface processing in R8";
+    }
+
+    @Override
+    public void acceptEmulatedInterfaceMethod(ProgramMethod method) {
+      assert false : "TODO(b/183998768): Support Interface processing in R8";
+    }
+
+    @Override
+    public void acceptAPIConversionCallback(ProgramMethod method) {
+      additions.addLiveMethod(method);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
index 6cd9be6..c78f6db 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
@@ -7,9 +7,13 @@
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.EmptyCfClassDesugaringCollection;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.utils.ThrowingConsumer;
+import java.util.function.Consumer;
 
 public class EmptyCfInstructionDesugaringCollection extends CfInstructionDesugaringCollection {
 
@@ -58,7 +62,17 @@
   }
 
   @Override
+  public InterfaceMethodProcessorFacade getInterfaceMethodPostProcessingDesugaring(Flavor flavor) {
+    return null;
+  }
+
+  @Override
   public RetargetingInfo getRetargetingInfo() {
     return null;
   }
+
+  @Override
+  public void withDesugaredLibraryAPIConverter(Consumer<DesugaredLibraryAPIConverter> consumer) {
+    // Intentionally empty.
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index 8437128..b1ea3a0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -14,10 +14,13 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.EmptyCfClassDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.NonEmptyCfClassDesugaringCollection;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaring;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor;
 import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
@@ -33,6 +36,7 @@
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import java.util.function.Consumer;
 
 public class NonEmptyCfInstructionDesugaringCollection extends CfInstructionDesugaringCollection {
 
@@ -42,6 +46,8 @@
   private final NestBasedAccessDesugaring nestBasedAccessDesugaring;
   private final RecordRewriter recordRewriter;
   private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
+  private final InterfaceMethodRewriter interfaceMethodRewriter;
+  private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
 
   NonEmptyCfInstructionDesugaringCollection(AppView<?> appView) {
     this.appView = appView;
@@ -49,6 +55,8 @@
       this.nestBasedAccessDesugaring = null;
       this.recordRewriter = null;
       this.desugaredLibraryRetargeter = null;
+      this.interfaceMethodRewriter = null;
+      this.desugaredLibraryAPIConverter = null;
       return;
     }
     this.nestBasedAccessDesugaring = NestBasedAccessDesugaring.create(appView);
@@ -70,9 +78,23 @@
     // TODO(b/183998768): Enable interface method rewriter cf to cf also in R8.
     if (appView.options().isInterfaceMethodDesugaringEnabled()
         && !appView.enableWholeProgramOptimizations()) {
-      desugarings.add(
+      interfaceMethodRewriter =
           new InterfaceMethodRewriter(
-              appView, backportedMethodRewriter, desugaredLibraryRetargeter));
+              appView, backportedMethodRewriter, desugaredLibraryRetargeter);
+      desugarings.add(interfaceMethodRewriter);
+    } else {
+      interfaceMethodRewriter = null;
+    }
+    desugaredLibraryAPIConverter =
+        appView.rewritePrefix.isRewriting() && !appView.enableWholeProgramOptimizations()
+            ? new DesugaredLibraryAPIConverter(
+                appView,
+                interfaceMethodRewriter,
+                desugaredLibraryRetargeter,
+                backportedMethodRewriter)
+            : null;
+    if (desugaredLibraryAPIConverter != null) {
+      desugarings.add(desugaredLibraryAPIConverter);
     }
     desugarings.add(new LambdaInstructionDesugaring(appView));
     desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
@@ -306,10 +328,24 @@
   }
 
   @Override
+  public InterfaceMethodProcessorFacade getInterfaceMethodPostProcessingDesugaring(Flavor flavor) {
+    return interfaceMethodRewriter != null
+        ? interfaceMethodRewriter.getPostProcessingDesugaring(flavor)
+        : null;
+  }
+
+  @Override
   public RetargetingInfo getRetargetingInfo() {
     if (desugaredLibraryRetargeter != null) {
       return desugaredLibraryRetargeter.getRetargetingInfo();
     }
     return null;
   }
+
+  @Override
+  public void withDesugaredLibraryAPIConverter(Consumer<DesugaredLibraryAPIConverter> consumer) {
+    if (desugaredLibraryAPIConverter != null) {
+      consumer.accept(desugaredLibraryAPIConverter);
+    }
+  }
 }
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 42db4ed..3fb9030 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
@@ -4,11 +4,15 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary;
 
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DebugLocalInfo;
-import com.android.tools.r8.graph.DexApplication;
+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.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -16,6 +20,9 @@
 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.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.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -24,11 +31,22 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.FreshLocalProvider;
+import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverterEventConsumer.DesugaredLibraryAPIConverterPostProcessingEventConsumer;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
+import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConversionCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.OptionalBool;
@@ -37,15 +55,17 @@
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
+import org.jetbrains.annotations.Nullable;
+import org.objectweb.asm.Opcodes;
 
 // I convert library calls with desugared parameters/return values so they can work normally.
 // In the JSON of the desugared library, one can specify conversions between desugared and
@@ -61,22 +81,31 @@
 // DesugarType is only a rewritten type (generated through rewriting of type).
 // The type, from the library, may either be rewritten to the desugarType,
 // or be a rewritten type (generated through rewriting of vivifiedType).
-public class DesugaredLibraryAPIConverter {
+public class DesugaredLibraryAPIConverter
+    implements CfInstructionDesugaring, CfPostProcessingDesugaring {
 
   static final String VIVIFIED_PREFIX = "$-vivified-$.";
   public static final String DESCRIPTOR_VIVIFIED_PREFIX = "L$-vivified-$/";
+  private static final String SUPER_CONVERSION_METHOD_PREFIX = "api$super$conversion$";
 
   private final AppView<?> appView;
   private final DexItemFactory factory;
   // For debugging only, allows to assert that synthesized code in R8 have been synthesized in the
   // Enqueuer and not during IR processing.
   private final Mode mode;
+  // This is used to filter out double desugaring on backported methods.
+  private final BackportedMethodRewriter backportedMethodRewriter;
+  private final InterfaceMethodRewriter interfaceMethodRewriter;
+  private final DesugaredLibraryRetargeter retargeter;
+
   private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
   private final Map<DexClass, Set<DexEncodedMethod>> callBackMethods = new IdentityHashMap<>();
   private final Map<DexProgramClass, List<DexEncodedMethod>> pendingCallBackMethods =
       new IdentityHashMap<>();
   private final Set<DexMethod> trackedCallBackAPIs;
   private final Set<DexMethod> trackedAPIs;
+  private final MethodAccessFlags superAPIConversionMethodAccessFlags =
+      MethodAccessFlags.createPublicStaticSynthetic();
 
   public enum Mode {
     GENERATE_CALLBACKS_AND_WRAPPERS,
@@ -84,9 +113,29 @@
   }
 
   public DesugaredLibraryAPIConverter(AppView<?> appView, Mode mode) {
+    this(appView, mode, null, null, null);
+  }
+
+  public DesugaredLibraryAPIConverter(
+      AppView<?> appView,
+      InterfaceMethodRewriter interfaceMethodRewriter,
+      DesugaredLibraryRetargeter retargeter,
+      BackportedMethodRewriter backportedMethodRewriter) {
+    this(appView, null, interfaceMethodRewriter, retargeter, backportedMethodRewriter);
+  }
+
+  private DesugaredLibraryAPIConverter(
+      AppView<?> appView,
+      Mode mode,
+      InterfaceMethodRewriter interfaceMethodRewriter,
+      DesugaredLibraryRetargeter retargeter,
+      BackportedMethodRewriter backportedMethodRewriter) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
     this.mode = mode;
+    this.interfaceMethodRewriter = interfaceMethodRewriter;
+    this.retargeter = retargeter;
+    this.backportedMethodRewriter = backportedMethodRewriter;
     this.wrapperSynthesizor = new DesugaredLibraryWrapperSynthesizer(appView, this);
     if (appView.options().testing.trackDesugaredAPIConversions) {
       trackedCallBackAPIs = Sets.newConcurrentHashSet();
@@ -97,6 +146,95 @@
     }
   }
 
+  // TODO(b/191656218): Consider parallelizing post processing across classes instead of per
+  // implementor
+  // method.
+  @Override
+  public void postProcessingDesugaring(
+      CfPostProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService) {
+    assert noPendingWrappersOrConversions();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (!appView.isAlreadyLibraryDesugared(clazz)) {
+        ArrayList<DexEncodedMethod> callbacks = new ArrayList<>();
+        for (ProgramMethod virtualProgramMethod : clazz.virtualProgramMethods()) {
+          if (shouldRegisterCallback(virtualProgramMethod)) {
+            if (trackedCallBackAPIs != null) {
+              trackedCallBackAPIs.add(virtualProgramMethod.getReference());
+            }
+            ProgramMethod callback =
+                generateCallbackMethod(
+                    virtualProgramMethod.getDefinition(),
+                    virtualProgramMethod.getHolder(),
+                    eventConsumer);
+            callbacks.add(callback.getDefinition());
+          }
+        }
+        if (!callbacks.isEmpty()) {
+          clazz.addVirtualMethods(callbacks);
+        }
+      }
+    }
+    assert noPendingWrappersOrConversions();
+    generateTrackingWarnings();
+  }
+
+  private boolean noPendingWrappersOrConversions() {
+    for (DexProgramClass pendingSyntheticClass :
+        appView.getSyntheticItems().getPendingSyntheticClasses()) {
+      assert !isAPIConversionSyntheticType(pendingSyntheticClass.type);
+    }
+    return true;
+  }
+
+  @Override
+  public Collection<CfInstruction> desugarInstruction(
+      CfInstruction instruction,
+      FreshLocalProvider freshLocalProvider,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext,
+      DexItemFactory dexItemFactory) {
+    assert !appView.enableWholeProgramOptimizations();
+    if (needsDesugaring(instruction, context)) {
+      assert instruction.isInvoke();
+      return Collections.singletonList(
+          rewriteLibraryInvoke(
+              instruction.asInvoke(), methodProcessingContext, eventConsumer, context));
+    }
+    return null;
+  }
+
+  @Override
+  public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+    if (!instruction.isInvoke()) {
+      return false;
+    }
+    if (skipDesugaring(context)) {
+      return false;
+    }
+    CfInvoke invoke = instruction.asInvoke();
+    return shouldRewriteInvoke(
+        invoke.getMethod(), invoke.getInvokeType(context), invoke.isInterface(), context);
+  }
+
+  // We should not generate conversion for Wrappers and for conversion methods.
+  private boolean skipDesugaring(ProgramMethod method) {
+    return isAPIConversionSyntheticType(method.getHolderType())
+        || isSuperAPIConversionMethod(method);
+  }
+
+  private boolean isSuperAPIConversionMethod(ProgramMethod method) {
+    return method.getDefinition().isD8R8Synthesized()
+        && method.getAccessFlags().equals(superAPIConversionMethodAccessFlags)
+        && method.getName().toString().startsWith(SUPER_CONVERSION_METHOD_PREFIX);
+  }
+
+  private boolean isAPIConversionSyntheticType(DexType type) {
+    return wrapperSynthesizor.isSyntheticWrapper(type)
+        || appView.getSyntheticItems().isSyntheticOfKind(type, SyntheticKind.API_CONVERSION);
+  }
+
   public static boolean isVivifiedType(DexType type) {
     return type.descriptor.toString().startsWith(DESCRIPTOR_VIVIFIED_PREFIX);
   }
@@ -107,6 +245,8 @@
 
   public void desugar(IRCode code) {
 
+    assert appView.enableWholeProgramOptimizations();
+
     if (wrapperSynthesizor.isSyntheticWrapper(code.method().getHolderType())) {
       return;
     }
@@ -130,13 +270,31 @@
         DexMethod invokedMethod = invokeMethod.getInvokedMethod();
         // Library methods do not understand desugared types, hence desugared types have to be
         // converted around non desugared library calls for the invoke to resolve.
-        if (shouldRewriteInvoke(invokedMethod)) {
+        if (invokedMethod != null
+            && shouldRewriteInvoke(
+                invokedMethod,
+                invokeMethod.getType(),
+                invokeMethod.getInterfaceBit(),
+                code.context())) {
           rewriteLibraryInvoke(code, invokeMethod, iterator, blockIterator);
         }
       }
     }
   }
 
+  @Nullable
+  private DexMethod getMethodForDesugaring(
+      DexMethod invokedMethod, boolean isInvokeSuper, ProgramMethod context) {
+    if (isInvokeSuper) {
+      // TODO(b/191656218): Use lookupInvokeSpecial instead when this is all to Cf.
+      DexClassAndMethod result =
+          appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context);
+      return result != null ? result.getReference() : null;
+    }
+    // TODO(b/192439456): Make a test to prove resolution is needed here and fix it.
+    return invokedMethod;
+  }
+
   private boolean validateCallbackWasGeneratedInEnqueuer(ProgramMethod method) {
     if (!shouldRegisterCallback(method)) {
       return true;
@@ -146,7 +304,17 @@
     return true;
   }
 
-  public boolean shouldRewriteInvoke(DexMethod invokedMethod) {
+  private boolean shouldRewriteInvoke(
+      DexMethod unresolvedInvokedMethod,
+      Type invokeType,
+      Boolean isInterface,
+      ProgramMethod context) {
+    DexMethod invokedMethod =
+        getMethodForDesugaring(unresolvedInvokedMethod, invokeType == Type.SUPER, context);
+    if (invokedMethod == null) {
+      // Implies a resolution/look-up failure, we do not convert to keep the runtime error.
+      return false;
+    }
     if (appView.rewritePrefix.hasRewrittenType(invokedMethod.holder, appView)
         || invokedMethod.holder.isArrayType()) {
       return false;
@@ -155,6 +323,19 @@
     if (dexClass == null || !dexClass.isLibraryClass()) {
       return false;
     }
+    if (interfaceMethodRewriter != null
+        && interfaceMethodRewriter.needsRewriting(invokedMethod, invokeType, context)) {
+      return false;
+    }
+    assert retargeter == null || isInterface != null;
+    if (retargeter != null
+        && retargeter.hasNewInvokeTarget(invokedMethod, false, invokeType == Type.SUPER, context)) {
+      return false;
+    }
+    if (backportedMethodRewriter != null
+        && backportedMethodRewriter.methodIsBackport(invokedMethod)) {
+      return false;
+    }
     return appView.rewritePrefix.hasRewrittenTypeInSignature(invokedMethod.proto, appView);
   }
 
@@ -164,16 +345,18 @@
     }
   }
 
-  public ProgramMethod generateCallbackIfRequired(ProgramMethod method) {
+  public void generateCallbackIfRequired(
+      ProgramMethod method, DesugaredLibraryAPIConverterPostProcessingEventConsumer eventConsumer) {
     if (!shouldRegisterCallback(method)) {
-      return null;
+      return;
     }
     if (trackedCallBackAPIs != null) {
       trackedCallBackAPIs.add(method.getReference());
     }
-    ProgramMethod callback = generateCallbackMethod(method.getDefinition(), method.getHolder());
-    method.getHolder().addVirtualMethod(callback.getDefinition());
-    return callback;
+    ProgramMethod callback =
+        generateCallbackMethod(method.getDefinition(), method.getHolder(), eventConsumer);
+    callback.getHolder().addVirtualMethod(callback.getDefinition());
+    assert noPendingWrappersOrConversions();
   }
 
   public boolean shouldRegisterCallback(ProgramMethod method) {
@@ -187,6 +370,7 @@
     DexEncodedMethod definition = method.getDefinition();
     if (definition.isPrivateMethod()
         || definition.isStatic()
+        || definition.isAbstract()
         || definition.isLibraryMethodOverride().isFalse()) {
       return false;
     }
@@ -311,19 +495,9 @@
     return appView.dexItemFactory().createMethod(holder, newProto, originalMethod.name);
   }
 
-  public void finalizeWrappers(
-      DexApplication.Builder<?> builder, IRConverter irConverter, ExecutorService executorService)
-      throws ExecutionException {
-    // In D8, we generate the wrappers here. In R8, wrappers have already been generated in the
-    // enqueuer, so nothing needs to be done.
-    if (appView.enableWholeProgramOptimizations()) {
-      return;
-    }
-    SortedProgramMethodSet callbacks = generateCallbackMethods();
-    irConverter.processMethodsConcurrently(callbacks, executorService);
-    if (appView.options().isDesugaredLibraryCompilation()) {
-      wrapperSynthesizor.finalizeWrappersForL8();
-    }
+  public void ensureWrappersForL8(CfInstructionDesugaringEventConsumer eventConsumer) {
+    assert appView.options().isDesugaredLibraryCompilation();
+    wrapperSynthesizor.ensureWrappersForL8(eventConsumer);
   }
 
   public SortedProgramMethodSet generateCallbackMethods() {
@@ -334,7 +508,7 @@
           List<DexEncodedMethod> newVirtualMethods = new ArrayList<>();
           callbacks.forEach(
               callback -> {
-                ProgramMethod callbackMethod = generateCallbackMethod(callback, clazz);
+                ProgramMethod callbackMethod = generateCallbackMethod(callback, clazz, null);
                 newVirtualMethods.add(callbackMethod.getDefinition());
                 allCallbackMethods.add(callbackMethod);
               });
@@ -358,12 +532,14 @@
   }
 
   private ProgramMethod generateCallbackMethod(
-      DexEncodedMethod originalMethod, DexProgramClass clazz) {
+      DexEncodedMethod originalMethod,
+      DexProgramClass clazz,
+      DesugaredLibraryAPIConverterPostProcessingEventConsumer eventConsumer) {
     DexMethod methodToInstall =
         methodWithVivifiedTypeInSignature(originalMethod.getReference(), clazz.type, appView);
     CfCode cfCode =
         new APIConverterWrapperCfCodeProvider(
-                appView, originalMethod.getReference(), null, this, clazz.isInterface())
+                appView, originalMethod.getReference(), null, this, clazz.isInterface(), null)
             .generateCfCode();
     DexEncodedMethod newMethod =
         wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
@@ -371,7 +547,13 @@
     if (originalMethod.isLibraryMethodOverride().isTrue()) {
       newMethod.setLibraryMethodOverride(OptionalBool.TRUE);
     }
-    return new ProgramMethod(clazz, newMethod);
+    ProgramMethod callback = new ProgramMethod(clazz, newMethod);
+    if (eventConsumer != null) {
+      eventConsumer.acceptAPIConversionCallback(callback);
+    } else {
+      assert appView.enableWholeProgramOptimizations();
+    }
+    return callback;
   }
 
   private void generateTrackDesugaredAPIWarnings(Set<DexMethod> tracked, String inner) {
@@ -414,8 +596,10 @@
     return vivifiedType;
   }
 
-  public void registerWrappersForLibraryInvokeIfRequired(DexMethod invokedMethod) {
-    if (!shouldRewriteInvoke(invokedMethod)) {
+  public void registerWrappersForLibraryInvokeIfRequired(
+      DexMethod invokedMethod, Type invokeType, ProgramMethod context) {
+    // TODO(b/191656218): Once R8 support is done, use an unboxed boolean here.
+    if (!shouldRewriteInvoke(invokedMethod, invokeType, null, context)) {
       return;
     }
     if (trackedAPIs != null) {
@@ -432,6 +616,120 @@
     }
   }
 
+  private DexMethod computeReturnConversion(
+      DexMethod invokedMethod, CfInstructionDesugaringEventConsumer eventConsumer) {
+    DexType returnType = invokedMethod.proto.returnType;
+    if (!appView.rewritePrefix.hasRewrittenType(returnType, appView)) {
+      return null;
+    }
+    if (canConvert(returnType)) {
+      DexType newReturnType = DesugaredLibraryAPIConverter.vivifiedTypeFor(returnType, appView);
+      return ensureConversionMethod(returnType, newReturnType, returnType, eventConsumer);
+    }
+    reportInvalidInvoke(returnType, invokedMethod, "return ");
+    return null;
+  }
+
+  private DexMethod[] computeParameterConversions(
+      DexMethod invokedMethod, CfInstructionDesugaringEventConsumer eventConsumer) {
+    DexMethod[] parameterConversions = new DexMethod[invokedMethod.getArity()];
+    DexType[] parameters = invokedMethod.proto.parameters.values;
+    for (int i = 0; i < parameters.length; i++) {
+      DexType argType = parameters[i];
+      if (appView.rewritePrefix.hasRewrittenType(argType, appView)) {
+        if (canConvert(argType)) {
+          DexType argVivifiedType = vivifiedTypeFor(argType, appView);
+          parameterConversions[i] =
+              ensureConversionMethod(argType, argType, argVivifiedType, eventConsumer);
+        } else {
+          reportInvalidInvoke(argType, invokedMethod, "parameter ");
+        }
+      }
+    }
+    return parameterConversions;
+  }
+
+  private CfInvoke rewriteLibraryInvoke(
+      CfInvoke invoke,
+      MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context) {
+    DexMethod invokedMethod = invoke.getMethod();
+    if (trackedAPIs != null) {
+      trackedAPIs.add(invokedMethod);
+    }
+    DexProto newProto =
+        invoke.isInvokeStatic()
+            ? invokedMethod.proto
+            : factory.prependTypeToProto(invokedMethod.getHolderType(), invokedMethod.getProto());
+    DexMethod apiConversionMethod =
+        invoke.isInvokeSuper(context.getHolderType())
+            ? createSuperAPIConversion(
+                invoke, methodProcessingContext, eventConsumer, newProto, context)
+            : createOutlinedAPIConversion(invoke, methodProcessingContext, eventConsumer, newProto);
+    return new CfInvoke(Opcodes.INVOKESTATIC, apiConversionMethod, false);
+  }
+
+  private DexMethod createSuperAPIConversion(
+      CfInvoke invoke,
+      MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      DexProto newProto,
+      ProgramMethod context) {
+    DexMethod invokedMethod = invoke.getMethod();
+    String uniqueSuffix = methodProcessingContext.createUniqueContext().getSyntheticSuffix();
+    DexMethod method =
+        factory.createMethod(
+            context.getHolderType(), newProto, SUPER_CONVERSION_METHOD_PREFIX + uniqueSuffix);
+    DexEncodedMethod apiConversion =
+        new DexEncodedMethod(
+            method,
+            superAPIConversionMethodAccessFlags,
+            MethodTypeSignature.noSignature(),
+            DexAnnotationSet.empty(),
+            ParameterAnnotationsList.empty(),
+            new APIConversionCfCodeProvider(
+                    appView,
+                    method.holder,
+                    invoke,
+                    computeReturnConversion(invokedMethod, eventConsumer),
+                    computeParameterConversions(invokedMethod, eventConsumer))
+                .generateCfCode(),
+            true);
+    eventConsumer.acceptSuperAPIConversion(new ProgramMethod(context.getHolder(), apiConversion));
+    return method;
+  }
+
+  private DexMethod createOutlinedAPIConversion(
+      CfInvoke invoke,
+      MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      DexProto newProto) {
+    DexMethod invokedMethod = invoke.getMethod();
+    ProgramMethod outline =
+        appView
+            .getSyntheticItems()
+            .createMethod(
+                SyntheticKind.API_CONVERSION,
+                methodProcessingContext.createUniqueContext(),
+                appView,
+                builder ->
+                    builder
+                        .setProto(newProto)
+                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                        .setCode(
+                            methodSig ->
+                                new APIConversionCfCodeProvider(
+                                        appView,
+                                        methodSig.holder,
+                                        invoke,
+                                        computeReturnConversion(invokedMethod, eventConsumer),
+                                        computeParameterConversions(invokedMethod, eventConsumer))
+                                    .generateCfCode()));
+    eventConsumer.acceptAPIConversion(outline);
+    return outline.getReference();
+  }
+
   private void rewriteLibraryInvoke(
       IRCode code,
       InvokeMethod invokeMethod,
@@ -563,7 +861,7 @@
 
   private Instruction createParameterConversion(
       IRCode code, DexType argType, DexType argVivifiedType, Value inValue) {
-    DexMethod conversionMethod = ensureConversionMethod(argType, argType, argVivifiedType);
+    DexMethod conversionMethod = ensureConversionMethod(argType, argType, argVivifiedType, null);
     // The value is null only if the input is null.
     Value convertedValue =
         createConversionValue(code, inValue.getType().nullability(), argVivifiedType, null);
@@ -572,7 +870,8 @@
 
   private Instruction createReturnConversionAndReplaceUses(
       IRCode code, InvokeMethod invokeMethod, DexType returnType, DexType returnVivifiedType) {
-    DexMethod conversionMethod = ensureConversionMethod(returnType, returnVivifiedType, returnType);
+    DexMethod conversionMethod =
+        ensureConversionMethod(returnType, returnVivifiedType, returnType, null);
     Value outValue = invokeMethod.outValue();
     Value convertedValue =
         createConversionValue(code, Nullability.maybeNull(), returnType, outValue.getLocalInfo());
@@ -589,7 +888,11 @@
     }
   }
 
-  public DexMethod ensureConversionMethod(DexType type, DexType srcType, DexType destType) {
+  public DexMethod ensureConversionMethod(
+      DexType type,
+      DexType srcType,
+      DexType destType,
+      DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
     // 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 =
@@ -597,8 +900,8 @@
     if (conversionHolder == null) {
       conversionHolder =
           type == srcType
-              ? wrapperSynthesizor.ensureTypeWrapper(type)
-              : wrapperSynthesizor.ensureVivifiedTypeWrapper(type);
+              ? wrapperSynthesizor.ensureTypeWrapper(type, eventConsumer)
+              : wrapperSynthesizor.ensureVivifiedTypeWrapper(type, eventConsumer);
     }
     assert conversionHolder != null;
     return factory.createMethod(
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
new file mode 100644
index 0000000..65f5ece
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverterEventConsumer.java
@@ -0,0 +1,25 @@
+// 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);
+
+  void acceptSuperAPIConversion(ProgramMethod method);
+
+  interface DesugaredLibraryAPIConverterPostProcessingEventConsumer {
+
+    void acceptAPIConversionCallback(ProgramMethod method);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfiguration.java
index cf81745..84e3558 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfiguration.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
@@ -37,23 +38,6 @@
 public class DesugaredLibraryConfiguration {
   public static final String FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX = "j$/";
   public static final boolean FALL_BACK_SUPPORT_ALL_CALLBACKS_FROM_LIBRARY = true;
-  public static final DesugaredLibraryConfiguration EMPTY_DESUGARED_LIBRARY_CONFIGURATION =
-      new DesugaredLibraryConfiguration(
-          AndroidApiLevel.B,
-          false,
-          FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX,
-          null,
-          null,
-          FALL_BACK_SUPPORT_ALL_CALLBACKS_FROM_LIBRARY,
-          ImmutableMap.of(),
-          ImmutableMap.of(),
-          ImmutableMap.of(),
-          ImmutableMap.of(),
-          ImmutableMap.of(),
-          ImmutableSet.of(),
-          ImmutableList.of(),
-          ImmutableList.of(),
-          PrefixRewritingMapper.empty());
   private final AndroidApiLevel requiredCompilationAPILevel;
   private final boolean libraryCompilation;
   private final String synthesizedLibraryClassesPackagePrefix;
@@ -102,7 +86,33 @@
   }
 
   public static DesugaredLibraryConfiguration empty() {
-    return EMPTY_DESUGARED_LIBRARY_CONFIGURATION;
+    return new DesugaredLibraryConfiguration(
+        AndroidApiLevel.B,
+        false,
+        FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX,
+        null,
+        null,
+        FALL_BACK_SUPPORT_ALL_CALLBACKS_FROM_LIBRARY,
+        ImmutableMap.of(),
+        ImmutableMap.of(),
+        ImmutableMap.of(),
+        ImmutableMap.of(),
+        ImmutableMap.of(),
+        ImmutableSet.of(),
+        ImmutableList.of(),
+        ImmutableList.of(),
+        PrefixRewritingMapper.empty()) {
+
+      @Override
+      public boolean isSupported(DexReference reference, AppView<?> appView) {
+        return false;
+      }
+
+      @Override
+      public boolean isEmptyConfiguration() {
+        return true;
+      }
+    };
   }
 
   private DesugaredLibraryConfiguration(
@@ -181,8 +191,8 @@
     return emulateLibraryInterface;
   }
 
-  public boolean isSupported(DexMethod method, AppView<?> appView) {
-    return prefixRewritingMapper.hasRewrittenType(method.getHolderType(), appView);
+  public boolean isSupported(DexReference reference, AppView<?> appView) {
+    return prefixRewritingMapper.hasRewrittenType(reference.getContextType(), appView);
   }
 
   // If the method is retargeted, answers the retargeted method, else null.
@@ -231,6 +241,10 @@
     return jsonSource;
   }
 
+  public boolean isEmptyConfiguration() {
+    return false;
+  }
+
   public static class Builder {
     private final DexItemFactory factory;
     private final Reporter reporter;
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 0b0039e..dac3079 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
@@ -23,6 +23,8 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 
 // The rewrite of virtual calls requires to go through emulate dispatch. This class is responsible
 // for inserting interfaces on library boundaries and forwarding methods in the program, and to
@@ -41,7 +43,9 @@
   }
 
   @Override
-  public void postProcessingDesugaring(CfPostProcessingDesugaringEventConsumer eventConsumer) {
+  public void postProcessingDesugaring(
+      CfPostProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService)
+      throws ExecutionException {
     if (appView.options().isDesugaredLibraryCompilation()) {
       ensureEmulatedDispatchMethodsSynthesized(eventConsumer);
     } else {
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 1f8322f..02eb59b 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
@@ -150,6 +150,7 @@
               .setCode(
                   methodSig ->
                       new EmulateInterfaceSyntheticCfCodeProvider(
+                              methodSig.getHolderType(),
                               emulatedDispatchMethod.getHolderType(),
                               desugarMethod,
                               itfMethod,
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 6c1e889..7e2164f 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
@@ -117,14 +117,16 @@
     return appView.options().desugaredLibraryConfiguration.getWrapperConversions().contains(type);
   }
 
-  DexType ensureTypeWrapper(DexType type) {
-    return ensureWrappers(type).getWrapper().type;
+  DexType ensureTypeWrapper(DexType type, DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
+    return ensureWrappers(type, eventConsumer).getWrapper().type;
   }
 
-  DexType ensureVivifiedTypeWrapper(DexType type) {
-    return ensureWrappers(type).getVivifiedWrapper().type;
+  DexType ensureVivifiedTypeWrapper(
+      DexType type, DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
+    return ensureWrappers(type, eventConsumer).getVivifiedWrapper().type;
   }
 
+
   public void registerWrapper(DexType type) {
     wrappersToGenerate.add(type);
     assert getValidClassToWrap(type) != null;
@@ -161,13 +163,17 @@
     }
   }
 
-  private Wrappers ensureWrappers(DexType type) {
+  private Wrappers ensureWrappers(
+      DexType type, DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
     assert canGenerateWrapper(type) : type;
     DexClass dexClass = getValidClassToWrap(type);
-    return ensureWrappers(dexClass, ignored -> {});
+    return ensureWrappers(dexClass, ignored -> {}, eventConsumer);
   }
 
-  private Wrappers ensureWrappers(DexClass context, Consumer<DexClasspathClass> creationCallback) {
+  private Wrappers ensureWrappers(
+      DexClass context,
+      Consumer<DexClasspathClass> creationCallback,
+      DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
     DexType type = context.type;
     DexClass wrapper;
     DexClass vivifiedWrapper;
@@ -180,15 +186,20 @@
               vivifiedTypeFor(type),
               type,
               programContext,
-              wrapperField -> synthesizeVirtualMethodsForTypeWrapper(programContext, wrapperField));
+              eventConsumer,
+              wrapperField ->
+                  synthesizeVirtualMethodsForTypeWrapper(
+                      programContext, wrapperField, eventConsumer));
       vivifiedWrapper =
           ensureProgramWrapper(
               SyntheticKind.VIVIFIED_WRAPPER,
               type,
               vivifiedTypeFor(type),
               programContext,
+              eventConsumer,
               wrapperField ->
-                  synthesizeVirtualMethodsForVivifiedTypeWrapper(programContext, wrapperField));
+                  synthesizeVirtualMethodsForVivifiedTypeWrapper(
+                      programContext, wrapperField, eventConsumer));
       DexField wrapperField = getWrapperUniqueField(wrapper);
       DexField vivifiedWrapperField = getWrapperUniqueField(vivifiedWrapper);
       ensureProgramConversionMethod(
@@ -205,7 +216,9 @@
               type,
               classpathOrLibraryContext,
               creationCallback,
-              wrapperField -> synthesizeVirtualMethodsForTypeWrapper(context, wrapperField));
+              eventConsumer,
+              wrapperField ->
+                  synthesizeVirtualMethodsForTypeWrapper(context, wrapperField, eventConsumer));
       vivifiedWrapper =
           ensureClasspathWrapper(
               SyntheticKind.VIVIFIED_WRAPPER,
@@ -213,8 +226,10 @@
               vivifiedTypeFor(type),
               classpathOrLibraryContext,
               creationCallback,
+              eventConsumer,
               wrapperField ->
-                  synthesizeVirtualMethodsForVivifiedTypeWrapper(context, wrapperField));
+                  synthesizeVirtualMethodsForVivifiedTypeWrapper(
+                      context, wrapperField, eventConsumer));
       DexField wrapperField = getWrapperUniqueField(wrapper);
       DexField vivifiedWrapperField = getWrapperUniqueField(vivifiedWrapper);
       ensureClasspathConversionMethod(
@@ -242,6 +257,7 @@
       DexType wrappingType,
       DexType wrappedType,
       DexProgramClass programContext,
+      DesugaredLibraryAPIConverterEventConsumer eventConsumer,
       Function<DexEncodedField, DexEncodedMethod[]> virtualMethodProvider) {
     return appView
         .getSyntheticItems()
@@ -252,9 +268,13 @@
             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 ->
-                wrapper.setVirtualMethods(
-                    virtualMethodProvider.apply(getWrapperUniqueEncodedField(wrapper))));
+            wrapper -> {
+              if (eventConsumer != null) {
+                eventConsumer.acceptWrapperProgramClass(wrapper);
+              }
+              wrapper.setVirtualMethods(
+                  virtualMethodProvider.apply(getWrapperUniqueEncodedField(wrapper)));
+            });
   }
 
   private DexClasspathClass ensureClasspathWrapper(
@@ -263,6 +283,7 @@
       DexType wrappedType,
       ClasspathOrLibraryClass classpathOrLibraryContext,
       Consumer<DexClasspathClass> creationCallback,
+      DesugaredLibraryAPIConverterEventConsumer eventConsumer,
       Function<DexEncodedField, DexEncodedMethod[]> virtualMethodProvider) {
     return appView
         .getSyntheticItems()
@@ -276,6 +297,9 @@
             // 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);
@@ -382,7 +406,9 @@
   }
 
   private DexEncodedMethod[] synthesizeVirtualMethodsForVivifiedTypeWrapper(
-      DexClass dexClass, DexEncodedField wrapperField) {
+      DexClass dexClass,
+      DexEncodedField wrapperField,
+      DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
     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
@@ -421,7 +447,12 @@
       } else {
         cfCode =
             new APIConverterVivifiedWrapperCfCodeProvider(
-                    appView, methodToInstall, wrapperField.getReference(), converter, isInterface)
+                    appView,
+                    methodToInstall,
+                    wrapperField.getReference(),
+                    converter,
+                    isInterface,
+                    eventConsumer)
                 .generateCfCode();
       }
       DexEncodedMethod newDexEncodedMethod =
@@ -432,7 +463,9 @@
   }
 
   private DexEncodedMethod[] synthesizeVirtualMethodsForTypeWrapper(
-      DexClass dexClass, DexEncodedField wrapperField) {
+      DexClass dexClass,
+      DexEncodedField wrapperField,
+      DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
     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
@@ -465,7 +498,8 @@
                     dexEncodedMethod.getReference(),
                     wrapperField.getReference(),
                     converter,
-                    isInterface)
+                    isInterface,
+                    eventConsumer)
                 .generateCfCode();
       }
       DexEncodedMethod newDexEncodedMethod =
@@ -574,7 +608,7 @@
         field, fieldAccessFlags, FieldTypeSignature.noSignature(), DexAnnotationSet.empty(), null);
   }
 
-  void finalizeWrappersForL8() {
+  void ensureWrappersForL8(DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
     DesugaredLibraryConfiguration conf = appView.options().desugaredLibraryConfiguration;
     for (DexType type : conf.getWrapperConversions()) {
       assert !conf.getCustomConversions().containsKey(type);
@@ -582,7 +616,7 @@
       // 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 -> {});
+        ensureWrappers(validClassToWrap, ignored -> {}, eventConsumer);
       }
     }
   }
@@ -599,7 +633,8 @@
             classpathWrapper -> {
               changed.set(true);
               synthesizedCallback.accept(classpathWrapper);
-            });
+            },
+            null);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
index d86d22c..14e5fea 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
@@ -380,7 +380,8 @@
   }
 
   @Override
-  public void process(DexProgramClass clazz, ProgramMethodSet synthesizedMethods) {
+  public void process(
+      DexProgramClass clazz, InterfaceProcessingDesugaringEventConsumer eventConsumer) {
     if (!clazz.isInterface()) {
       visitClassInfo(clazz, new ReportingContext(clazz, clazz));
     }
@@ -389,11 +390,11 @@
   // We introduce forwarding methods only once all desugaring has been performed to avoid
   // confusing the look-up with inserted forwarding methods.
   @Override
-  public final void finalizeProcessing(ProgramMethodSet synthesizedMethods) {
+  public final void finalizeProcessing(InterfaceProcessingDesugaringEventConsumer eventConsumer) {
     newSyntheticMethods.forEach(
         (clazz, newForwardingMethods) -> {
           clazz.addVirtualMethods(newForwardingMethods.toDefinitionSet());
-          newForwardingMethods.forEach(synthesizedMethods::add);
+          newForwardingMethods.forEach(eventConsumer::acceptForwardingMethod);
         });
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
new file mode 100644
index 0000000..b2308ae
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
@@ -0,0 +1,158 @@
+// 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.AppView;
+import com.android.tools.r8.graph.DexApplication;
+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.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.GenericSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.utils.IterableUtils;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+public final class EmulatedInterfaceApplicationRewriter {
+
+  private final AppView<?> appView;
+  private final Map<DexType, DexType> emulatedInterfaces;
+
+  public EmulatedInterfaceApplicationRewriter(AppView<?> appView) {
+    this.appView = appView;
+    this.emulatedInterfaces =
+        appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
+  }
+
+  public void rewriteApplication(DexApplication.Builder<?> builder) {
+    assert appView.options().isDesugaredLibraryCompilation();
+    ArrayList<DexProgramClass> newProgramClasses = new ArrayList<>();
+    for (DexProgramClass clazz : builder.getProgramClasses()) {
+      if (emulatedInterfaces.containsKey(clazz.type)) {
+        newProgramClasses.add(rewriteEmulatedInterface(clazz));
+      } else if (clazz.isInterface()
+          && !appView.rewritePrefix.hasRewrittenType(clazz.type, appView)
+          && isEmulatedInterfaceSubInterface(clazz)) {
+        // Intentionally filter such classes out.
+        handleEmulatedInterfaceSubInterface(clazz);
+      } else {
+        newProgramClasses.add(clazz);
+      }
+    }
+    builder.replaceProgramClasses(newProgramClasses);
+  }
+
+  private void handleEmulatedInterfaceSubInterface(DexProgramClass clazz) {
+    // TODO(b/183918843): Investigate how to specify these in the json file.
+    // These are interfaces which needs a companion class for desugared library to work, but
+    // the interface is not supported outside of desugared library. The interface has to be
+    // present during the compilation for the companion class to be generated, but filtered out
+    // afterwards. The companion class needs to be rewritten to have the desugared library
+    // prefix since all classes in desugared library should have the prefix, we used the
+    // questionable method convertJavaNameToDesugaredLibrary to generate a correct type.
+    String newName =
+        appView
+            .options()
+            .desugaredLibraryConfiguration
+            .convertJavaNameToDesugaredLibrary(clazz.type);
+    InterfaceMethodRewriter.addCompanionClassRewriteRule(clazz.type, newName, appView);
+  }
+
+  private boolean isEmulatedInterfaceSubInterface(DexClass subInterface) {
+    assert !emulatedInterfaces.containsKey(subInterface.type);
+    LinkedList<DexType> workList = new LinkedList<>(Arrays.asList(subInterface.interfaces.values));
+    while (!workList.isEmpty()) {
+      DexType next = workList.removeFirst();
+      if (emulatedInterfaces.containsKey(next)) {
+        return true;
+      }
+      DexClass nextClass = appView.definitionFor(next);
+      if (nextClass != null) {
+        workList.addAll(Arrays.asList(nextClass.interfaces.values));
+      }
+    }
+    return false;
+  }
+
+  // The method transforms emulated interface such as they now have the rewritten type and
+  // implement the rewritten version of each emulated interface they implement.
+  private DexProgramClass rewriteEmulatedInterface(DexProgramClass emulatedInterface) {
+    if (appView.isAlreadyLibraryDesugared(emulatedInterface)) {
+      return emulatedInterface;
+    }
+    DexType newType = emulatedInterfaces.get(emulatedInterface.type);
+    assert newType != null;
+    DexEncodedMethod[] newVirtualMethods =
+        renameHolder(emulatedInterface.virtualMethods(), newType);
+    DexEncodedMethod[] newDirectMethods = renameHolder(emulatedInterface.directMethods(), newType);
+    assert emulatedInterface.getSuperType() == appView.dexItemFactory().objectType;
+    assert !emulatedInterface.hasFields();
+    assert emulatedInterface.getNestHost() == null;
+    assert !emulatedInterface.hasNestMemberAttributes();
+    assert !emulatedInterface.hasFields();
+    DexProgramClass newEmulatedInterface =
+        new DexProgramClass(
+            newType,
+            emulatedInterface.getOriginKind(),
+            emulatedInterface.getOrigin(),
+            emulatedInterface.getAccessFlags(),
+            appView.dexItemFactory().objectType,
+            DexTypeList.empty(),
+            emulatedInterface.getSourceFile(),
+            null,
+            Collections.emptyList(),
+            null, // Note that we clear the enclosing and inner class attributes.
+            Collections.emptyList(),
+            emulatedInterface.getClassSignature(),
+            emulatedInterface.annotations(),
+            DexEncodedField.EMPTY_ARRAY,
+            DexEncodedField.EMPTY_ARRAY,
+            newDirectMethods,
+            newVirtualMethods,
+            false,
+            emulatedInterface.getChecksumSupplier());
+    newEmulatedInterface.addExtraInterfaces(
+        getRewrittenInterfacesOfEmulatedInterface(emulatedInterface));
+    return newEmulatedInterface;
+  }
+
+  private List<GenericSignature.ClassTypeSignature> getRewrittenInterfacesOfEmulatedInterface(
+      DexProgramClass emulatedInterface) {
+    List<GenericSignature.ClassTypeSignature> newInterfaces = new ArrayList<>();
+    ClassSignature classSignature = emulatedInterface.getClassSignature();
+    for (int i = 0; i < emulatedInterface.interfaces.size(); i++) {
+      DexType itf = emulatedInterface.interfaces.values[i];
+      if (emulatedInterfaces.containsKey(itf)) {
+        List<GenericSignature.FieldTypeSignature> typeArguments;
+        if (classSignature == null) {
+          typeArguments = Collections.emptyList();
+        } else {
+          GenericSignature.ClassTypeSignature classTypeSignature =
+              classSignature.superInterfaceSignatures().get(i);
+          assert itf == classTypeSignature.type();
+          typeArguments = classTypeSignature.typeArguments();
+        }
+        newInterfaces.add(
+            new GenericSignature.ClassTypeSignature(emulatedInterfaces.get(itf), typeArguments));
+      }
+    }
+    return newInterfaces;
+  }
+
+  private DexEncodedMethod[] renameHolder(Iterable<DexEncodedMethod> methods, DexType newName) {
+    List<DexEncodedMethod> methodArray = IterableUtils.toNewArrayList(methods);
+    DexEncodedMethod[] newMethods = new DexEncodedMethod[methodArray.size()];
+    for (int i = 0; i < newMethods.length; i++) {
+      newMethods[i] = methodArray.get(i).toRenamedHolderMethod(newName, appView.dexItemFactory());
+    }
+    return newMethods;
+  }
+}
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/EmulatedInterfaceProcessor.java
index b9e9889..f0d298f 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/EmulatedInterfaceProcessor.java
@@ -6,24 +6,19 @@
 import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.emulateInterfaceLibraryMethod;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 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.GenericSignature;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.synthetic.EmulateInterfaceSyntheticCfCodeProvider;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
 import com.android.tools.r8.synthesis.SyntheticNaming;
-import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.StringDiagnostic;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -37,8 +32,6 @@
 
 public final class EmulatedInterfaceProcessor implements InterfaceDesugaringProcessor {
 
-  private static final String JUNK_SUFFIX = "$JUNK";
-
   private final AppView<?> appView;
   private final InterfaceMethodRewriter rewriter;
   private final Map<DexType, DexType> emulatedInterfaces;
@@ -94,51 +87,8 @@
     }
   }
 
-  // The method transforms emulated interface such as they implement the rewritten version
-  // of each emulated interface they implement. Such change should have no effect on the look-up
-  // results, since each class implementing an emulated interface should also implement the
-  // rewritten one.
-  private void replaceInterfacesInEmulatedInterface(DexProgramClass emulatedInterface) {
-    List<GenericSignature.ClassTypeSignature> newInterfaces = new ArrayList<>();
-    ClassSignature classSignature = emulatedInterface.getClassSignature();
-    for (int i = 0; i < emulatedInterface.interfaces.size(); i++) {
-      DexType itf = emulatedInterface.interfaces.values[i];
-      if (emulatedInterfaces.containsKey(itf)) {
-        List<GenericSignature.FieldTypeSignature> typeArguments;
-        if (classSignature == null) {
-          typeArguments = Collections.emptyList();
-        } else {
-          GenericSignature.ClassTypeSignature classTypeSignature =
-              classSignature.superInterfaceSignatures().get(i);
-          assert itf == classTypeSignature.type();
-          typeArguments = classTypeSignature.typeArguments();
-        }
-        newInterfaces.add(
-            new GenericSignature.ClassTypeSignature(emulatedInterfaces.get(itf), typeArguments));
-      }
-    }
-    emulatedInterface.replaceInterfaces(newInterfaces);
-  }
-
-  private void renameEmulatedInterface(DexProgramClass emulatedInterface) {
-    DexType newType = emulatedInterfaces.get(emulatedInterface.type);
-    assert newType != null;
-    emulatedInterface.type = newType;
-    emulatedInterface.setVirtualMethods(renameHolder(emulatedInterface.virtualMethods(), newType));
-    emulatedInterface.setDirectMethods(renameHolder(emulatedInterface.directMethods(), newType));
-  }
-
-  private DexEncodedMethod[] renameHolder(Iterable<DexEncodedMethod> methods, DexType newName) {
-    List<DexEncodedMethod> methods1 = IterableUtils.toNewArrayList(methods);
-    DexEncodedMethod[] newMethods = new DexEncodedMethod[methods1.size()];
-    for (int i = 0; i < newMethods.length; i++) {
-      newMethods[i] = methods1.get(i).toRenamedHolderMethod(newName, appView.dexItemFactory());
-    }
-    return newMethods;
-  }
-
   DexProgramClass ensureEmulateInterfaceLibrary(
-      DexProgramClass emulatedInterface, ProgramMethodSet synthesizedMethods) {
+      DexProgramClass emulatedInterface, InterfaceProcessingDesugaringEventConsumer eventConsumer) {
     assert rewriter.isEmulatedInterface(emulatedInterface.type);
     DexProgramClass emulateInterfaceClass =
         appView
@@ -156,7 +106,7 @@
                                     synthesizeEmulatedInterfaceMethod(
                                         method, emulatedInterface, methodBuilder))),
                 ignored -> {});
-    emulateInterfaceClass.forEachProgramMethod(synthesizedMethods::add);
+    emulateInterfaceClass.forEachProgramMethod(eventConsumer::acceptEmulatedInterfaceMethod);
     assert emulateInterfaceClass.getType()
         == InterfaceMethodRewriter.getEmulateLibraryInterfaceClassType(
             emulatedInterface.type, appView.dexItemFactory());
@@ -180,9 +130,10 @@
         .setProto(emulatedMethod.getProto())
         .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
         .setCode(
-            theMethod ->
+            emulatedInterfaceMethod ->
                 new EmulateInterfaceSyntheticCfCodeProvider(
-                        theMethod.getHolderType(),
+                        emulatedInterfaceMethod.getHolderType(),
+                        method.getHolderType(),
                         companionMethod,
                         libraryMethod,
                         extraDispatchCases,
@@ -266,14 +217,15 @@
   }
 
   @Override
-  public void process(DexProgramClass emulatedInterface, ProgramMethodSet synthesizedMethods) {
+  public void process(
+      DexProgramClass emulatedInterface, InterfaceProcessingDesugaringEventConsumer eventConsumer) {
     if (!appView.options().isDesugaredLibraryCompilation()
         || !rewriter.isEmulatedInterface(emulatedInterface.type)
         || appView.isAlreadyLibraryDesugared(emulatedInterface)) {
       return;
     }
     if (needsEmulateInterfaceLibrary(emulatedInterface)) {
-      ensureEmulateInterfaceLibrary(emulatedInterface, synthesizedMethods);
+      ensureEmulateInterfaceLibrary(emulatedInterface, eventConsumer);
     }
   }
 
@@ -282,74 +234,8 @@
   }
 
   @Override
-  public void finalizeProcessing(ProgramMethodSet synthesizedMethods) {
+  public void finalizeProcessing(InterfaceProcessingDesugaringEventConsumer eventConsumer) {
     warnMissingEmulatedInterfaces();
-    if (!appView.options().isDesugaredLibraryCompilation()) {
-      return;
-    }
-    for (DexType interfaceType : emulatedInterfaces.keySet()) {
-      DexClass theInterface = appView.definitionFor(interfaceType);
-      if (theInterface != null && theInterface.isProgramClass()) {
-        DexProgramClass emulatedInterface = theInterface.asProgramClass();
-        if (!appView.isAlreadyLibraryDesugared(emulatedInterface)) {
-          replaceInterfacesInEmulatedInterface(emulatedInterface);
-          renameEmulatedInterface(emulatedInterface);
-        }
-      }
-    }
-  }
-
-  // TODO(b/183918843): Investigate what to do. The whole method is trying to fill a hole in the
-  //  desugaring library specifications by patching types and classes through questionable renaming.
-  public static void filterEmulatedInterfaceSubInterfaces(
-      AppView<?> appView, DexApplication.Builder<?> builder) {
-    assert appView.options().isDesugaredLibraryCompilation();
-    ArrayList<DexProgramClass> filteredProgramClasses = new ArrayList<>();
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      if (clazz.isInterface()
-          && !appView
-              .options()
-              .desugaredLibraryConfiguration
-              .getEmulateLibraryInterface()
-              .containsKey(clazz.type)
-          && !appView.rewritePrefix.hasRewrittenType(clazz.type, appView)
-          && isEmulatedInterfaceSubInterface(clazz, appView)) {
-        String newName =
-            appView
-                .options()
-                .desugaredLibraryConfiguration
-                .convertJavaNameToDesugaredLibrary(clazz.type);
-        InterfaceMethodRewriter.addCompanionClassRewriteRule(clazz.type, newName, appView);
-      } else {
-        filteredProgramClasses.add(clazz);
-      }
-    }
-    builder.replaceProgramClasses(filteredProgramClasses);
-  }
-
-  private static boolean isEmulatedInterfaceSubInterface(
-      DexClass subInterface, AppView<?> appView) {
-    assert !appView
-        .options()
-        .desugaredLibraryConfiguration
-        .getEmulateLibraryInterface()
-        .containsKey(subInterface.type);
-    LinkedList<DexType> workList = new LinkedList<>(Arrays.asList(subInterface.interfaces.values));
-    while (!workList.isEmpty()) {
-      DexType next = workList.removeFirst();
-      if (appView
-          .options()
-          .desugaredLibraryConfiguration
-          .getEmulateLibraryInterface()
-          .containsKey(next)) {
-        return true;
-      }
-      DexClass nextClass = appView.definitionFor(next);
-      if (nextClass != null) {
-        workList.addAll(Arrays.asList(nextClass.interfaces.values));
-      }
-    }
-    return false;
   }
 
   private void warnMissingEmulatedInterfaces() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringProcessor.java
index 7f3d047..4f4ba32 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringProcessor.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.ir.desugar.itf;
 
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
 
 public interface InterfaceDesugaringProcessor {
 
@@ -14,11 +13,11 @@
   // so this phase cannot modify the classes themselves (for example insertion/removal of methods).
   // The phase can insert new classes with new methods, such as emulated interface dispatch classes
   // or companion classes with their methods.
-  void process(DexProgramClass clazz, ProgramMethodSet synthesizedMethods);
+  void process(DexProgramClass clazz, InterfaceProcessingDesugaringEventConsumer eventConsumer);
 
   // The finalization phase is done at a join point, after all code desugaring have been performed.
   // All finalization phases of all desugaring processors are performed sequentially.
   // Complex computations should be avoided if possible here and be moved to the concurrent phase.
   // Classes may be mutated here (new methods can be inserted, etc.).
-  void finalizeProcessing(ProgramMethodSet synthesizedMethods);
+  void finalizeProcessing(InterfaceProcessingDesugaringEventConsumer eventConsumer);
 }
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 731316f..71844b0 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
@@ -6,7 +6,10 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
@@ -16,21 +19,22 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
-class InterfaceMethodProcessorFacade {
+public class InterfaceMethodProcessorFacade implements CfPostProcessingDesugaring {
 
   private final AppView<?> appView;
+  private final Flavor flavour;
+  private final List<InterfaceDesugaringProcessor> interfaceDesugaringProcessors;
 
-  InterfaceMethodProcessorFacade(AppView<?> appView) {
+  InterfaceMethodProcessorFacade(
+      AppView<?> appView, Flavor flavour, InterfaceMethodRewriter rewriter) {
     this.appView = appView;
+    this.flavour = flavour;
+    interfaceDesugaringProcessors = instantiateInterfaceDesugaringProcessors(appView, rewriter);
   }
 
-  /** Runs the interfaceProcessor, the class processor and the emulated interface processor. */
-  void runInterfaceDesugaringProcessors(
-      InterfaceMethodRewriter rewriter,
-      IRConverter converter,
-      Flavor flavour,
-      ExecutorService executorService)
-      throws ExecutionException {
+  private List<InterfaceDesugaringProcessor> instantiateInterfaceDesugaringProcessors(
+      AppView<?> appView, InterfaceMethodRewriter rewriter) {
+
     // 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 =
@@ -46,17 +50,47 @@
     // classes if needed.
     InterfaceProcessor interfaceProcessor = new InterfaceProcessor(appView, rewriter);
 
-    // The interface processors must be ordered so that finalization of the processing is performed
-    // in that order. The emulatedInterfaceProcessor has to be last at this point to avoid renaming
-    // emulated interfaces before the other processing.
-    ImmutableList<InterfaceDesugaringProcessor> orderedInterfaceDesugaringProcessors =
-        ImmutableList.of(classProcessor, interfaceProcessor, emulatedInterfaceProcessor);
+    // The processors can be listed in any order.
+    return ImmutableList.of(classProcessor, interfaceProcessor, emulatedInterfaceProcessor);
+  }
+
+  /** Runs the interfaceProcessor, the class processor and the emulated interface processor. */
+  void runInterfaceDesugaringProcessorsForR8(IRConverter converter, ExecutorService executorService)
+      throws ExecutionException {
+
+    CollectingInterfaceDesugaringEventConsumer eventConsumer =
+        new CollectingInterfaceDesugaringEventConsumer();
+    processClassesConcurrently(eventConsumer, executorService);
+    converter.processMethodsConcurrently(
+        eventConsumer.getSortedSynthesizedMethods(), executorService);
+  }
+
+  // This temporary class avoids the duality between collecting with IR processing and
+  // having events with the Cf desugaring.
+  private static class CollectingInterfaceDesugaringEventConsumer
+      implements InterfaceProcessingDesugaringEventConsumer {
 
     SortedProgramMethodSet sortedSynthesizedMethods = SortedProgramMethodSet.createConcurrent();
-    processClassesConcurrently(
-        orderedInterfaceDesugaringProcessors, sortedSynthesizedMethods, flavour, executorService);
-    assert converter != null;
-    converter.processMethodsConcurrently(sortedSynthesizedMethods, executorService);
+
+    @Override
+    public void acceptForwardingMethod(ProgramMethod method) {
+      sortedSynthesizedMethods.add(method);
+    }
+
+    @Override
+    public void acceptCompanionClassClinit(ProgramMethod method) {
+      sortedSynthesizedMethods.add(method);
+    }
+
+    @Override
+    public void acceptEmulatedInterfaceMethod(ProgramMethod method) {
+
+      sortedSynthesizedMethods.add(method);
+    }
+
+    public SortedProgramMethodSet getSortedSynthesizedMethods() {
+      return sortedSynthesizedMethods;
+    }
   }
 
   private boolean shouldProcess(DexProgramClass clazz, Flavor flavour) {
@@ -67,22 +101,28 @@
   }
 
   private void processClassesConcurrently(
-      List<InterfaceDesugaringProcessor> processors,
-      SortedProgramMethodSet sortedSynthesizedMethods,
-      Flavor flavour,
-      ExecutorService executorService)
+      InterfaceProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService)
       throws ExecutionException {
     ThreadUtils.processItems(
         Iterables.filter(
             appView.appInfo().classes(), (DexProgramClass clazz) -> shouldProcess(clazz, flavour)),
         clazz -> {
-          for (InterfaceDesugaringProcessor processor : processors) {
-            processor.process(clazz, sortedSynthesizedMethods);
+          for (InterfaceDesugaringProcessor processor : interfaceDesugaringProcessors) {
+            processor.process(clazz, eventConsumer);
           }
         },
         executorService);
-    for (InterfaceDesugaringProcessor processor : processors) {
-      processor.finalizeProcessing(sortedSynthesizedMethods);
+    for (InterfaceDesugaringProcessor processor : interfaceDesugaringProcessors) {
+      processor.finalizeProcessing(eventConsumer);
     }
   }
+
+  @Override
+  public void postProcessingDesugaring(
+      CfPostProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService)
+      throws ExecutionException {
+    // TODO(b/183998768): Would be nice to use the ClassProcessing for the processing of classes,
+    //  and do here only the finalization.
+    processClassesConcurrently(eventConsumer, executorService);
+  }
 }
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 413f803..e6471fe 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
@@ -467,6 +467,7 @@
             eventConsumer.acceptInvokeStaticInterfaceOutliningMethod(
                 staticOutliningMethod, context);
           };
+      // TODO(b/192439456): Make a test to prove resolution is needed here and fix it.
       return rewriteInvokeStatic(
           invoke.getMethod(),
           invoke.isInterface(),
@@ -671,6 +672,7 @@
                   methodProcessor,
                   methodProcessingContext);
       if (instruction.isInvokeStatic()) {
+        // TODO(b/192439456): Make a test to prove resolution is needed here and fix it.
         rewriteInvokeStatic(
             invoke.getInvokedMethod(),
             invoke.getInterfaceBit(),
@@ -1377,11 +1379,15 @@
     this.synthesizedMethods.clear();
   }
 
-  public void runInterfaceDesugaringProcessors(
+  public void runInterfaceDesugaringProcessorsForR8(
       IRConverter converter, Flavor flavour, ExecutorService executorService)
       throws ExecutionException {
-    new InterfaceMethodProcessorFacade(appView)
-        .runInterfaceDesugaringProcessors(this, converter, flavour, executorService);
+    getPostProcessingDesugaring(flavour)
+        .runInterfaceDesugaringProcessorsForR8(converter, executorService);
+  }
+
+  public InterfaceMethodProcessorFacade getPostProcessingDesugaring(Flavor flavour) {
+    return new InterfaceMethodProcessorFacade(appView, flavour, this);
   }
 
   final boolean isDefaultMethod(DexEncodedMethod method) {
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
new file mode 100644
index 0000000..2581364
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessingDesugaringEventConsumer.java
@@ -0,0 +1,16 @@
+// 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.ProgramMethod;
+
+public interface InterfaceProcessingDesugaringEventConsumer {
+
+  void acceptForwardingMethod(ProgramMethod method);
+
+  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 cb752ad..2dd65a9 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
@@ -47,7 +47,6 @@
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
 import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -80,12 +79,13 @@
   }
 
   @Override
-  public void process(DexProgramClass iface, ProgramMethodSet synthesizedMethods) {
+  public void process(
+      DexProgramClass iface, InterfaceProcessingDesugaringEventConsumer eventConsumer) {
     if (!iface.isInterface()) {
       return;
     }
     analyzeBridges(iface);
-    ensureCompanionClassMethods(iface, synthesizedMethods);
+    ensureCompanionClassMethods(iface, eventConsumer);
   }
 
   private void analyzeBridges(DexProgramClass iface) {
@@ -99,8 +99,8 @@
   }
 
   private void ensureCompanionClassMethods(
-      DexProgramClass iface, ProgramMethodSet synthesizedMethods) {
-    ensureCompanionClassInitializesInterface(iface, synthesizedMethods);
+      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.
@@ -134,7 +134,7 @@
   }
 
   private void ensureCompanionClassInitializesInterface(
-      DexProgramClass iface, ProgramMethodSet synthesizedMethods) {
+      DexProgramClass iface, InterfaceProcessingDesugaringEventConsumer eventConsumer) {
     if (!hasStaticMethodThatTriggersNonTrivialClassInitializer(iface)) {
       return;
     }
@@ -146,7 +146,7 @@
             appView.dexItemFactory().createProto(appView.dexItemFactory().voidType),
             appView,
             methodBuilder -> createCompanionClassInitializer(iface, clinitField, methodBuilder));
-    synthesizedMethods.add(clinit);
+    eventConsumer.acceptCompanionClassClinit(clinit);
   }
 
   private DexEncodedField ensureStaticClinitFieldToTriggerInterfaceInitialization(
@@ -441,7 +441,7 @@
   }
 
   @Override
-  public void finalizeProcessing(ProgramMethodSet synthesizedMethods) {
+  public void finalizeProcessing(InterfaceProcessingDesugaringEventConsumer eventConsumer) {
     InterfaceProcessorNestedGraphLens graphLens = postProcessInterfaces();
     if (appView.enableWholeProgramOptimizations() && graphLens != null) {
       appView.setGraphLens(graphLens);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index 0ecfc91..c6bb8a7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
+import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -119,9 +120,20 @@
     InstructionListIterator iterator = block.listIterator(code, block.getInstructions().size());
     while (iterator.hasPrevious()) {
       Instruction current = iterator.previous();
-      // Remove unused invoke results.
-      if (current.isInvoke() && current.hasOutValue() && !current.outValue().isUsed()) {
-        current.setOutValue(null);
+      if (current.hasOutValue()) {
+        // Replace unnecessary cast values.
+        if (current.isCheckCast()) {
+          CheckCast checkCast = current.asCheckCast();
+          if (!checkCast.isRefiningStaticType(appView.options())
+              && checkCast.outValue().getLocalInfo() == checkCast.object().getLocalInfo()) {
+            checkCast.outValue().replaceUsers(checkCast.object());
+            checkCast.object().uniquePhiUsers().forEach(Phi::removeTrivialPhi);
+          }
+        }
+        // Remove unused invoke results.
+        if (current.isInvoke() && !current.outValue().isUsed()) {
+          current.setOutValue(null);
+        }
       }
       DeadInstructionResult deadInstructionResult = current.canBeDeadCode(appView, code);
       if (deadInstructionResult.isNotDead()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index f7a59dd..191d6af 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -68,6 +68,7 @@
 import com.android.tools.r8.utils.collections.LongLivedProgramMethodMultisetBuilder;
 import com.android.tools.r8.utils.collections.ProgramMethodMultiset;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -722,7 +723,7 @@
     int argumentRegisters;
     DexType returnType;
     Value returnValue;
-    int returnValueUsersLeft;
+    int returnValueUniqueUsersLeft;
     int pendingNewInstanceIndex = -1;
 
     OutlineSpotter(ProgramMethod method, BasicBlock block) {
@@ -816,18 +817,16 @@
     // Check if the current instruction can be included in the outline.
     private boolean canIncludeInstruction(Instruction instruction) {
       // Find the users of the active out-value (potential return value).
-      int returnValueUsersLeftIfIncluded = returnValueUsersLeft;
-      if (returnValue != null) {
-        for (Value value : instruction.inValues()) {
-          if (value.getAliasedValue() == returnValue) {
-            returnValueUsersLeftIfIncluded--;
-          }
-        }
+      int returnValueUniqueUsersLeftIfIncluded = returnValueUniqueUsersLeft;
+      if (returnValue != null
+          && Iterables.any(
+              instruction.inValues(), value -> value.getAliasedValue() == returnValue)) {
+        returnValueUniqueUsersLeftIfIncluded--;
       }
 
       // If this instruction has an out-value, but the previous one is still active end the
       // outline.
-      if (instruction.outValue() != null && returnValueUsersLeftIfIncluded > 0) {
+      if (instruction.outValue() != null && returnValueUniqueUsersLeftIfIncluded > 0) {
         return false;
       }
 
@@ -1061,9 +1060,9 @@
     }
 
     private void updateReturnValueState(Value newReturnValue, DexType newReturnType) {
-      returnValueUsersLeft = newReturnValue.numberOfAllUsers();
+      returnValueUniqueUsersLeft = newReturnValue.numberOfAllUsers();
       // If the return value is not used don't track it.
-      if (returnValueUsersLeft == 0) {
+      if (returnValueUniqueUsersLeft == 0) {
         returnValue = null;
         returnType = appView.dexItemFactory().voidType;
       } else {
@@ -1073,9 +1072,9 @@
     }
 
     private void adjustReturnValueUsersLeft(int change) {
-      returnValueUsersLeft += change;
-      assert returnValueUsersLeft >= 0;
-      if (returnValueUsersLeft == 0) {
+      returnValueUniqueUsersLeft += change;
+      assert returnValueUniqueUsersLeft >= 0;
+      if (returnValueUniqueUsersLeft == 0) {
         returnValue = null;
         returnType = appView.dexItemFactory().voidType;
       }
@@ -1127,7 +1126,7 @@
       argumentRegisters = 0;
       returnType = appView.dexItemFactory().voidType;
       returnValue = null;
-      returnValueUsersLeft = 0;
+      returnValueUniqueUsersLeft = 0;
       pendingNewInstanceIndex = -1;
     }
   }
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 07ce458..eb6014b 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
@@ -20,6 +20,7 @@
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
 import static com.android.tools.r8.ir.code.Opcodes.RETURN;
 import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 
 import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.AppView;
@@ -34,7 +35,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -79,6 +79,8 @@
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldMappingData;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldOrdinalData;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldUnknownData;
+import com.android.tools.r8.ir.optimize.enums.classification.CheckNotNullEnumUnboxerMethodClassification;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
 import com.android.tools.r8.ir.optimize.enums.eligibility.Reason;
 import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.IllegalInvokeWithImpreciseParameterTypeReason;
 import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.MissingContentsForEnumValuesArrayReason;
@@ -92,11 +94,11 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
+import com.android.tools.r8.utils.collections.ProgramMethodMap;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.HashMultiset;
 import com.google.common.collect.ImmutableMap;
@@ -136,6 +138,10 @@
   private final ProgramMethodSet methodsDependingOnLibraryModelisation =
       ProgramMethodSet.createConcurrent();
 
+  // Map from checkNotNull() methods to the enums that use the given method.
+  private final ProgramMethodMap<Set<DexProgramClass>> checkNotNullMethods =
+      ProgramMethodMap.createConcurrent();
+
   private final DexEncodedField ordinalField;
 
   private EnumUnboxingRewriter enumUnboxerRewriter;
@@ -518,31 +524,28 @@
     enumUnboxingCandidatesInfo.clear();
     // Update keep info on any of the enum methods of the removed classes.
     updateKeepInfo(enumsToUnbox);
-    DirectMappedDexApplication.Builder appBuilder = appView.appInfo().app().asDirect().builder();
-    FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder =
-        FieldAccessInfoCollectionModifier.builder();
 
     EnumUnboxingUtilityClasses utilityClasses =
         EnumUnboxingUtilityClasses.builder(appView)
-            .synthesizeEnumUnboxingUtilityClasses(
-                enumClassesToUnbox,
-                enumDataMap,
-                appBuilder,
-                fieldAccessInfoCollectionModifierBuilder)
-            .build();
-    utilityClasses.forEach(
-        utilityClass -> utilityClass.getDefinition().forEachProgramMethod(postBuilder::add));
+            .synthesizeEnumUnboxingUtilityClasses(enumClassesToUnbox, enumDataMap)
+            .build(converter, executorService);
 
-    fieldAccessInfoCollectionModifierBuilder.build().modify(appView);
     EnumUnboxingTreeFixer.Result treeFixerResult =
-        new EnumUnboxingTreeFixer(appView, enumDataMap, enumClassesToUnbox, utilityClasses)
+        new EnumUnboxingTreeFixer(
+                appView, checkNotNullMethods, enumDataMap, enumClassesToUnbox, utilityClasses)
             .fixupTypeReferences(converter, executorService);
     EnumUnboxingLens enumUnboxingLens = treeFixerResult.getLens();
     enumUnboxerRewriter =
-        new EnumUnboxingRewriter(appView, converter, enumUnboxingLens, enumDataMap, utilityClasses);
+        new EnumUnboxingRewriter(
+            appView,
+            treeFixerResult.getCheckNotNullToCheckNotZeroMapping(),
+            converter,
+            enumUnboxingLens,
+            enumDataMap,
+            utilityClasses);
     appView.setUnboxedEnums(enumDataMap);
     GraphLens previousLens = appView.graphLens();
-    appView.rewriteWithLensAndApplication(enumUnboxingLens, appBuilder.build());
+    appView.rewriteWithLens(enumUnboxingLens);
     updateOptimizationInfos(executorService, feedback, treeFixerResult.getPrunedItems());
     postBuilder.put(dependencies);
     // Methods depending on library modelisation need to be reprocessed so they are peephole
@@ -789,11 +792,6 @@
     return OptionalInt.empty();
   }
 
-  public Constraint constraintForEnumUnboxing(
-      DexEncodedMethod method, EnumAccessibilityUseRegistry useRegistry) {
-    return useRegistry.computeConstraint(method.asProgramMethod(appView));
-  }
-
   public void recordEnumState(DexProgramClass clazz, StaticFieldValues staticFieldValues) {
     if (staticFieldValues == null || !staticFieldValues.isEnumStaticFieldValues()) {
       return;
@@ -1218,6 +1216,24 @@
           return Reason.INVALID_INIT;
         }
       }
+
+      // Check if this is a checkNotNull() user. In this case, we can create a copy of the method
+      // that takes an int instead of java.lang.Object and call that method instead.
+      EnumUnboxerMethodClassification classification =
+          singleTarget.getOptimizationInfo().getEnumUnboxerMethodClassification();
+      if (classification.isCheckNotNullClassification()) {
+        CheckNotNullEnumUnboxerMethodClassification checkNotNullClassification =
+            classification.asCheckNotNullClassification();
+        if (checkNotNullClassification.isUseEligibleForUnboxing(
+            invoke.asInvokeStatic(), enumValue)) {
+          checkNotNullMethods
+              .computeIfAbsent(
+                  singleTarget.asProgramMethod(), ignoreKey(Sets::newConcurrentHashSet))
+              .add(enumClass);
+          return Reason.ELIGIBLE;
+        }
+      }
+
       // Check that the enum-value only flows into parameters whose type exactly matches the
       // enum's type.
       for (int i = 0; i < singleTarget.getParameters().size(); i++) {
@@ -1473,13 +1489,6 @@
     return Sets.newIdentityHashSet();
   }
 
-  public void synthesizeUtilityMethods(IRConverter converter, ExecutorService executorService)
-      throws ExecutionException {
-    if (enumUnboxerRewriter != null) {
-      enumUnboxerRewriter.synthesizeEnumUnboxingUtilityMethods(converter, executorService);
-    }
-  }
-
   public void unsetRewriter() {
     enumUnboxerRewriter = null;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
index 58cb128..a5978fa 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -10,12 +10,16 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.NestedGraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.collections.BidirectionalOneToManyRepresentativeHashMap;
+import com.android.tools.r8.utils.collections.BidirectionalOneToManyRepresentativeMap;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
+import com.android.tools.r8.utils.collections.MutableBidirectionalOneToManyRepresentativeMap;
 import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
 import com.google.common.collect.ImmutableMap;
 import java.util.IdentityHashMap;
@@ -29,10 +33,10 @@
   EnumUnboxingLens(
       AppView<?> appView,
       BidirectionalOneToOneMap<DexField, DexField> fieldMap,
-      BidirectionalOneToOneMap<DexMethod, DexMethod> methodMap,
+      BidirectionalOneToManyRepresentativeMap<DexMethod, DexMethod> methodMap,
       Map<DexType, DexType> typeMap,
       Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod) {
-    super(appView, fieldMap, methodMap, typeMap);
+    super(appView, fieldMap, methodMap::getRepresentativeValue, typeMap, methodMap);
     this.prototypeChangesPerMethod = prototypeChangesPerMethod;
   }
 
@@ -67,8 +71,8 @@
     private final Map<DexType, DexType> typeMap = new IdentityHashMap<>();
     private final MutableBidirectionalOneToOneMap<DexField, DexField> newFieldSignatures =
         new BidirectionalOneToOneHashMap<>();
-    private final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> newMethodSignatures =
-        new BidirectionalOneToOneHashMap<>();
+    private final MutableBidirectionalOneToManyRepresentativeMap<DexMethod, DexMethod>
+        newMethodSignatures = new BidirectionalOneToManyRepresentativeHashMap<>();
 
     private Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod =
         new IdentityHashMap<>();
@@ -135,6 +139,19 @@
               .withExtraUnusedNullParameters(numberOfExtraNullParameters));
     }
 
+    void recordCheckNotZeroMethod(
+        ProgramMethod checkNotNullMethod, ProgramMethod checkNotZeroMethod) {
+      DexMethod originalCheckNotNullMethodSignature =
+          newMethodSignatures.getKeyOrDefault(
+              checkNotNullMethod.getReference(), checkNotNullMethod.getReference());
+      newMethodSignatures.put(
+          originalCheckNotNullMethodSignature, checkNotNullMethod.getReference());
+      newMethodSignatures.put(
+          originalCheckNotNullMethodSignature, checkNotZeroMethod.getReference());
+      newMethodSignatures.setRepresentative(
+          originalCheckNotNullMethodSignature, checkNotNullMethod.getReference());
+    }
+
     public EnumUnboxingLens build(AppView<?> appView) {
       assert !typeMap.isEmpty();
       return new EnumUnboxingLens(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index fe3049f..287a4d6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -4,27 +4,17 @@
 
 package com.android.tools.r8.ir.optimize.enums;
 
-import com.android.tools.r8.cf.CfVersion;
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexEncodedMember;
-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.DexMember;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-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.analysis.type.ArrayTypeElement;
-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.code.ArrayAccess;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -44,32 +34,23 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
-import com.android.tools.r8.ir.synthetic.EnumUnboxingCfCodeProvider;
+import com.android.tools.r8.ir.optimize.enums.classification.CheckNotNullEnumUnboxerMethodClassification;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.function.Function;
 
 public class EnumUnboxingRewriter {
 
-  public static final String ENUM_UNBOXING_UTILITY_METHOD_PREFIX = "$enumboxing$";
-  private static final CfVersion REQUIRED_CLASS_FILE_VERSION = CfVersion.V1_8;
-
   private final AppView<AppInfoWithLiveness> appView;
+  private final Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping;
   private final IRConverter converter;
   private final DexItemFactory factory;
   private final InternalOptions options;
@@ -77,56 +58,21 @@
   private final EnumUnboxingLens enumUnboxingLens;
   private final EnumUnboxingUtilityClasses utilityClasses;
 
-  private final Map<DexMethod, DexEncodedMethod> utilityMethods = new ConcurrentHashMap<>();
-
-  private final DexMethod ordinalUtilityMethod;
-  private final DexMethod equalsUtilityMethod;
-  private final DexMethod compareToUtilityMethod;
-  private final DexMethod zeroCheckMethod;
-  private final DexMethod zeroCheckMessageMethod;
-
   EnumUnboxingRewriter(
       AppView<AppInfoWithLiveness> appView,
+      Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping,
       IRConverter converter,
       EnumUnboxingLens enumUnboxingLens,
       EnumDataMap unboxedEnumsInstanceFieldData,
       EnumUnboxingUtilityClasses utilityClasses) {
     this.appView = appView;
+    this.checkNotNullToCheckNotZeroMapping = checkNotNullToCheckNotZeroMapping;
     this.converter = converter;
     this.factory = appView.dexItemFactory();
     this.options = appView.options();
     this.enumUnboxingLens = enumUnboxingLens;
     this.unboxedEnumsData = unboxedEnumsInstanceFieldData;
     this.utilityClasses = utilityClasses;
-
-    // Custom methods for java.lang.Enum methods ordinal, equals and compareTo.
-    DexType sharedEnumUnboxingUtilityType = utilityClasses.getSharedUtilityClass().getType();
-    this.ordinalUtilityMethod =
-        factory.createMethod(
-            sharedEnumUnboxingUtilityType,
-            factory.createProto(factory.intType, factory.intType),
-            ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "ordinal");
-    this.equalsUtilityMethod =
-        factory.createMethod(
-            sharedEnumUnboxingUtilityType,
-            factory.createProto(factory.booleanType, factory.intType, factory.intType),
-            ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "equals");
-    this.compareToUtilityMethod =
-        factory.createMethod(
-            sharedEnumUnboxingUtilityType,
-            factory.createProto(factory.intType, factory.intType, factory.intType),
-            ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "compareTo");
-    // Custom methods for Object#getClass without outValue and Objects.requireNonNull.
-    this.zeroCheckMethod =
-        factory.createMethod(
-            sharedEnumUnboxingUtilityType,
-            factory.createProto(factory.voidType, factory.intType),
-            ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheck");
-    this.zeroCheckMessageMethod =
-        factory.createMethod(
-            sharedEnumUnboxingUtilityType,
-            factory.createProto(factory.voidType, factory.intType, factory.stringType),
-            ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheckMessage");
   }
 
   private LocalEnumUnboxingUtilityClass getLocalUtilityClass(DexType enumType) {
@@ -171,69 +117,67 @@
         // - toString is non-final, implemented in java.lang.Object, java.lang.Enum and possibly
         //   also in the unboxed enum class.
         if (instruction.isInvokeMethodWithReceiver()) {
-          InvokeMethodWithReceiver invokeMethod = instruction.asInvokeMethodWithReceiver();
-          DexType enumType = getEnumTypeOrNull(invokeMethod.getReceiver(), convertedEnums);
-          DexMethod invokedMethod = invokeMethod.getInvokedMethod();
+          InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
+          DexType enumType = getEnumTypeOrNull(invoke.getReceiver(), convertedEnums);
+          DexMethod invokedMethod = invoke.getInvokedMethod();
           if (enumType != null) {
             if (invokedMethod == factory.enumMembers.ordinalMethod
                 || invokedMethod.match(factory.enumMembers.hashCode)) {
               replaceEnumInvoke(
-                  iterator, invokeMethod, ordinalUtilityMethod, m -> synthesizeOrdinalMethod());
+                  iterator, invoke, getSharedUtilityClass().ensureOrdinalMethod(appView));
               continue;
             } else if (invokedMethod.match(factory.enumMembers.equals)) {
               replaceEnumInvoke(
-                  iterator, invokeMethod, equalsUtilityMethod, m -> synthesizeEqualsMethod());
+                  iterator, invoke, getSharedUtilityClass().ensureEqualsMethod(appView));
               continue;
             } else if (invokedMethod == factory.enumMembers.compareTo
                 || invokedMethod == factory.enumMembers.compareToWithObject) {
               replaceEnumInvoke(
-                  iterator,
-                  invokeMethod,
-                  getSharedUtilityClass()
-                      .ensureCompareToMethod(appView, converter, methodProcessor));
+                  iterator, invoke, getSharedUtilityClass().ensureCompareToMethod(appView));
               continue;
             } else if (invokedMethod == factory.enumMembers.nameMethod) {
-              rewriteNameMethod(iterator, invokeMethod, enumType);
+              rewriteNameMethod(iterator, invoke, enumType, methodProcessor);
               continue;
             } else if (invokedMethod.match(factory.enumMembers.toString)) {
               DexMethod lookupMethod = enumUnboxingLens.lookupMethod(invokedMethod);
               // If the lookupMethod is different, then a toString method was on the enumType
               // class, which was moved, and the lens code rewriter will rewrite the invoke to
               // that method.
-              if (invokeMethod.isInvokeSuper() || lookupMethod == invokedMethod) {
-                rewriteNameMethod(iterator, invokeMethod, enumType);
+              if (invoke.isInvokeSuper() || lookupMethod == invokedMethod) {
+                rewriteNameMethod(iterator, invoke, enumType, methodProcessor);
                 continue;
               }
             } else if (invokedMethod == factory.objectMembers.getClass) {
-              assert !invokeMethod.hasOutValue() || !invokeMethod.outValue().hasAnyUsers();
+              assert !invoke.hasOutValue() || !invoke.outValue().hasAnyUsers();
               replaceEnumInvoke(
-                  iterator, invokeMethod, zeroCheckMethod, m -> synthesizeZeroCheckMethod());
+                  iterator, invoke, getSharedUtilityClass().ensureCheckNotZeroMethod(appView));
               continue;
             }
           } else if (invokedMethod == factory.stringBuilderMethods.appendObject
               || invokedMethod == factory.stringBufferMethods.appendObject) {
             // Rewrites stringBuilder.append(enumInstance) as if it was
             // stringBuilder.append(String.valueOf(unboxedEnumInstance));
-            Value enumArg = invokeMethod.getArgument(1);
+            Value enumArg = invoke.getArgument(1);
             DexType enumArgType = getEnumTypeOrNull(enumArg, convertedEnums);
             if (enumArgType != null) {
-              DexMethod stringValueOfMethod = computeStringValueOfUtilityMethod(enumArgType);
+              ProgramMethod stringValueOfMethod =
+                  getLocalUtilityClass(enumArgType).ensureStringValueOfMethod(appView);
               InvokeStatic toStringInvoke =
                   InvokeStatic.builder()
                       .setMethod(stringValueOfMethod)
                       .setSingleArgument(enumArg)
                       .setFreshOutValue(appView, code)
-                      .setPosition(invokeMethod)
+                      .setPosition(invoke)
                       .build();
               DexMethod newAppendMethod =
                   invokedMethod == factory.stringBuilderMethods.appendObject
                       ? factory.stringBuilderMethods.appendString
                       : factory.stringBufferMethods.appendString;
               List<Value> arguments =
-                  ImmutableList.of(invokeMethod.getReceiver(), toStringInvoke.outValue());
+                  ImmutableList.of(invoke.getReceiver(), toStringInvoke.outValue());
               InvokeVirtual invokeAppendString =
-                  new InvokeVirtual(newAppendMethod, invokeMethod.clearOutValue(), arguments);
-              invokeAppendString.setPosition(invokeMethod.getPosition());
+                  new InvokeVirtual(newAppendMethod, invoke.clearOutValue(), arguments);
+              invokeAppendString.setPosition(invoke.getPosition());
               iterator.replaceCurrentInstruction(toStringInvoke);
               if (block.hasCatchHandlers()) {
                 iterator
@@ -248,7 +192,13 @@
           }
         } else if (instruction.isInvokeStatic()) {
           rewriteInvokeStatic(
-              instruction.asInvokeStatic(), code, context, convertedEnums, iterator, affectedPhis);
+              instruction.asInvokeStatic(),
+              code,
+              context,
+              convertedEnums,
+              iterator,
+              affectedPhis,
+              methodProcessor);
         }
         if (instruction.isStaticGet()) {
           StaticGet staticGet = instruction.asStaticGet();
@@ -301,14 +251,16 @@
           InstanceGet instanceGet = instruction.asInstanceGet();
           DexType holder = instanceGet.getField().holder;
           if (unboxedEnumsData.isUnboxedEnum(holder)) {
-            DexMethod fieldMethod = computeInstanceFieldMethod(instanceGet.getField());
+            ProgramMethod fieldMethod =
+                ensureInstanceFieldMethod(instanceGet.getField(), methodProcessor);
             Value rewrittenOutValue =
                 code.createValue(
-                    TypeElement.fromDexType(
-                        fieldMethod.proto.returnType, Nullability.maybeNull(), appView));
+                    TypeElement.fromDexType(fieldMethod.getReturnType(), maybeNull(), appView));
             InvokeStatic invoke =
                 new InvokeStatic(
-                    fieldMethod, rewrittenOutValue, ImmutableList.of(instanceGet.object()));
+                    fieldMethod.getReference(),
+                    rewrittenOutValue,
+                    ImmutableList.of(instanceGet.object()));
             iterator.replaceCurrentInstruction(invoke);
             if (unboxedEnumsData.isUnboxedEnum(instanceGet.getField().type)) {
               convertedEnums.put(invoke, instanceGet.getField().type);
@@ -350,7 +302,8 @@
       ProgramMethod context,
       Map<Instruction, DexType> convertedEnums,
       InstructionListIterator instructionIterator,
-      Set<Phi> affectedPhis) {
+      Set<Phi> affectedPhis,
+      MethodProcessor methodProcessor) {
     DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context);
     if (singleTarget == null) {
       return;
@@ -368,7 +321,7 @@
         if (!unboxedEnumsData.isUnboxedEnum(enumType)) {
           return;
         }
-        DexMethod valueOfMethod = computeValueOfUtilityMethod(enumType);
+        ProgramMethod valueOfMethod = getLocalUtilityClass(enumType).ensureValueOfMethod(appView);
         Value outValue = invoke.outValue();
         Value rewrittenOutValue = null;
         if (outValue != null) {
@@ -377,7 +330,7 @@
         }
         InvokeStatic replacement =
             new InvokeStatic(
-                valueOfMethod,
+                valueOfMethod.getReference(),
                 rewrittenOutValue,
                 Collections.singletonList(invoke.inValues().get(1)));
         instructionIterator.replaceCurrentInstruction(replacement);
@@ -394,7 +347,9 @@
         DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
         if (enumType != null) {
           replaceEnumInvoke(
-              instructionIterator, invoke, zeroCheckMethod, m -> synthesizeZeroCheckMethod());
+              instructionIterator,
+              invoke,
+              getSharedUtilityClass().ensureCheckNotZeroMethod(appView));
         }
       } else if (invokedMethod == factory.objectsMethods.requireNonNullWithMessage) {
         assert invoke.arguments().size() == 2;
@@ -404,8 +359,7 @@
           replaceEnumInvoke(
               instructionIterator,
               invoke,
-              zeroCheckMessageMethod,
-              m -> synthesizeZeroCheckMessageMethod());
+              getSharedUtilityClass().ensureCheckNotZeroWithMessageMethod(appView));
         }
       }
       return;
@@ -418,9 +372,11 @@
         Value argument = invoke.getFirstArgument();
         DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
         if (enumType != null) {
-          DexMethod stringValueOfMethod = computeStringValueOfUtilityMethod(enumType);
+          ProgramMethod stringValueOfMethod =
+              getLocalUtilityClass(enumType).ensureStringValueOfMethod(appView);
           instructionIterator.replaceCurrentInstruction(
-              new InvokeStatic(stringValueOfMethod, invoke.outValue(), invoke.arguments()));
+              new InvokeStatic(
+                  stringValueOfMethod.getReference(), invoke.outValue(), invoke.arguments()));
         }
       }
       return;
@@ -441,6 +397,29 @@
       }
       return;
     }
+
+    if (singleTarget.isProgramMethod()) {
+      EnumUnboxerMethodClassification classification =
+          singleTarget.getOptimizationInfo().getEnumUnboxerMethodClassification();
+      if (classification.isCheckNotNullClassification()) {
+        CheckNotNullEnumUnboxerMethodClassification checkNotNullClassification =
+            classification.asCheckNotNullClassification();
+        Value argument = invoke.getArgument(checkNotNullClassification.getArgumentIndex());
+        DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
+        if (enumType != null) {
+          InvokeStatic replacement =
+              InvokeStatic.builder()
+                  .setMethod(checkNotNullToCheckNotZeroMapping.get(singleTarget.getReference()))
+                  .setArguments(invoke.arguments())
+                  .setPosition(invoke.getPosition())
+                  .build();
+          instructionIterator.replaceCurrentInstruction(replacement);
+          convertedEnums.put(replacement, enumType);
+        }
+      } else {
+        assert !checkNotNullToCheckNotZeroMapping.containsKey(singleTarget.getReference());
+      }
+    }
   }
 
   private void removeRedundantValuesArrayCloning(
@@ -464,11 +443,15 @@
   }
 
   private void rewriteNameMethod(
-      InstructionListIterator iterator, InvokeMethodWithReceiver invokeMethod, DexType enumType) {
-    DexMethod toStringMethod =
-        computeInstanceFieldUtilityMethod(enumType, factory.enumMembers.nameField);
+      InstructionListIterator iterator,
+      InvokeMethodWithReceiver invoke,
+      DexType enumType,
+      MethodProcessor methodProcessor) {
+    ProgramMethod toStringMethod =
+        getLocalUtilityClass(enumType)
+            .ensureGetInstanceFieldMethod(appView, factory.enumMembers.nameField);
     iterator.replaceCurrentInstruction(
-        new InvokeStatic(toStringMethod, invokeMethod.outValue(), invokeMethod.arguments()));
+        new InvokeStatic(toStringMethod.getReference(), invoke.outValue(), invoke.arguments()));
   }
 
   private Value fixNullsInBlockPhis(IRCode code, BasicBlock block, Value zeroConstValue) {
@@ -496,32 +479,22 @@
     return iterator.insertConstIntInstruction(code, options, 0);
   }
 
-  private DexMethod computeInstanceFieldMethod(DexField field) {
+  private ProgramMethod ensureInstanceFieldMethod(DexField field, MethodProcessor methodProcessor) {
     EnumInstanceFieldKnownData enumFieldKnownData =
         unboxedEnumsData.getInstanceFieldData(field.holder, field);
     if (enumFieldKnownData.isOrdinal()) {
-      utilityMethods.computeIfAbsent(ordinalUtilityMethod, m -> synthesizeOrdinalMethod());
-      return ordinalUtilityMethod;
+      return getSharedUtilityClass().ensureOrdinalMethod(appView);
     }
-    return computeInstanceFieldUtilityMethod(field.holder, field);
+    return getLocalUtilityClass(field.getHolderType()).ensureGetInstanceFieldMethod(appView, field);
   }
 
   private void replaceEnumInvoke(
       InstructionListIterator iterator, InvokeMethod invoke, ProgramMethod method) {
-    replaceEnumInvoke(iterator, invoke, method.getReference(), null);
-  }
-
-  private void replaceEnumInvoke(
-      InstructionListIterator iterator,
-      InvokeMethod invoke,
-      DexMethod method,
-      Function<DexMethod, DexEncodedMethod> synthesizor) {
-    if (synthesizor != null) {
-      utilityMethods.computeIfAbsent(method, synthesizor);
-    }
     InvokeStatic replacement =
         new InvokeStatic(
-            method, invoke.hasUnusedOutValue() ? null : invoke.outValue(), invoke.arguments());
+            method.getReference(),
+            invoke.hasUnusedOutValue() ? null : invoke.outValue(),
+            invoke.arguments());
     assert !replacement.hasOutValue()
         || !replacement.getInvokedMethod().getReturnType().isVoidType();
     iterator.replaceCurrentInstruction(replacement);
@@ -555,57 +528,6 @@
     return unboxedEnumsData.isUnboxedEnum(enumType) ? enumType : null;
   }
 
-  public static String compatibleName(DexType type) {
-    return type.toSourceString().replace('.', '$');
-  }
-
-  private DexMethod computeInstanceFieldUtilityMethod(DexType enumType, DexField field) {
-    assert unboxedEnumsData.isUnboxedEnum(enumType);
-    assert field.holder == enumType || field.holder == factory.enumType;
-    String methodName =
-        "get"
-            + (enumType == field.holder ? "" : "Enum$")
-            + field.name
-            + "$$"
-            + compatibleName(enumType);
-    DexMethod fieldMethod =
-        factory.createMethod(
-            utilityClasses.getLocalUtilityClass(enumType).getType(),
-            factory.createProto(field.type, factory.intType),
-            methodName);
-    utilityMethods.computeIfAbsent(
-        fieldMethod, m -> synthesizeInstanceFieldMethod(m, enumType, field, null));
-    return fieldMethod;
-  }
-
-  private DexMethod computeStringValueOfUtilityMethod(DexType enumType) {
-    // TODO(b/167994636): remove duplication between instance field name read and this method.
-    assert unboxedEnumsData.isUnboxedEnum(enumType);
-    String methodName = "string$valueOf$" + compatibleName(enumType);
-    DexMethod fieldMethod =
-        factory.createMethod(
-            utilityClasses.getLocalUtilityClass(enumType).getType(),
-            factory.createProto(factory.stringType, factory.intType),
-            methodName);
-    AbstractValue nullString =
-        appView.abstractValueFactory().createSingleStringValue(factory.createString("null"));
-    utilityMethods.computeIfAbsent(
-        fieldMethod,
-        m -> synthesizeInstanceFieldMethod(m, enumType, factory.enumMembers.nameField, nullString));
-    return fieldMethod;
-  }
-
-  private DexMethod computeValueOfUtilityMethod(DexType enumType) {
-    assert unboxedEnumsData.isUnboxedEnum(enumType);
-    DexMethod valueOf =
-        factory.createMethod(
-            utilityClasses.getLocalUtilityClass(enumType).getType(),
-            factory.createProto(factory.intType, factory.stringType),
-            "valueOf" + compatibleName(enumType));
-    utilityMethods.computeIfAbsent(valueOf, m -> synthesizeValueOfUtilityMethod(m, enumType));
-    return valueOf;
-  }
-
   private DexType getEnumTypeOrNull(ArrayAccess arrayAccess) {
     ArrayTypeElement arrayType = arrayAccess.array().getType().asArrayType();
     if (arrayType == null) {
@@ -622,121 +544,4 @@
     DexType classType = baseType.asClassType().getClassType();
     return unboxedEnumsData.isUnboxedEnum(classType) ? classType : null;
   }
-
-  void synthesizeEnumUnboxingUtilityMethods(IRConverter converter, ExecutorService executorService)
-      throws ExecutionException {
-    // Append to the various utility classes, in deterministic order, the utility methods and
-    // fields required.
-    Map<DexType, List<DexEncodedMethod>> methodMap = triageEncodedMembers(utilityMethods.values());
-    if (methodMap.isEmpty()) {
-      return;
-    }
-    SortedProgramMethodSet wave = SortedProgramMethodSet.create();
-    methodMap.forEach(
-        (type, methodsSorted) -> {
-          DexProgramClass utilityClass = appView.definitionFor(type).asProgramClass();
-          assert utilityClass != null;
-          utilityClass.addDirectMethods(methodsSorted);
-          for (DexEncodedMethod dexEncodedMethod : methodsSorted) {
-            wave.add(new ProgramMethod(utilityClass, dexEncodedMethod));
-          }
-        });
-    converter.processMethodsConcurrently(wave, executorService);
-  }
-
-  <R extends DexMember<T, R>, T extends DexEncodedMember<T, R>>
-      Map<DexType, List<T>> triageEncodedMembers(Collection<T> encodedMembers) {
-    if (encodedMembers.isEmpty()) {
-      return Collections.emptyMap();
-    }
-    Map<DexType, List<T>> encodedMembersMap = new IdentityHashMap<>();
-    // We compute encodedMembers by types.
-    for (T encodedMember : encodedMembers) {
-      List<T> members =
-          encodedMembersMap.computeIfAbsent(
-              encodedMember.getHolderType(), ignored -> new ArrayList<>());
-      members.add(encodedMember);
-    }
-    // We make the order deterministic.
-    for (List<T> value : encodedMembersMap.values()) {
-      value.sort(Comparator.comparing(DexEncodedMember::getReference));
-    }
-    return encodedMembersMap;
-  }
-
-  private DexEncodedMethod synthesizeInstanceFieldMethod(
-      DexMethod method, DexType enumType, DexField field, AbstractValue nullValue) {
-    assert method.proto.returnType == field.type;
-    assert unboxedEnumsData.getInstanceFieldData(enumType, field).isMapping();
-    CfCode cfCode =
-        new EnumUnboxingCfCodeProvider.EnumUnboxingInstanceFieldCfCodeProvider(
-                appView,
-                method.holder,
-                field.type,
-                unboxedEnumsData.getInstanceFieldData(enumType, field).asEnumFieldMappingData(),
-                nullValue)
-            .generateCfCode();
-    return synthesizeUtilityMethod(cfCode, method);
-  }
-
-  private DexEncodedMethod synthesizeValueOfUtilityMethod(DexMethod method, DexType enumType) {
-    assert method.proto.returnType == factory.intType;
-    assert unboxedEnumsData
-        .getInstanceFieldData(enumType, factory.enumMembers.nameField)
-        .isMapping();
-    CfCode cfCode =
-        new EnumUnboxingCfCodeProvider.EnumUnboxingValueOfCfCodeProvider(
-                appView,
-                method.holder,
-                enumType,
-                unboxedEnumsData
-                    .getInstanceFieldData(enumType, factory.enumMembers.nameField)
-                    .asEnumFieldMappingData())
-            .generateCfCode();
-    return synthesizeUtilityMethod(cfCode, method);
-  }
-
-  private DexEncodedMethod synthesizeZeroCheckMethod() {
-    CfCode cfCode =
-        EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheck(appView.options(), zeroCheckMethod);
-    return synthesizeUtilityMethod(cfCode, zeroCheckMethod);
-  }
-
-  private DexEncodedMethod synthesizeZeroCheckMessageMethod() {
-    CfCode cfCode =
-        EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheckMessage(
-            appView.options(), zeroCheckMessageMethod);
-    return synthesizeUtilityMethod(cfCode, zeroCheckMessageMethod);
-  }
-
-  private DexEncodedMethod synthesizeOrdinalMethod() {
-    CfCode cfCode =
-        EnumUnboxingCfMethods.EnumUnboxingMethods_ordinal(appView.options(), ordinalUtilityMethod);
-    return synthesizeUtilityMethod(cfCode, ordinalUtilityMethod);
-  }
-
-  private DexEncodedMethod synthesizeEqualsMethod() {
-    CfCode cfCode =
-        EnumUnboxingCfMethods.EnumUnboxingMethods_equals(appView.options(), equalsUtilityMethod);
-    return synthesizeUtilityMethod(cfCode, equalsUtilityMethod);
-  }
-
-  private DexEncodedMethod synthesizeCompareToMethod() {
-    CfCode cfCode =
-        EnumUnboxingCfMethods.EnumUnboxingMethods_compareTo(
-            appView.options(), compareToUtilityMethod);
-    return synthesizeUtilityMethod(cfCode, compareToUtilityMethod);
-  }
-
-  private DexEncodedMethod synthesizeUtilityMethod(CfCode cfCode, DexMethod method) {
-    return new DexEncodedMethod(
-        method,
-        MethodAccessFlags.createPublicStaticSynthetic(),
-        MethodTypeSignature.noSignature(),
-        DexAnnotationSet.empty(),
-        ParameterAnnotationsList.empty(),
-        cfCode,
-        true,
-        REQUIRED_CLASS_FILE_VERSION);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 243e721..9331f51 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 
+import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -18,6 +19,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
+import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
@@ -36,19 +38,28 @@
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
+import com.android.tools.r8.ir.optimize.enums.classification.CheckNotNullEnumUnboxerMethodClassification;
+import com.android.tools.r8.ir.optimize.enums.code.CheckNotZeroCode;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.ImmutableArrayUtils;
 import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.ProgramMethodMap;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.ListIterator;
@@ -62,6 +73,7 @@
 
   private final EnumUnboxingLens.Builder lensBuilder;
   private final AppView<AppInfoWithLiveness> appView;
+  private final ProgramMethodMap<Set<DexProgramClass>> checkNotNullMethods;
   private final DexItemFactory factory;
   private final EnumDataMap enumDataMap;
   private final Set<DexProgramClass> unboxedEnums;
@@ -69,10 +81,12 @@
 
   EnumUnboxingTreeFixer(
       AppView<AppInfoWithLiveness> appView,
+      ProgramMethodMap<Set<DexProgramClass>> checkNotNullMethods,
       EnumDataMap enumDataMap,
       Set<DexProgramClass> unboxedEnums,
       EnumUnboxingUtilityClasses utilityClasses) {
     this.appView = appView;
+    this.checkNotNullMethods = checkNotNullMethods;
     this.enumDataMap = enumDataMap;
     this.factory = appView.dexItemFactory();
     this.lensBuilder =
@@ -117,9 +131,82 @@
       }
     }
 
-    return new Result(lensBuilder.build(appView), prunedItemsBuilder.build());
+    // Create mapping from checkNotNull() to checkNotZero() methods.
+    Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping =
+        duplicateCheckNotNullMethods(converter, executorService);
+
+    return new Result(
+        checkNotNullToCheckNotZeroMapping, lensBuilder.build(appView), prunedItemsBuilder.build());
   }
 
+  private Map<DexMethod, DexMethod> duplicateCheckNotNullMethods(
+      IRConverter converter, ExecutorService executorService) throws ExecutionException {
+    Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping = new IdentityHashMap<>();
+    ProcessorContext processorContext = appView.createProcessorContext();
+    OneTimeMethodProcessor.Builder methodProcessorBuilder =
+        OneTimeMethodProcessor.builder(processorContext);
+
+    // Only duplicate checkNotNull() methods that are required for enum unboxing.
+    checkNotNullMethods.removeIf(
+        (checkNotNullMethod, dependentEnums) ->
+            !SetUtils.containsAnyOf(unboxedEnums, dependentEnums));
+
+    // For each checkNotNull() method, synthesize a free flowing static checkNotZero() method that
+    // takes an int instead of an Object with the same implementation.
+    checkNotNullMethods.forEach(
+        (checkNotNullMethod, dependentEnums) -> {
+          CheckNotNullEnumUnboxerMethodClassification checkNotNullClassification =
+              checkNotNullMethod
+                  .getOptimizationInfo()
+                  .getEnumUnboxerMethodClassification()
+                  .asCheckNotNullClassification();
+          DexProto newProto =
+              factory.createProto(
+                  factory.voidType,
+                  ImmutableArrayUtils.set(
+                      checkNotNullMethod.getParameters().getBacking(),
+                      checkNotNullClassification.getArgumentIndex(),
+                      factory.intType));
+          ProgramMethod checkNotZeroMethod =
+              appView
+                  .getSyntheticItems()
+                  .createMethod(
+                      SyntheticKind.ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD,
+                      // Use the context of the checkNotNull() method to ensure the method is placed
+                      // in the same feature split.
+                      processorContext
+                          .createMethodProcessingContext(checkNotNullMethod)
+                          .createUniqueContext(),
+                      appView,
+                      builder ->
+                          builder
+                              .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                              .setClassFileVersion(
+                                  checkNotNullMethod
+                                      .getDefinition()
+                                      .getClassFileVersionOrElse(null))
+                              .setCode(method -> new CheckNotZeroCode(checkNotNullMethod))
+                              .setProto(newProto));
+          checkNotNullToCheckNotZeroMapping.put(
+              checkNotNullMethod.getReference(), checkNotZeroMethod.getReference());
+          lensBuilder.recordCheckNotZeroMethod(checkNotNullMethod, checkNotZeroMethod);
+          methodProcessorBuilder.add(checkNotZeroMethod);
+        });
+
+    // Convert each of the synthesized methods. These methods are converted eagerly, since their
+    // code objects are of type 'CheckNotZeroCode', which implements most methods using throw new
+    // Unreachable().
+    OneTimeMethodProcessor methodProcessor = methodProcessorBuilder.build();
+    methodProcessor.forEachWaveWithExtension(
+        (method, methodProcessingContext) ->
+            converter.processDesugaredMethod(
+                method, OptimizationFeedback.getSimple(), methodProcessor, methodProcessingContext),
+        executorService);
+
+    return checkNotNullToCheckNotZeroMapping;
+  }
+
+
   private void fixupEnumClassInitializers(IRConverter converter, ExecutorService executorService)
       throws ExecutionException {
     DexEncodedField ordinalField =
@@ -367,12 +454,13 @@
       Predicate<DexMethod> availableMethodSignatures) {
     DexMethod methodReference = method.getReference();
 
-    // Create a new, fresh method signature on the local utility class.
+    // Create a new, fresh method signature on the local utility class. We prefix the method by "_"
+    // such that this does not collide with the utility methods we synthesize for unboxing.
     DexMethod newMethod =
         method.getDefinition().isClassInitializer()
             ? factory.createClassInitializer(localUtilityClass.getType())
             : factory.createFreshMethodNameWithoutHolder(
-                method.getName().toString(),
+                "_" + method.getName().toString(),
                 fixupProto(
                     method.getAccessFlags().isStatic()
                         ? method.getProto()
@@ -531,14 +619,23 @@
 
   public static class Result {
 
+    private final Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping;
     private final EnumUnboxingLens lens;
     private final PrunedItems prunedItems;
 
-    Result(EnumUnboxingLens lens, PrunedItems prunedItems) {
+    Result(
+        Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping,
+        EnumUnboxingLens lens,
+        PrunedItems prunedItems) {
+      this.checkNotNullToCheckNotZeroMapping = checkNotNullToCheckNotZeroMapping;
       this.lens = lens;
       this.prunedItems = prunedItems;
     }
 
+    Map<DexMethod, DexMethod> getCheckNotNullToCheckNotZeroMapping() {
+      return checkNotNullToCheckNotZeroMapping;
+    }
+
     EnumUnboxingLens getLens() {
       return lens;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClass.java
index 6026c71..34dd886 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClass.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClass.java
@@ -4,9 +4,23 @@
 
 package com.android.tools.r8.ir.optimize.enums;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public abstract class EnumUnboxingUtilityClass {
 
+  private final DexProgramClass synthesizingContext;
+
+  EnumUnboxingUtilityClass(DexProgramClass synthesizingContext) {
+    this.synthesizingContext = synthesizingContext;
+  }
+
+  public abstract void ensureMethods(AppView<AppInfoWithLiveness> appView);
+
   public abstract DexProgramClass getDefinition();
+
+  public final DexProgramClass getSynthesizingContext() {
+    return synthesizingContext;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java
index 05d4955..8dd1398 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java
@@ -8,12 +8,17 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
+import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableMap;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
 
 public class EnumUnboxingUtilityClasses {
@@ -61,38 +66,63 @@
     private ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses;
     private SharedEnumUnboxingUtilityClass sharedUtilityClass;
 
+    private final FieldAccessInfoCollectionModifier.Builder
+        fieldAccessInfoCollectionModifierBuilder = FieldAccessInfoCollectionModifier.builder();
+
     public Builder(AppView<AppInfoWithLiveness> appView) {
       this.appView = appView;
     }
 
     public Builder synthesizeEnumUnboxingUtilityClasses(
-        Set<DexProgramClass> enumsToUnbox,
-        EnumDataMap enumDataMap,
-        DirectMappedDexApplication.Builder appBuilder,
-        FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
+        Set<DexProgramClass> enumsToUnbox, EnumDataMap enumDataMap) {
       SharedEnumUnboxingUtilityClass sharedUtilityClass =
           SharedEnumUnboxingUtilityClass.builder(
                   appView, enumDataMap, enumsToUnbox, fieldAccessInfoCollectionModifierBuilder)
               .build();
       ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses =
-          createLocalUtilityClasses(enumsToUnbox, appBuilder);
+          createLocalUtilityClasses(enumsToUnbox, enumDataMap);
       this.localUtilityClasses = localUtilityClasses;
       this.sharedUtilityClass = sharedUtilityClass;
       return this;
     }
 
-    public EnumUnboxingUtilityClasses build() {
-      return new EnumUnboxingUtilityClasses(sharedUtilityClass, localUtilityClasses);
+    public EnumUnboxingUtilityClasses build(IRConverter converter, ExecutorService executorService)
+        throws ExecutionException {
+      EnumUnboxingUtilityClasses utilityClasses =
+          new EnumUnboxingUtilityClasses(sharedUtilityClass, localUtilityClasses);
+
+      // Extend the field access info collection with information about synthesized fields.
+      fieldAccessInfoCollectionModifierBuilder.build().modify(appView);
+
+      // Create and process the utility methods.
+      OneTimeMethodProcessor.Builder methodProcessorBuilder =
+          OneTimeMethodProcessor.builder(appView.createProcessorContext());
+      utilityClasses.forEach(
+          utilityClass -> {
+            utilityClass.ensureMethods(appView);
+            utilityClass.getDefinition().forEachProgramMethod(methodProcessorBuilder::add);
+          });
+      OneTimeMethodProcessor methodProcessor = methodProcessorBuilder.build();
+      methodProcessor.forEachWaveWithExtension(
+          (method, methodProcessingContext) ->
+              converter.processDesugaredMethod(
+                  method,
+                  OptimizationFeedbackSimple.getInstance(),
+                  methodProcessor,
+                  methodProcessingContext),
+          executorService);
+      return utilityClasses;
     }
 
     private ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> createLocalUtilityClasses(
-        Set<DexProgramClass> enumsToUnbox, DirectMappedDexApplication.Builder appBuilder) {
+        Set<DexProgramClass> enumsToUnbox, EnumDataMap dataMap) {
       ImmutableMap.Builder<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses =
           ImmutableMap.builder();
       for (DexProgramClass enumToUnbox : enumsToUnbox) {
+        EnumData data = dataMap.get(enumToUnbox);
         localUtilityClasses.put(
             enumToUnbox.getType(),
-            LocalEnumUnboxingUtilityClass.builder(appView, enumToUnbox).build(appBuilder));
+            LocalEnumUnboxingUtilityClass.builder(appView, enumToUnbox, data).build());
       }
       return localUtilityClasses.build();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
index f4d4636..8dbd8a4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
@@ -4,20 +4,25 @@
 
 package com.android.tools.r8.ir.optimize.enums;
 
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
+import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.DexAnnotationSet;
-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.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.DexTypeList;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
+import com.android.tools.r8.ir.synthetic.EnumUnboxingCfCodeProvider;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import java.util.Collections;
+import com.android.tools.r8.synthesis.SyntheticMethodBuilder.SyntheticCodeGenerator;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 
 public class LocalEnumUnboxingUtilityClass extends EnumUnboxingUtilityClass {
 
@@ -25,13 +30,108 @@
       "$r8$EnumUnboxingLocalUtility";
 
   private final DexProgramClass localUtilityClass;
+  private final EnumData data;
 
-  public LocalEnumUnboxingUtilityClass(DexProgramClass localUtilityClass) {
+  public LocalEnumUnboxingUtilityClass(
+      DexProgramClass localUtilityClass, EnumData data, DexProgramClass synthesizingContext) {
+    super(synthesizingContext);
     this.localUtilityClass = localUtilityClass;
+    this.data = data;
   }
 
-  public static Builder builder(AppView<AppInfoWithLiveness> appView, DexProgramClass enumToUnbox) {
-    return new Builder(appView, enumToUnbox);
+  public static Builder builder(
+      AppView<AppInfoWithLiveness> appView, DexProgramClass enumToUnbox, EnumData data) {
+    return new Builder(appView, enumToUnbox, data);
+  }
+
+  @Override
+  public void ensureMethods(AppView<AppInfoWithLiveness> appView) {
+    data.instanceFieldMap.forEach(
+        (field, fieldData) -> {
+          if (fieldData.isMapping()) {
+            ensureGetInstanceFieldMethod(appView, field);
+          }
+        });
+    if (data.instanceFieldMap.containsKey(appView.dexItemFactory().enumMembers.nameField)) {
+      ensureStringValueOfMethod(appView);
+      ensureValueOfMethod(appView);
+    }
+  }
+
+  public ProgramMethod ensureGetInstanceFieldMethod(
+      AppView<AppInfoWithLiveness> appView,
+      DexField field) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    String fieldName = field.getName().toString();
+    DexString methodName;
+    if (field.getHolderType() == getSynthesizingContext().getType()) {
+      methodName =
+          dexItemFactory.createString(
+              "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1));
+    } else {
+      assert field == appView.dexItemFactory().enumMembers.nameField
+          || field == appView.dexItemFactory().enumMembers.ordinalField;
+      methodName = field.getName();
+    }
+    return internalEnsureMethod(
+        appView,
+        methodName,
+        dexItemFactory.createProto(field.getType(), dexItemFactory.intType),
+        method ->
+            new EnumUnboxingCfCodeProvider.EnumUnboxingInstanceFieldCfCodeProvider(
+                    appView, getType(), data, field)
+                .generateCfCode());
+  }
+
+  public ProgramMethod ensureStringValueOfMethod(AppView<AppInfoWithLiveness> appView) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    AbstractValue defaultValue =
+        appView.abstractValueFactory().createSingleStringValue(dexItemFactory.createString("null"));
+    return internalEnsureMethod(
+        appView,
+        dexItemFactory.createString("stringValueOf"),
+        dexItemFactory.createProto(dexItemFactory.stringType, dexItemFactory.intType),
+        method ->
+            new EnumUnboxingCfCodeProvider.EnumUnboxingInstanceFieldCfCodeProvider(
+                    appView, getType(), data, dexItemFactory.enumMembers.nameField, defaultValue)
+                .generateCfCode());
+  }
+
+  public ProgramMethod ensureValueOfMethod(AppView<AppInfoWithLiveness> appView) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    return internalEnsureMethod(
+        appView,
+        dexItemFactory.createString("valueOf"),
+        dexItemFactory.createProto(dexItemFactory.intType, dexItemFactory.stringType),
+        method ->
+            new EnumUnboxingCfCodeProvider.EnumUnboxingValueOfCfCodeProvider(
+                    appView,
+                    getType(),
+                    getSynthesizingContext().getType(),
+                    data.getInstanceFieldData(dexItemFactory.enumMembers.nameField)
+                        .asEnumFieldMappingData())
+                .generateCfCode());
+  }
+
+  private ProgramMethod internalEnsureMethod(
+      AppView<AppInfoWithLiveness> appView,
+      DexString methodName,
+      DexProto methodProto,
+      SyntheticCodeGenerator codeGenerator) {
+    return appView
+        .getSyntheticItems()
+        .ensureFixedClassMethod(
+            methodName,
+            methodProto,
+            SyntheticKind.ENUM_UNBOXING_LOCAL_UTILITY_CLASS,
+            getSynthesizingContext(),
+            appView,
+            emptyConsumer(),
+            methodBuilder ->
+                methodBuilder
+                    .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                    .setCode(codeGenerator)
+                    .setClassFileVersion(CfVersion.V1_6));
   }
 
   @Override
@@ -46,49 +146,38 @@
   public static class Builder {
 
     private final AppView<AppInfoWithLiveness> appView;
-    private final DexItemFactory dexItemFactory;
+    private final EnumData data;
     private final DexProgramClass enumToUnbox;
     private final DexType localUtilityClassType;
 
-    private Builder(AppView<AppInfoWithLiveness> appView, DexProgramClass enumToUnbox) {
+    private Builder(
+        AppView<AppInfoWithLiveness> appView, DexProgramClass enumToUnbox, EnumData data) {
       this.appView = appView;
-      this.dexItemFactory = appView.dexItemFactory();
+      this.data = data;
       this.enumToUnbox = enumToUnbox;
       this.localUtilityClassType =
           EnumUnboxingUtilityClasses.Builder.getUtilityClassType(
-              enumToUnbox, ENUM_UNBOXING_LOCAL_UTILITY_CLASS_SUFFIX, dexItemFactory);
+              enumToUnbox, ENUM_UNBOXING_LOCAL_UTILITY_CLASS_SUFFIX, appView.dexItemFactory());
 
       assert appView.appInfo().definitionForWithoutExistenceAssert(localUtilityClassType) == null;
     }
 
-    LocalEnumUnboxingUtilityClass build(DirectMappedDexApplication.Builder appBuilder) {
+    LocalEnumUnboxingUtilityClass build() {
       DexProgramClass clazz = createClass();
-      appBuilder.addSynthesizedClass(clazz);
-      appView.appInfo().addSynthesizedClass(clazz, enumToUnbox);
-      return new LocalEnumUnboxingUtilityClass(clazz);
+      return new LocalEnumUnboxingUtilityClass(clazz, data, enumToUnbox);
     }
 
     private DexProgramClass createClass() {
-      return new DexProgramClass(
-          localUtilityClassType,
-          null,
-          new SynthesizedOrigin("enum unboxing", EnumUnboxer.class),
-          ClassAccessFlags.createPublicFinalSynthetic(),
-          appView.dexItemFactory().objectType,
-          DexTypeList.empty(),
-          null,
-          null,
-          Collections.emptyList(),
-          null,
-          Collections.emptyList(),
-          ClassSignature.noSignature(),
-          DexAnnotationSet.empty(),
-          DexEncodedField.EMPTY_ARRAY,
-          DexEncodedField.EMPTY_ARRAY,
-          DexEncodedMethod.EMPTY_ARRAY,
-          DexEncodedMethod.EMPTY_ARRAY,
-          appView.dexItemFactory().getSkipNameValidationForTesting(),
-          DexProgramClass::checksumFromType);
+      DexProgramClass clazz =
+          appView
+              .getSyntheticItems()
+              .createFixedClass(
+                  SyntheticKind.ENUM_UNBOXING_LOCAL_UTILITY_CLASS,
+                  enumToUnbox,
+                  appView,
+                  builder -> builder.setUseSortedMethodBacking(true));
+      assert clazz.getAccessFlags().equals(ClassAccessFlags.createPublicFinalSynthetic());
+      return clazz;
     }
   }
 }
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 3e556e6..5cbab1f 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
@@ -25,6 +25,8 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 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.FieldAccessFlags;
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
@@ -34,11 +36,9 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.conversion.MethodProcessor;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
+import com.android.tools.r8.synthesis.SyntheticMethodBuilder.SyntheticCodeGenerator;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.google.common.collect.ImmutableList;
@@ -51,15 +51,14 @@
 public class SharedEnumUnboxingUtilityClass extends EnumUnboxingUtilityClass {
 
   private final DexProgramClass sharedUtilityClass;
-  private final DexProgramClass synthesizingContext;
   private final ProgramMethod valuesMethod;
 
   public SharedEnumUnboxingUtilityClass(
       DexProgramClass sharedUtilityClass,
       DexProgramClass synthesizingContext,
       ProgramMethod valuesMethod) {
+    super(synthesizingContext);
     this.sharedUtilityClass = sharedUtilityClass;
-    this.synthesizingContext = synthesizingContext;
     this.valuesMethod = valuesMethod;
   }
 
@@ -72,11 +71,69 @@
         appView, enumDataMap, enumsToUnbox, fieldAccessInfoCollectionModifierBuilder);
   }
 
-  public ProgramMethod ensureCompareToMethod(
-      AppView<AppInfoWithLiveness> appView,
-      IRConverter converter,
-      MethodProcessor methodProcessor) {
+  @Override
+  public void ensureMethods(AppView<AppInfoWithLiveness> appView) {
+    ensureCheckNotZeroMethod(appView);
+    ensureCheckNotZeroWithMessageMethod(appView);
+    ensureCompareToMethod(appView);
+    ensureEqualsMethod(appView);
+    ensureOrdinalMethod(appView);
+  }
+
+  public ProgramMethod ensureCheckNotZeroMethod(AppView<AppInfoWithLiveness> appView) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
+    return internalEnsureMethod(
+        appView,
+        dexItemFactory.createString("checkNotZero"),
+        dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.intType),
+        method -> EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheck(appView.options(), method));
+  }
+
+  public ProgramMethod ensureCheckNotZeroWithMessageMethod(AppView<AppInfoWithLiveness> appView) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    return internalEnsureMethod(
+        appView,
+        dexItemFactory.createString("checkNotZero"),
+        dexItemFactory.createProto(
+            dexItemFactory.voidType, dexItemFactory.intType, dexItemFactory.stringType),
+        method ->
+            EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheckMessage(appView.options(), method));
+  }
+
+  public ProgramMethod ensureCompareToMethod(AppView<AppInfoWithLiveness> appView) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    return internalEnsureMethod(
+        appView,
+        dexItemFactory.enumMembers.compareTo.getName(),
+        dexItemFactory.createProto(
+            dexItemFactory.intType, dexItemFactory.intType, dexItemFactory.intType),
+        method -> EnumUnboxingCfMethods.EnumUnboxingMethods_compareTo(appView.options(), method));
+  }
+
+  public ProgramMethod ensureEqualsMethod(AppView<AppInfoWithLiveness> appView) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    return internalEnsureMethod(
+        appView,
+        dexItemFactory.enumMembers.equals.getName(),
+        dexItemFactory.createProto(
+            dexItemFactory.booleanType, dexItemFactory.intType, dexItemFactory.intType),
+        method -> EnumUnboxingCfMethods.EnumUnboxingMethods_equals(appView.options(), method));
+  }
+
+  public ProgramMethod ensureOrdinalMethod(AppView<AppInfoWithLiveness> appView) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    return internalEnsureMethod(
+        appView,
+        dexItemFactory.enumMembers.ordinalMethod.getName(),
+        dexItemFactory.createProto(dexItemFactory.intType, dexItemFactory.intType),
+        method -> EnumUnboxingCfMethods.EnumUnboxingMethods_ordinal(appView.options(), method));
+  }
+
+  private ProgramMethod internalEnsureMethod(
+      AppView<AppInfoWithLiveness> appView,
+      DexString methodName,
+      DexProto methodProto,
+      SyntheticCodeGenerator codeGenerator) {
     // TODO(b/191957637): Consider creating free flowing static methods instead. The synthetic
     //  infrastructure needs to be augmented with a new method ensureFixedMethod() or
     //  ensureFixedFreeFlowingMethod() for this, if we want to create only one utility method (and
@@ -84,27 +141,17 @@
     return appView
         .getSyntheticItems()
         .ensureFixedClassMethod(
-            dexItemFactory.enumMembers.compareTo.getName(),
-            dexItemFactory.createProto(
-                dexItemFactory.intType, dexItemFactory.intType, dexItemFactory.intType),
+            methodName,
+            methodProto,
             SyntheticKind.ENUM_UNBOXING_SHARED_UTILITY_CLASS,
-            synthesizingContext,
+            getSynthesizingContext(),
             appView,
             ConsumerUtils.emptyConsumer(),
             methodBuilder ->
                 methodBuilder
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
-                    .setCode(
-                        method ->
-                            EnumUnboxingCfMethods.EnumUnboxingMethods_compareTo(
-                                appView.options(), method))
-                    .setClassFileVersion(CfVersion.V1_6),
-            newMethod ->
-                converter.processDesugaredMethod(
-                    newMethod,
-                    OptimizationFeedbackSimple.getInstance(),
-                    methodProcessor,
-                    methodProcessor.createMethodProcessingContext(newMethod)));
+                    .setCode(codeGenerator)
+                    .setClassFileVersion(CfVersion.V1_6));
   }
 
   @Override
@@ -146,8 +193,10 @@
 
     SharedEnumUnboxingUtilityClass build() {
       DexProgramClass clazz = createClass();
-      return new SharedEnumUnboxingUtilityClass(
-          clazz, synthesizingContext, new ProgramMethod(clazz, valuesMethod));
+      SharedEnumUnboxingUtilityClass sharedUtilityClass =
+          new SharedEnumUnboxingUtilityClass(
+              clazz, synthesizingContext, new ProgramMethod(clazz, valuesMethod));
+      return sharedUtilityClass;
     }
 
     private DexProgramClass createClass() {
@@ -166,7 +215,8 @@
                             ImmutableList.of(
                                 createClassInitializer(sharedUtilityClassType, valuesField),
                                 createValuesMethod(sharedUtilityClassType, valuesField)))
-                        .setStaticFields(ImmutableList.of(valuesField));
+                        .setStaticFields(ImmutableList.of(valuesField))
+                        .setUseSortedMethodBacking(true);
                   });
       assert clazz.getAccessFlags().equals(ClassAccessFlags.createPublicFinalSynthetic());
       return clazz;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/CheckNotNullEnumUnboxerMethodClassification.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/CheckNotNullEnumUnboxerMethodClassification.java
new file mode 100644
index 0000000..fd892c1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/CheckNotNullEnumUnboxerMethodClassification.java
@@ -0,0 +1,42 @@
+// 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.enums.classification;
+
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.Value;
+
+public final class CheckNotNullEnumUnboxerMethodClassification
+    extends EnumUnboxerMethodClassification {
+
+  private int argumentIndex;
+
+  CheckNotNullEnumUnboxerMethodClassification(int argumentIndex) {
+    this.argumentIndex = argumentIndex;
+  }
+
+  public int getArgumentIndex() {
+    return argumentIndex;
+  }
+
+  public boolean isUseEligibleForUnboxing(InvokeStatic invoke, Value enumValue) {
+    for (int argumentIndex = 0; argumentIndex < invoke.arguments().size(); argumentIndex++) {
+      Value argument = invoke.getArgument(argumentIndex);
+      if (argument == enumValue && argumentIndex != getArgumentIndex()) {
+        return false;
+      }
+    }
+    return invoke.hasUnusedOutValue();
+  }
+
+  @Override
+  public boolean isCheckNotNullClassification() {
+    return true;
+  }
+
+  @Override
+  public CheckNotNullEnumUnboxerMethodClassification asCheckNotNullClassification() {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassification.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassification.java
new file mode 100644
index 0000000..2b34291
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassification.java
@@ -0,0 +1,30 @@
+// 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.enums.classification;
+
+public abstract class EnumUnboxerMethodClassification {
+
+  public static UnknownEnumUnboxerMethodClassification unknown() {
+    return UnknownEnumUnboxerMethodClassification.getInstance();
+  }
+
+  public EnumUnboxerMethodClassification fixupAfterRemovingThisParameter() {
+    // Only static methods are currently classified by the enum unboxer.
+    assert isUnknownClassification();
+    return unknown();
+  }
+
+  public boolean isCheckNotNullClassification() {
+    return false;
+  }
+
+  public CheckNotNullEnumUnboxerMethodClassification asCheckNotNullClassification() {
+    return null;
+  }
+
+  public boolean isUnknownClassification() {
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassificationAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassificationAnalysis.java
new file mode 100644
index 0000000..f01abfc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassificationAnalysis.java
@@ -0,0 +1,106 @@
+// 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.enums.classification;
+
+import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
+import static com.android.tools.r8.ir.code.Opcodes.IF;
+import static com.android.tools.r8.ir.code.Opcodes.RETURN;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.If.Type;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Set;
+
+public class EnumUnboxerMethodClassificationAnalysis {
+
+  /**
+   * Simple analysis that classifies the given method using {@link
+   * CheckNotNullEnumUnboxerMethodClassification} if the method is static and has a parameter of
+   * type Object, which has a single if-zero user.
+   */
+  public static EnumUnboxerMethodClassification analyze(
+      AppView<AppInfoWithLiveness> appView,
+      ProgramMethod method,
+      IRCode code,
+      MethodProcessor methodProcessor) {
+    if (!appView.options().enableEnumUnboxing) {
+      // The classification is unused when enum unboxing is disabled.
+      return EnumUnboxerMethodClassification.unknown();
+    }
+
+    if (!method.getAccessFlags().isStatic() || method.getParameters().isEmpty()) {
+      // Not classified for enum unboxing.
+      return EnumUnboxerMethodClassification.unknown();
+    }
+
+    // Look for an argument with a single if-zero user.
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    InstructionIterator entryIterator = code.entryBlock().iterator();
+    for (int index = 0; index < method.getParameters().size(); index++) {
+      Argument argument = entryIterator.next().asArgument();
+      DexType parameter = method.getParameter(index);
+      if (parameter != dexItemFactory.objectType) {
+        continue;
+      }
+
+      if (onlyHasCheckNotNullUsers(argument, methodProcessor)) {
+        return new CheckNotNullEnumUnboxerMethodClassification(index);
+      }
+    }
+
+    return EnumUnboxerMethodClassification.unknown();
+  }
+
+  private static boolean onlyHasCheckNotNullUsers(
+      Argument argument, MethodProcessor methodProcessor) {
+    // Check that the argument has an if-zero user and a return user (optional).
+    Value value = argument.outValue();
+    if (value.hasDebugUsers() || value.hasPhiUsers()) {
+      return false;
+    }
+    Set<Instruction> users = value.aliasedUsers();
+    boolean seenIf = false;
+    for (Instruction user : users) {
+      switch (user.opcode()) {
+        case ASSUME:
+          if (user.outValue().hasDebugUsers() || user.outValue().hasPhiUsers()) {
+            return false;
+          }
+          break;
+        case IF:
+          {
+            If ifUser = user.asIf();
+            if (!ifUser.isZeroTest()
+                || (ifUser.getType() != Type.EQ && ifUser.getType() != Type.NE)) {
+              return false;
+            }
+            seenIf = true;
+            break;
+          }
+        case RETURN:
+          break;
+        default:
+          return false;
+      }
+    }
+
+    // During the primary optimization pass, we require seeing an if instruction (to limit the
+    // amount of method duplication). For monotonicity, we do not require seeing an if instruction
+    // in the subsequent optimization passes, since the if instruction that lead us to return a
+    // CheckNotNullEnumUnboxerMethodClassification may be eliminated by (for example) the call site
+    // optimization.
+    return !methodProcessor.isPrimaryMethodProcessor() || seenIf;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/UnknownEnumUnboxerMethodClassification.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/UnknownEnumUnboxerMethodClassification.java
new file mode 100644
index 0000000..9f51d0a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/UnknownEnumUnboxerMethodClassification.java
@@ -0,0 +1,22 @@
+// 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.enums.classification;
+
+public final class UnknownEnumUnboxerMethodClassification extends EnumUnboxerMethodClassification {
+
+  private static final UnknownEnumUnboxerMethodClassification INSTANCE =
+      new UnknownEnumUnboxerMethodClassification();
+
+  private UnknownEnumUnboxerMethodClassification() {}
+
+  static UnknownEnumUnboxerMethodClassification getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public boolean isUnknownClassification() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
new file mode 100644
index 0000000..c2c5427
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
@@ -0,0 +1,127 @@
+// 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.enums.code;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClasspathMethod;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.IteratorUtils;
+
+/**
+ * A special code object used by enum unboxing that supplies IR from an existing method
+ * checkNotNull() method. Minor rewritings are applied to the code after IR building to account for
+ * enum unboxing.
+ *
+ * <p>Instances of {@link CheckNotZeroCode} are converted to {@link
+ * com.android.tools.r8.graph.CfCode} or {@link com.android.tools.r8.graph.DexCode} immediately, and
+ * thus should never be seen outside of the {@link
+ * com.android.tools.r8.ir.optimize.enums.EnumUnboxer}.
+ */
+public class CheckNotZeroCode extends Code {
+
+  // The checkNotNull() method to build IR from.
+  private final ProgramMethod checkNotNullMethod;
+
+  public CheckNotZeroCode(ProgramMethod checkNotNullMethod) {
+    this.checkNotNullMethod = checkNotNullMethod;
+  }
+
+  @Override
+  public IRCode buildIR(ProgramMethod checkNotZeroMethod, AppView<?> appView, Origin origin) {
+    // Build IR from the checkNotNull() method.
+    IRCode code = checkNotNullMethod.buildIR(appView);
+    InstructionListIterator instructionIterator = code.instructionListIterator();
+
+    // Start iterating at the argument instruction for the checked argument.
+    IteratorUtils.skip(
+        instructionIterator,
+        checkNotNullMethod
+            .getOptimizationInfo()
+            .getEnumUnboxerMethodClassification()
+            .asCheckNotNullClassification()
+            .getArgumentIndex());
+
+    // Rewrite the type of the argument instruction to int.
+    Argument argument = instructionIterator.next().asArgument();
+    instructionIterator.replaceCurrentInstruction(
+        Argument.builder()
+            .setFreshOutValue(code, TypeElement.getInt())
+            .setIndex(argument.getIndex())
+            .build());
+
+    // Remove any assume instructions linked to the argument and replace all returns by return-void.
+    while (instructionIterator.hasNext()) {
+      Instruction instruction = instructionIterator.next();
+      if (instruction.isAssume()) {
+        instruction.outValue().replaceUsers(instruction.getFirstOperand());
+        instructionIterator.removeOrReplaceByDebugLocalRead();
+      } else if (instruction.isReturn() && instruction.asReturn().hasReturnValue()) {
+        instructionIterator.replaceCurrentInstruction(new Return());
+      }
+    }
+
+    // Transfer the IR to the given checkNotZero() method.
+    return new IRCode(
+        appView.options(),
+        checkNotZeroMethod,
+        code.getBlocks(),
+        code.valueNumberGenerator,
+        code.basicBlockNumberGenerator,
+        code.metadata(),
+        checkNotZeroMethod.getOrigin());
+  }
+
+  @Override
+  protected boolean computeEquals(Object other) {
+    throw new Unreachable();
+  }
+
+  @Override
+  protected int computeHashCode() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public int estimatedDexCodeSizeUpperBoundInBytes() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public boolean isEmptyVoidMethod() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public String toString() {
+    return "CheckNotZeroCode(" + checkNotNullMethod.toSourceString() + ")";
+  }
+
+  @Override
+  public String toString(DexEncodedMethod method, ClassNameMapper naming) {
+    return toString();
+  }
+}
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 a98bc38..2d8ff77 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
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+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.DefaultInstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
@@ -65,6 +66,11 @@
   }
 
   @Override
+  public EnumUnboxerMethodClassification getEnumUnboxerMethodClassification() {
+    return EnumUnboxerMethodClassification.unknown();
+  }
+
+  @Override
   public TypeElement getDynamicUpperBoundType() {
     return UNKNOWN_TYPE;
   }
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 a25eabb..4de895c 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
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+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.InternalOptions;
@@ -36,6 +37,8 @@
 
   public abstract ClassInlinerMethodConstraint getClassInlinerMethodConstraint();
 
+  public abstract EnumUnboxerMethodClassification getEnumUnboxerMethodClassification();
+
   public abstract TypeElement getDynamicUpperBoundType();
 
   public final TypeElement getDynamicUpperBoundTypeOrElse(TypeElement orElse) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 15afa8c..ccb3076 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -83,9 +83,12 @@
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.DynamicTypeOptimization;
 import com.android.tools.r8.ir.optimize.classinliner.analysis.ClassInlinerMethodConstraintAnalysis;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassificationAnalysis;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeAnalyzer;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
@@ -129,6 +132,7 @@
       OptimizationFeedback feedback,
       DynamicTypeOptimization dynamicTypeOptimization,
       InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos,
+      MethodProcessor methodProcessor,
       Timing timing) {
     DexEncodedMethod definition = method.getDefinition();
     identifyBridgeInfo(definition, code, feedback, timing);
@@ -136,10 +140,15 @@
     if (options.enableInlining) {
       identifyInvokeSemanticsForInlining(definition, code, feedback, timing);
     }
-    computeClassInlinerMethodConstraint(method, code, feedback, timing);
+    if (options.enableClassInlining) {
+      computeClassInlinerMethodConstraint(method, code, feedback, timing);
+    }
+    computeEnumUnboxerMethodClassification(method, code, feedback, methodProcessor, timing);
     computeSimpleInliningConstraint(method, code, feedback, timing);
     computeDynamicReturnType(dynamicTypeOptimization, feedback, definition, code, timing);
-    computeInitializedClassesOnNormalExit(feedback, definition, code, timing);
+    if (options.enableInitializedClassesAnalysis) {
+      computeInitializedClassesOnNormalExit(feedback, definition, code, timing);
+    }
     computeInstanceInitializerInfo(
         definition, code, feedback, instanceFieldInitializationInfos, timing);
     computeMayHaveSideEffects(feedback, definition, code, timing);
@@ -783,6 +792,27 @@
     feedback.setClassInlinerMethodConstraint(method, classInlinerMethodConstraint);
   }
 
+  private void computeEnumUnboxerMethodClassification(
+      ProgramMethod method,
+      IRCode code,
+      OptimizationFeedback feedback,
+      MethodProcessor methodProcessor,
+      Timing timing) {
+    timing.begin("Compute enum unboxer method classification");
+    computeEnumUnboxerMethodClassification(method, code, feedback, methodProcessor);
+    timing.end();
+  }
+
+  private void computeEnumUnboxerMethodClassification(
+      ProgramMethod method,
+      IRCode code,
+      OptimizationFeedback feedback,
+      MethodProcessor methodProcessor) {
+    EnumUnboxerMethodClassification enumUnboxerMethodClassification =
+        EnumUnboxerMethodClassificationAnalysis.analyze(appView, method, code, methodProcessor);
+    feedback.setEnumUnboxerMethodClassification(method, enumUnboxerMethodClassification);
+  }
+
   private void computeSimpleInliningConstraint(
       ProgramMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
     if (appView.options().enableSimpleInliningConstraints) {
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 7a0dae1..348312f 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
@@ -20,6 +20,7 @@
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+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.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
@@ -41,6 +42,8 @@
       DefaultMethodOptimizationInfo.UNKNOWN_ABSTRACT_RETURN_VALUE;
   private ClassInlinerMethodConstraint classInlinerConstraint =
       ClassInlinerMethodConstraint.alwaysFalse();
+  private EnumUnboxerMethodClassification enumUnboxerMethodClassification =
+      EnumUnboxerMethodClassification.unknown();
   private TypeElement returnsObjectWithUpperBoundType = DefaultMethodOptimizationInfo.UNKNOWN_TYPE;
   private ClassTypeElement returnsObjectWithLowerBoundType =
       DefaultMethodOptimizationInfo.UNKNOWN_CLASS_TYPE;
@@ -154,6 +157,7 @@
     nonNullParamOrThrow = template.nonNullParamOrThrow;
     nonNullParamOnNormalExits = template.nonNullParamOnNormalExits;
     classInlinerConstraint = template.classInlinerConstraint;
+    enumUnboxerMethodClassification = template.enumUnboxerMethodClassification;
     apiReferenceLevel = template.apiReferenceLevel;
   }
 
@@ -246,6 +250,19 @@
   }
 
   @Override
+  public EnumUnboxerMethodClassification getEnumUnboxerMethodClassification() {
+    return enumUnboxerMethodClassification;
+  }
+
+  void setEnumUnboxerMethodClassification(
+      EnumUnboxerMethodClassification enumUnboxerMethodClassification) {
+    // Check monotonicity.
+    assert !this.enumUnboxerMethodClassification.isCheckNotNullClassification()
+        || enumUnboxerMethodClassification.isCheckNotNullClassification();
+    this.enumUnboxerMethodClassification = enumUnboxerMethodClassification;
+  }
+
+  @Override
   public TypeElement getDynamicUpperBoundType() {
     return returnsObjectWithUpperBoundType;
   }
@@ -555,6 +572,8 @@
   public void adjustOptimizationInfoAfterRemovingThisParameter(
       AppView<AppInfoWithLiveness> appView) {
     classInlinerConstraint = classInlinerConstraint.fixupAfterRemovingThisParameter();
+    enumUnboxerMethodClassification =
+        enumUnboxerMethodClassification.fixupAfterRemovingThisParameter();
     simpleInliningConstraint =
         simpleInliningConstraint.fixupAfterRemovingThisParameter(
             appView.simpleInliningConstraintFactory());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
index 89674f5..b38f436 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
@@ -36,6 +36,10 @@
     }
   }
 
+  public static OptimizationFeedbackSimple getSimple() {
+    return OptimizationFeedbackSimple.getInstance();
+  }
+
   public void fixupOptimizationInfos(
       AppView<?> appView, ExecutorService executorService, OptimizationInfoFixer fixer)
       throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index dd781e8..c9390d7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+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.InstanceInitializerInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -264,6 +265,13 @@
   }
 
   @Override
+  public synchronized void setEnumUnboxerMethodClassification(
+      ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification) {
+    getMethodOptimizationInfoForUpdating(method)
+        .setEnumUnboxerMethodClassification(enumUnboxerMethodClassification);
+  }
+
+  @Override
   public synchronized void setInstanceInitializerInfoCollection(
       DexEncodedMethod method,
       InstanceInitializerInfoCollection instanceInitializerInfoCollection) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index e862824..623d8d2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+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.InstanceInitializerInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -117,6 +118,10 @@
       ProgramMethod method, ClassInlinerMethodConstraint classInlinerConstraint) {}
 
   @Override
+  public void setEnumUnboxerMethodClassification(
+      ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification) {}
+
+  @Override
   public void setInstanceInitializerInfoCollection(
       DexEncodedMethod method,
       InstanceInitializerInfoCollection instanceInitializerInfoCollection) {}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 5c39d40..b248a6d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+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.InstanceInitializerInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -169,6 +170,12 @@
   }
 
   @Override
+  public void setEnumUnboxerMethodClassification(
+      ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification) {
+    // Ignored.
+  }
+
+  @Override
   public void setInstanceInitializerInfoCollection(
       DexEncodedMethod method,
       InstanceInitializerInfoCollection instanceInitializerInfoCollection) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ByteMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ByteMethodOptimizer.java
new file mode 100644
index 0000000..5fb3d05
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ByteMethodOptimizer.java
@@ -0,0 +1,58 @@
+// 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.library;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import java.util.Set;
+
+public class ByteMethodOptimizer extends StatelessLibraryMethodModelCollection {
+
+  private final AppView<?> appView;
+  private final DexItemFactory dexItemFactory;
+
+  ByteMethodOptimizer(AppView<?> appView) {
+    this.appView = appView;
+    this.dexItemFactory = appView.dexItemFactory();
+  }
+
+  @Override
+  public DexType getType() {
+    return dexItemFactory.boxedByteType;
+  }
+
+  @Override
+  public void optimize(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      DexClassAndMethod singleTarget,
+      Set<Value> affectedValues) {
+    if (singleTarget.getReference() == dexItemFactory.byteMembers.byteValue) {
+      optimizeByteValue(instructionIterator, invoke);
+    }
+  }
+
+  private void optimizeByteValue(
+      InstructionListIterator instructionIterator, InvokeMethod byteValueInvoke) {
+    // Optimize Byte.valueOf(b).byteValue() into b.
+    Value argument = byteValueInvoke.getFirstArgument().getAliasedValue();
+    if (!argument.isPhi()) {
+      Instruction definition = argument.getDefinition();
+      if (definition.isInvokeStatic()
+          && definition.asInvokeStatic().getInvokedMethod() == dexItemFactory.byteMembers.valueOf) {
+        byteValueInvoke.outValue().replaceUsers(definition.asInvokeStatic().getFirstArgument());
+        instructionIterator.removeOrReplaceByDebugLocalRead();
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
index b7bc845..1ce2493 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
@@ -41,6 +41,7 @@
   public LibraryMemberOptimizer(AppView<?> appView) {
     this.appView = appView;
     register(new BooleanMethodOptimizer(appView));
+    register(new ByteMethodOptimizer(appView));
     register(new ObjectMethodOptimizer(appView));
     register(new ObjectsMethodOptimizer(appView));
     register(new StringBuilderMethodOptimizer(appView));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
index 0e8d33c..ab74fb6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
@@ -40,6 +40,9 @@
     ImmutableMap.Builder<DexMethod, BiPredicate<DexMethod, List<Value>>> builder =
         ImmutableMap.<DexMethod, BiPredicate<DexMethod, List<Value>>>builder()
             .put(
+                dexItemFactory.byteMembers.byteValue,
+                (method, arguments) -> arguments.get(0).isNeverNull())
+            .put(
                 dexItemFactory.objectsMethods.toStringWithObject,
                 (method, arguments) ->
                     !JavaLangObjectsSideEffectCollection.toStringMayHaveSideEffects(
@@ -68,6 +71,7 @@
     return ImmutableSet.<DexMethod>builder()
         .add(dexItemFactory.booleanMembers.toString)
         .add(dexItemFactory.byteMembers.toString)
+        .add(dexItemFactory.byteMembers.valueOf)
         .add(dexItemFactory.classMethods.desiredAssertionStatus)
         .add(dexItemFactory.charMembers.toString)
         .add(dexItemFactory.doubleMembers.toString)
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java
index c5f6ddc..efc5228 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java
@@ -22,7 +22,7 @@
 
   @Override
   protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
-    if (methodMap.get(originalMethod) == newMethod) {
+    if (methodMap.apply(originalMethod) == newMethod) {
       assert type == Type.VIRTUAL || type == Type.DIRECT;
       return Type.STATIC;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 179dffd..e98adec 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -2574,7 +2574,7 @@
             addLiveRange(
                 definition,
                 block,
-                instruction.getNumber() + INSTRUCTION_NUMBER_DELTA,
+                instruction.getNumber() + INSTRUCTION_NUMBER_DELTA - 1,
                 liveIntervals,
                 options);
             assert !options.isGeneratingClassFiles() || instruction.isArgument()
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index 9cb4c09..0c5705c 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -416,6 +416,9 @@
       register = NO_REGISTER;
       return this;
     }
+
+    assert !getValue().isDefinedByInstructionSatisfying(Instruction::isInitClass);
+
     start = toGapPosition(start);
     LiveIntervals splitChild = new LiveIntervals(splitParent);
     splitParent.splitChildren.add(splitChild);
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 db93ced..5680337 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,6 +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.utils.BooleanUtils;
 import com.android.tools.r8.utils.collections.ImmutableDeque;
 import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
 import java.util.ArrayList;
@@ -62,22 +64,25 @@
   public static class APIConverterVivifiedWrapperCfCodeProvider
       extends DesugaredLibraryAPIConversionCfCodeProvider {
 
-    DexField wrapperField;
-    DexMethod forwardMethod;
-    DesugaredLibraryAPIConverter converter;
-    boolean itfCall;
+    private final DexField wrapperField;
+    private final DexMethod forwardMethod;
+    private final DesugaredLibraryAPIConverter converter;
+    private final boolean itfCall;
+    private final DesugaredLibraryAPIConverterEventConsumer eventConsumer;
 
     public APIConverterVivifiedWrapperCfCodeProvider(
         AppView<?> appView,
         DexMethod forwardMethod,
         DexField wrapperField,
         DesugaredLibraryAPIConverter converter,
-        boolean itfCall) {
+        boolean itfCall,
+        DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
       super(appView, wrapperField.holder);
       this.forwardMethod = forwardMethod;
       this.wrapperField = wrapperField;
       this.converter = converter;
       this.itfCall = itfCall;
+      this.eventConsumer = eventConsumer;
     }
 
     @Override
@@ -98,7 +103,8 @@
           instructions.add(
               new CfInvoke(
                   Opcodes.INVOKESTATIC,
-                  converter.ensureConversionMethod(param, param, vivifiedTypeFor(param)),
+                  converter.ensureConversionMethod(
+                      param, param, vivifiedTypeFor(param), eventConsumer),
                   false));
           newParameters[index - 1] = vivifiedTypeFor(param);
         }
@@ -130,7 +136,7 @@
             new CfInvoke(
                 Opcodes.INVOKESTATIC,
                 converter.ensureConversionMethod(
-                    returnType, vivifiedTypeFor(returnType), returnType),
+                    returnType, vivifiedTypeFor(returnType), returnType, eventConsumer),
                 false));
       }
       if (returnType == factory.voidType) {
@@ -149,19 +155,22 @@
     DexMethod forwardMethod;
     DesugaredLibraryAPIConverter converter;
     boolean itfCall;
+    private final DesugaredLibraryAPIConverterEventConsumer eventConsumer;
 
     public APIConverterWrapperCfCodeProvider(
         AppView<?> appView,
         DexMethod forwardMethod,
         DexField wrapperField,
         DesugaredLibraryAPIConverter converter,
-        boolean itfCall) {
+        boolean itfCall,
+        DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
       //  Var wrapperField is null if should forward to receiver.
       super(appView, wrapperField == null ? forwardMethod.holder : wrapperField.holder);
       this.forwardMethod = forwardMethod;
       this.wrapperField = wrapperField;
       this.converter = converter;
       this.itfCall = itfCall;
+      this.eventConsumer = eventConsumer;
     }
 
     @Override
@@ -185,7 +194,8 @@
           instructions.add(
               new CfInvoke(
                   Opcodes.INVOKESTATIC,
-                  converter.ensureConversionMethod(param, vivifiedTypeFor(param), param),
+                  converter.ensureConversionMethod(
+                      param, vivifiedTypeFor(param), param, eventConsumer),
                   false));
         }
         if (param == factory.longType || param == factory.doubleType) {
@@ -206,7 +216,7 @@
             new CfInvoke(
                 Opcodes.INVOKESTATIC,
                 converter.ensureConversionMethod(
-                    returnType, returnType, vivifiedTypeFor(returnType)),
+                    returnType, returnType, vivifiedTypeFor(returnType), eventConsumer),
                 false));
         returnType = vivifiedTypeFor(returnType);
       }
@@ -282,6 +292,105 @@
     }
   }
 
+  public static class APIConversionCfCodeProvider extends SyntheticCfCodeProvider {
+
+    private final CfInvoke initialInvoke;
+    private final DexMethod returnConversion;
+    private final DexMethod[] parameterConversions;
+
+    public APIConversionCfCodeProvider(
+        AppView<?> appView,
+        DexType holder,
+        CfInvoke initialInvoke,
+        DexMethod returnConversion,
+        DexMethod[] parameterConversions) {
+      super(appView, holder);
+      this.initialInvoke = initialInvoke;
+      this.returnConversion = returnConversion;
+      this.parameterConversions = parameterConversions;
+    }
+
+    private DexType invalidType(DexMethod invokedMethod, DexMethod convertedMethod) {
+      if (invokedMethod.getReturnType() != convertedMethod.getReturnType()
+          && returnConversion == null) {
+        return invokedMethod.getReturnType();
+      }
+      for (int i = 0; i < invokedMethod.getArity(); i++) {
+        if (invokedMethod.getParameter(i) != convertedMethod.getParameter(i)
+            && parameterConversions[i] == null) {
+          return invokedMethod.getParameter(i);
+        }
+      }
+      return null;
+    }
+
+    @Override
+    public CfCode generateCfCode() {
+      DexMethod invokedMethod = initialInvoke.getMethod();
+      DexMethod convertedMethod =
+          DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
+              invokedMethod, invokedMethod.holder, appView);
+
+      DexType invalidType = invalidType(invokedMethod, convertedMethod);
+      if (invalidType != null) {
+        // This is true if the API conversion requires to convert a type which is impossible to
+        // convert: no custom conversion and no wrapping possible. This is extremely rare and
+        // should happen only with broken desugared library set-ups. A warning has already been
+        // reported at this point.
+        DexString message =
+            appView
+                .dexItemFactory()
+                .createString(
+                    "The method "
+                        + invokedMethod
+                        + " requires API conversion, but conversion was impossible because of the"
+                        + " non convertible type "
+                        + invalidType
+                        + ".");
+        return new APIConverterThrowRuntimeExceptionCfCodeProvider(
+                appView, message, invokedMethod.getHolderType())
+            .generateCfCode();
+      }
+
+      List<CfInstruction> instructions = new ArrayList<>();
+
+      boolean isStatic = initialInvoke.getOpcode() == Opcodes.INVOKESTATIC;
+      if (!isStatic) {
+        instructions.add(new CfLoad(ValueType.fromDexType(invokedMethod.holder), 0));
+      }
+      int receiverShift = BooleanUtils.intValue(!isStatic);
+      int stackIndex = 0;
+      for (int i = 0; i < invokedMethod.getArity(); i++) {
+        DexType param = invokedMethod.getParameter(i);
+        instructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex + receiverShift));
+        if (parameterConversions[i] != null) {
+          instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, parameterConversions[i], false));
+        }
+        if (param == appView.dexItemFactory().longType
+            || param == appView.dexItemFactory().doubleType) {
+          stackIndex++;
+        }
+        stackIndex++;
+      }
+
+      // Actual call to converted value.
+      instructions.add(
+          new CfInvoke(initialInvoke.getOpcode(), convertedMethod, initialInvoke.isInterface()));
+
+      // Return conversion.
+      if (returnConversion != null) {
+        instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, returnConversion, false));
+      }
+
+      if (invokedMethod.getReturnType().isVoidType()) {
+        instructions.add(new CfReturnVoid());
+      } else {
+        instructions.add(new CfReturn(ValueType.fromDexType(invokedMethod.getReturnType())));
+      }
+      return standardCfCodeFromInstructions(instructions);
+    }
+  }
+
   public static class APIConverterConstructorCfCodeProvider extends SyntheticCfCodeProvider {
 
     DexField wrapperField;
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java
index 7f26a19..e1ba3b2 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java
@@ -36,12 +36,13 @@
   private final List<Pair<DexType, DexMethod>> extraDispatchCases;
 
   public EmulateInterfaceSyntheticCfCodeProvider(
+      DexType holder,
       DexType interfaceType,
       DexMethod companionMethod,
       DexMethod libraryMethod,
       List<Pair<DexType, DexMethod>> extraDispatchCases,
       AppView<?> appView) {
-    super(appView, interfaceType);
+    super(appView, holder);
     this.interfaceType = interfaceType;
     this.companionMethod = companionMethod;
     this.libraryMethod = libraryMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
index d21c6d9..cb5de3e 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldMappingData;
 import com.android.tools.r8.utils.collections.ImmutableDeque;
 import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
@@ -67,14 +68,19 @@
     private final AbstractValue nullValue;
 
     public EnumUnboxingInstanceFieldCfCodeProvider(
+        AppView<?> appView, DexType holder, EnumData data, DexField field) {
+      this(appView, holder, data, field, null);
+    }
+
+    public EnumUnboxingInstanceFieldCfCodeProvider(
         AppView<?> appView,
         DexType holder,
-        DexType returnType,
-        EnumInstanceFieldMappingData fieldDataMap,
+        EnumData data,
+        DexField field,
         AbstractValue nullValue) {
       super(appView, holder);
-      this.returnType = returnType;
-      this.fieldDataMap = fieldDataMap;
+      this.returnType = field.getType();
+      this.fieldDataMap = data.getInstanceFieldData(field).asEnumFieldMappingData();
       this.nullValue = nullValue;
     }
 
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index 16b6508..b719c89 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.GenericSignaturePartialTypeArgumentApplier;
 import com.android.tools.r8.graph.GenericSignatureTypeRewriter;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -50,13 +51,15 @@
     // ClassNameMinifier.
     Predicate<DexType> wasPruned =
         appView.hasLiveness() ? appView.withLiveness().appInfo()::wasPruned : alwaysFalse();
+    Predicate<DexType> hasGenericTypeVariables =
+        type -> GenericSignatureContextBuilder.hasGenericTypeVariables(appView, type, wasPruned);
     BiPredicate<DexType, DexType> hasPrunedRelationship =
         (enclosing, enclosed) ->
             contextBuilder.hasPrunedRelationship(appView, enclosing, enclosed, wasPruned);
-    Predicate<DexType> hasGenericTypeVariables =
-        type -> contextBuilder.hasGenericTypeVariables(appView, type, wasPruned);
     ThreadUtils.processItems(
-        classes,
+        // Final merging of classes can introduce pruned types that still exists in classes, we
+        // therefore prune them from work here.
+        IterableUtils.filter(classes, clazz -> !wasPruned.test(clazz.getType())),
         clazz -> {
           GenericSignaturePartialTypeArgumentApplier classArgumentApplier =
               contextBuilder != null
@@ -68,7 +71,7 @@
                       hasGenericTypeVariables)
                   : null;
           GenericSignatureTypeRewriter genericSignatureTypeRewriter =
-              new GenericSignatureTypeRewriter(appView, clazz);
+              new GenericSignatureTypeRewriter(appView, clazz, hasGenericTypeVariables);
           clazz.setClassSignature(
               genericSignatureTypeRewriter.rewrite(
                   classArgumentApplier != null
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationMatchResult.java b/src/main/java/com/android/tools/r8/shaking/AnnotationMatchResult.java
index 5b97077..c83cbfc 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationMatchResult.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationMatchResult.java
@@ -5,7 +5,10 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
+import com.android.tools.r8.graph.ProgramDefinition;
 import java.util.List;
+import java.util.Objects;
 
 public abstract class AnnotationMatchResult {
 
@@ -31,13 +34,13 @@
 
   static class ConcreteAnnotationMatchResult extends AnnotationMatchResult {
 
-    private final List<DexAnnotation> matchedAnnotations;
+    private final List<MatchedAnnotation> matchedAnnotations;
 
-    public ConcreteAnnotationMatchResult(List<DexAnnotation> matchedAnnotations) {
+    public ConcreteAnnotationMatchResult(List<MatchedAnnotation> matchedAnnotations) {
       this.matchedAnnotations = matchedAnnotations;
     }
 
-    public List<DexAnnotation> getMatchedAnnotations() {
+    public List<MatchedAnnotation> getMatchedAnnotations() {
       return matchedAnnotations;
     }
 
@@ -51,4 +54,49 @@
       return this;
     }
   }
+
+  static class MatchedAnnotation {
+
+    private final ProgramDefinition annotatedItem;
+    private final DexAnnotation annotation;
+    private final AnnotatedKind annotatedKind;
+
+    MatchedAnnotation(
+        ProgramDefinition annotatedItem, DexAnnotation annotation, AnnotatedKind annotatedKind) {
+      this.annotatedItem = annotatedItem;
+      this.annotation = annotation;
+      this.annotatedKind = annotatedKind;
+    }
+
+    public ProgramDefinition getAnnotatedItem() {
+      return annotatedItem;
+    }
+
+    public DexAnnotation getAnnotation() {
+      return annotation;
+    }
+
+    public AnnotatedKind getAnnotatedKind() {
+      return annotatedKind;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (obj == this) {
+        return true;
+      }
+      if (obj instanceof MatchedAnnotation) {
+        MatchedAnnotation annotationToRetain = (MatchedAnnotation) obj;
+        return annotatedItem == annotationToRetain.annotatedItem
+            && annotation == annotationToRetain.annotation
+            && annotatedKind == annotationToRetain.annotatedKind;
+      }
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(annotatedItem, annotation, annotatedKind);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 6a4089d..ace24131 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.graph.ProgramMember;
 import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
 import com.android.tools.r8.kotlin.KotlinPropertyInfo;
+import com.android.tools.r8.shaking.Enqueuer.Mode;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.ImmutableList;
@@ -33,6 +34,7 @@
 public class AnnotationRemover {
 
   private final AppView<AppInfoWithLiveness> appView;
+  private final Mode mode;
   private final InternalOptions options;
   private final Set<DexAnnotation> annotationsToRetain;
   private final ProguardKeepAttributes keep;
@@ -41,16 +43,18 @@
   private AnnotationRemover(
       AppView<AppInfoWithLiveness> appView,
       Set<DexAnnotation> annotationsToRetain,
+      Mode mode,
       Set<DexType> removedClasses) {
     this.appView = appView;
+    this.mode = mode;
     this.options = appView.options();
     this.annotationsToRetain = annotationsToRetain;
     this.keep = appView.options().getProguardConfiguration().getKeepAttributes();
     this.removedClasses = removedClasses;
   }
 
-  public static Builder builder() {
-    return new Builder();
+  public static Builder builder(Mode mode) {
+    return new Builder(mode);
   }
 
   /** Used to filter annotations on classes, methods and fields. */
@@ -263,17 +267,24 @@
   }
 
   private void removeAnnotations(ProgramDefinition definition, KeepInfo<?, ?> keepInfo) {
+    assert mode.isInitialTreeShaking() || annotationsToRetain.isEmpty();
     boolean isAnnotation =
         definition.isProgramClass() && definition.asProgramClass().isAnnotation();
-    if ((options.isForceProguardCompatibilityEnabled() || keepInfo.isPinned())) {
-      definition.rewriteAllAnnotations(
-          (annotation, kind) -> rewriteAnnotation(definition, annotation, kind));
-    } else if (!isAnnotation) {
-      definition.clearAllAnnotations();
+    if (keepInfo.isAnnotationRemovalAllowed(options)) {
+      if (isAnnotation) {
+        definition.rewriteAllAnnotations(
+            (annotation, isParameterAnnotation) ->
+                shouldRetainAnnotationOnAnnotationClass(annotation) ? annotation : null);
+      } else if (mode.isInitialTreeShaking()) {
+        definition.rewriteAllAnnotations(
+            (annotation, isParameterAnnotation) ->
+                annotationsToRetain.contains(annotation) ? annotation : null);
+      } else {
+        definition.clearAllAnnotations();
+      }
     } else {
       definition.rewriteAllAnnotations(
-          (annotation, isParameterAnnotation) ->
-              shouldRetainAnnotationOnAnnotationClass(annotation) ? annotation : null);
+          (annotation, kind) -> rewriteAnnotation(definition, annotation, kind));
     }
   }
 
@@ -284,7 +295,7 @@
     if (DexAnnotation.isJavaLangRetentionAnnotation(annotation, appView.dexItemFactory())) {
       return shouldRetainRetentionAnnotationOnAnnotationClass(annotation);
     }
-    return false;
+    return annotationsToRetain.contains(annotation);
   }
 
   private boolean shouldRetainAnnotationDefaultAnnotationOnAnnotationClass(
@@ -387,13 +398,23 @@
      */
     private final Set<DexAnnotation> annotationsToRetain = Sets.newIdentityHashSet();
 
+    private final Mode mode;
+
+    Builder(Mode mode) {
+      this.mode = mode;
+    }
+
+    public boolean isRetainedForFinalTreeShaking(DexAnnotation annotation) {
+      return annotationsToRetain.contains(annotation);
+    }
+
     public void retainAnnotation(DexAnnotation annotation) {
       annotationsToRetain.add(annotation);
     }
 
     public AnnotationRemover build(
         AppView<AppInfoWithLiveness> appView, Set<DexType> removedClasses) {
-      return new AnnotationRemover(appView, annotationsToRetain, removedClasses);
+      return new AnnotationRemover(appView, annotationsToRetain, mode, removedClasses);
     }
   }
 }
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 2ff9008..68b7e13 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -1050,7 +1050,8 @@
             .collect(Collectors.toList()));
 
     CommittedItems committedItems = getSyntheticItems().commitRewrittenWithLens(application, lens);
-    DexDefinitionSupplier definitionSupplier = application.getDefinitionsSupplier(committedItems);
+    DexDefinitionSupplier definitionSupplier =
+        committedItems.getApplication().getDefinitionsSupplier(committedItems);
     return new AppInfoWithLiveness(
         committedItems,
         getClassToFeatureSplitMap().rewrittenWithLens(lens),
@@ -1472,4 +1473,13 @@
   public boolean isNoVerticalClassMergingOfType(DexType type) {
     return noClassMerging.contains(type) || noVerticalClassMerging.contains(type);
   }
+
+  public boolean verifyNoIteratingOverPrunedClasses() {
+    classes()
+        .forEach(
+            clazz -> {
+              assert !wasPruned(clazz.type) : clazz.type + " was not pruned";
+            });
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 9537206..02aea63 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -10,8 +10,8 @@
 import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.getEmulateLibraryInterfaceClassType;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentifier;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
-import static com.android.tools.r8.shaking.AnnotationRemover.shouldKeepAnnotation;
 import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 import static java.util.Collections.emptySet;
 
 import com.android.tools.r8.Diagnostic;
@@ -110,15 +110,17 @@
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
 import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringTypeLookupResult;
+import com.android.tools.r8.shaking.AnnotationMatchResult.MatchedAnnotation;
 import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
+import com.android.tools.r8.shaking.EnqueuerEvent.ClassEnqueuerEvent;
+import com.android.tools.r8.shaking.EnqueuerEvent.InstantiatedClassEnqueuerEvent;
+import com.android.tools.r8.shaking.EnqueuerEvent.LiveClassEnqueuerEvent;
 import com.android.tools.r8.shaking.EnqueuerWorklist.EnqueuerAction;
 import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
-import com.android.tools.r8.shaking.KeepInfo.Joiner;
 import com.android.tools.r8.shaking.KeepInfoCollection.MutableKeepInfoCollection;
 import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSet;
 import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder;
 import com.android.tools.r8.shaking.RootSetUtils.ItemsWithRules;
-import com.android.tools.r8.shaking.RootSetUtils.MutableItemsWithRules;
 import com.android.tools.r8.shaking.RootSetUtils.RootSet;
 import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
 import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
@@ -135,7 +137,9 @@
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.Visibility;
 import com.android.tools.r8.utils.WorkList;
+import com.android.tools.r8.utils.collections.ProgramFieldMap;
 import com.android.tools.r8.utils.collections.ProgramFieldSet;
+import com.android.tools.r8.utils.collections.ProgramMethodMap;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableSet;
@@ -283,6 +287,10 @@
    */
   private final SetWithReportedReason<DexProgramClass> liveTypes = new SetWithReportedReason<>();
 
+  /** Set of interfaces that have been transitioned to being instantiated indirectly. */
+  private final Set<DexProgramClass> interfacesTransitionedToInstantiated =
+      Sets.newIdentityHashSet();
+
   /** Set of classes whose initializer may execute. */
   private final SetWithReportedReason<DexProgramClass> initializedClasses =
       new SetWithReportedReason<>();
@@ -378,6 +386,30 @@
   private final MutableKeepInfoCollection keepInfo = new MutableKeepInfoCollection();
 
   /**
+   * The minimum keep info for classes, fields, and methods. The minimum keep info for a program
+   * item should be applied to the {@link KeepInfo} of that item when the item becomes live.
+   */
+  private final Map<DexProgramClass, KeepClassInfo.Joiner> minimumKeepClassInfo =
+      new IdentityHashMap<>();
+
+  private final ProgramFieldMap<KeepFieldInfo.Joiner> minimumKeepFieldInfo =
+      ProgramFieldMap.create();
+  private final ProgramMethodMap<KeepMethodInfo.Joiner> minimumKeepMethodInfo =
+      ProgramMethodMap.create();
+
+  /**
+   * Conditional minimum keep info for classes, fields, and methods, which should only be applied if
+   * the outermost {@link EnqueuerEvent} is triggered during tracing (e.g., class X becomes live).
+   */
+  private final Map<EnqueuerEvent, Map<DexProgramClass, KeepClassInfo.Joiner>>
+      dependentMinimumKeepClassInfo = new HashMap<>();
+
+  private final Map<EnqueuerEvent, ProgramFieldMap<KeepFieldInfo.Joiner>>
+      dependentMinimumKeepFieldInfo = new HashMap<>();
+  private final Map<EnqueuerEvent, ProgramMethodMap<KeepMethodInfo.Joiner>>
+      dependentMinimumKeepMethodInfo = new HashMap<>();
+
+  /**
    * A set of seen const-class references that serve as an initial lock-candidate set and will
    * prevent class merging.
    */
@@ -863,10 +895,6 @@
         method, graphReporter.reportKeepMethod(precondition, rules, method.getDefinition()));
   }
 
-  private void enqueueRootItem(ProgramDefinition item, Set<ProguardKeepRuleBase> rules) {
-    internalEnqueueRootItem(item, rules, null);
-  }
-
   private void internalEnqueueRootItem(
       ProgramDefinition item, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
     if (item.isProgramClass()) {
@@ -1830,7 +1858,9 @@
       enqueueFirstNonSerializableClassInitializer(clazz, reason);
     }
 
-    checkDefinitionForSoftPinning(clazz);
+    // Update keep info.
+    applyMinimumKeepInfo(clazz);
+    applyMinimumKeepInfoDependentOn(new LiveClassEnqueuerEvent(clazz));
 
     processAnnotations(clazz);
 
@@ -1968,13 +1998,13 @@
     }
   }
 
-  private void processAnnotation(
+  void processAnnotation(
       ProgramDefinition annotatedItem, DexAnnotation annotation, AnnotatedKind kind) {
     DexType type = annotation.getAnnotationType();
     DexClass clazz = definitionFor(type, annotatedItem);
     boolean annotationTypeIsLibraryClass = clazz == null || clazz.isNotProgramClass();
     boolean isLive = annotationTypeIsLibraryClass || liveTypes.contains(clazz.asProgramClass());
-    if (!shouldKeepAnnotation(appView, annotatedItem, annotation, isLive, kind)) {
+    if (!shouldKeepAnnotation(annotatedItem, annotation, kind, isLive)) {
       // Remember this annotation for later.
       if (!annotationTypeIsLibraryClass) {
         Map<DexType, Map<DexAnnotation, List<ProgramDefinition>>> deferredAnnotations =
@@ -1998,6 +2028,20 @@
     annotation.annotation.collectIndexedItems(referenceMarker);
   }
 
+  private boolean shouldKeepAnnotation(
+      ProgramDefinition annotatedItem,
+      DexAnnotation annotation,
+      AnnotatedKind annotatedKind,
+      boolean isLive) {
+    if (annotationRemoverBuilder != null
+        && annotationRemoverBuilder.isRetainedForFinalTreeShaking(annotation)) {
+      assert mode.isInitialTreeShaking();
+      return true;
+    }
+    return AnnotationRemover.shouldKeepAnnotation(
+        appView, annotatedItem, annotation, isLive, annotatedKind);
+  }
+
   private FieldResolutionResult resolveField(DexField field, ProgramDefinition context) {
     // Record the references in case they are not program types.
     FieldResolutionResult resolutionResult = appInfo.resolveField(field);
@@ -2608,16 +2652,46 @@
   }
 
   private void transitionDependentItemsForInstantiatedItem(DexProgramClass clazz) {
-    do {
-      // Handle keep rules that are dependent on the class being instantiated.
-      rootSet.forEachDependentNonStaticMember(clazz, appView, this::enqueueDependentMember);
+    WorkList<DexProgramClass> interfacesToTransition =
+        WorkList.newWorkList(interfacesTransitionedToInstantiated);
+    if (clazz.getAccessFlags().isInterface()) {
+      interfacesToTransition.addIfNotSeen(clazz);
+    } else {
+      do {
+        // Handle keep rules that are dependent on the class being instantiated.
+        rootSet.forEachDependentNonStaticMember(clazz, appView, this::enqueueDependentMember);
+        applyMinimumKeepInfoDependentOn(new InstantiatedClassEnqueuerEvent(clazz));
 
-      // Visit the super type.
-      clazz =
-          clazz.superType != null
-              ? asProgramClassOrNull(appView.definitionFor(clazz.superType))
-              : null;
-    } while (clazz != null && !objectAllocationInfoCollection.isInstantiatedDirectly(clazz));
+        for (DexType interfaceType : clazz.getInterfaces()) {
+          DexProgramClass interfaceClass =
+              asProgramClassOrNull(definitionFor(interfaceType, clazz));
+          if (interfaceClass != null) {
+            interfacesToTransition.addIfNotSeen(interfaceClass);
+          }
+        }
+
+        // Visit the super type.
+        clazz =
+            clazz.superType != null
+                ? asProgramClassOrNull(appView.definitionFor(clazz.superType))
+                : null;
+      } while (clazz != null && !objectAllocationInfoCollection.isInstantiatedDirectly(clazz));
+    }
+
+    while (interfacesToTransition.hasNext()) {
+      DexProgramClass interfaceClass = interfacesToTransition.next();
+      rootSet.forEachDependentNonStaticMember(
+          interfaceClass, appView, this::enqueueDependentMember);
+      applyMinimumKeepInfoDependentOn(new InstantiatedClassEnqueuerEvent(interfaceClass));
+
+      for (DexType indirectInterfaceType : interfaceClass.getInterfaces()) {
+        DexProgramClass indirectInterfaceClass =
+            asProgramClassOrNull(definitionFor(indirectInterfaceType, interfaceClass));
+        if (indirectInterfaceClass != null) {
+          interfacesToTransition.addIfNotSeen(indirectInterfaceClass);
+        }
+      }
+    }
   }
 
   private void transitionUnusedInterfaceToLive(DexProgramClass clazz) {
@@ -2656,11 +2730,12 @@
       traceFieldDefinition(field);
     }
 
+    // Update keep info.
+    applyMinimumKeepInfo(field);
+
     // Add all dependent members to the workqueue.
     enqueueRootItems(rootSet.getDependentItems(field.getDefinition()));
 
-    checkDefinitionForSoftPinning(field);
-
     // Notify analyses.
     analyses.forEach(analysis -> analysis.processNewlyLiveField(field, context));
   }
@@ -2759,6 +2834,27 @@
         : info.isWritten();
   }
 
+  public boolean isPreconditionForMinimumKeepInfoSatisfied(EnqueuerEvent preconditionEvent) {
+    if (preconditionEvent == null) {
+      return true;
+    }
+    if (preconditionEvent.isClassEvent()) {
+      ClassEnqueuerEvent classEvent = preconditionEvent.asClassEvent();
+      DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(classEvent.getType()));
+      if (clazz == null) {
+        return false;
+      }
+      if (preconditionEvent.isLiveClassEvent()) {
+        return liveTypes.contains(clazz);
+      }
+      if (preconditionEvent.isInstantiatedClassEvent()) {
+        return objectAllocationInfoCollection.isInstantiatedDirectlyOrHasInstantiatedSubtype(clazz);
+      }
+    }
+    assert false;
+    return false;
+  }
+
   public boolean isMemberLive(DexEncodedMember<?, ?> member) {
     assert member != null;
     return member.isDexEncodedField()
@@ -2794,7 +2890,7 @@
   }
 
   public void forAllLiveClasses(Consumer<DexProgramClass> consumer) {
-    liveTypes.items.forEach(consumer);
+    liveTypes.getItems().forEach(consumer);
   }
 
   private void markVirtualMethodAsReachable(
@@ -3032,24 +3128,26 @@
     if (appView.options().apiModelingOptions().enableApiCallerIdentification) {
       registerAnalysis(new ApiModelAnalysis(appView, apiReferenceLevelCache));
     }
+
+    // Transfer the minimum keep info from the root set into the Enqueuer state.
+    rootSet.forEachMinimumKeepInfo(
+        appView,
+        this::recordDependentMinimumKeepInfo,
+        this::recordDependentMinimumKeepInfo,
+        this::recordDependentMinimumKeepInfo);
+
     if (mode.isInitialTreeShaking()) {
       // This is simulating the effect of the "root set" applied rules.
       // This is done only in the initial pass, in subsequent passes the "rules" are reapplied
       // by iterating the instances.
-      assert appView.options().isMinificationEnabled() || rootSet.noObfuscation.isEmpty();
-      for (DexReference reference : rootSet.noObfuscation) {
-        keepInfo.evaluateRule(reference, appInfo, Joiner::disallowMinification);
-      }
     } else if (appView.getKeepInfo() != null) {
       appView
           .getKeepInfo()
-          .getRuleInstances()
-          .forEach(
-              (reference, rules) -> {
-                for (Consumer<Joiner<?, ?, ?>> rule : rules) {
-                  keepInfo.evaluateRule(reference, appInfo, rule);
-                }
-              });
+          .forEachRuleInstance(
+              appView,
+              this::applyMinimumKeepInfoWhenLive,
+              this::applyMinimumKeepInfoWhenLive,
+              this::applyMinimumKeepInfoWhenLiveOrTargeted);
     }
     if (appView.options().isShrinking() || appView.options().getProguardConfiguration() == null) {
       enqueueRootItems(rootSet.noShrinking);
@@ -3087,18 +3185,133 @@
     return createEnqueuerResult(appInfo);
   }
 
+  private void applyMinimumKeepInfo(DexProgramClass clazz) {
+    KeepClassInfo.Joiner minimumKeepInfoForClass = minimumKeepClassInfo.remove(clazz);
+    if (minimumKeepInfoForClass != null) {
+      keepInfo.joinClass(clazz, info -> info.merge(minimumKeepInfoForClass));
+    }
+  }
+
+  private void applyMinimumKeepInfoWhenLive(
+      DexProgramClass clazz, KeepClassInfo.Joiner minimumKeepInfo) {
+    if (liveTypes.contains(clazz)) {
+      keepInfo.joinClass(clazz, info -> info.merge(minimumKeepInfo));
+    } else {
+      minimumKeepClassInfo
+          .computeIfAbsent(clazz, ignoreKey(KeepClassInfo::newEmptyJoiner))
+          .merge(minimumKeepInfo);
+    }
+  }
+
+  private void recordDependentMinimumKeepInfo(
+      EnqueuerEvent preconditionEvent,
+      DexProgramClass clazz,
+      KeepClassInfo.Joiner minimumKeepInfo) {
+    if (isPreconditionForMinimumKeepInfoSatisfied(preconditionEvent)) {
+      applyMinimumKeepInfoWhenLive(clazz, minimumKeepInfo);
+    } else {
+      dependentMinimumKeepClassInfo
+          .computeIfAbsent(preconditionEvent, ignoreKey(IdentityHashMap::new))
+          .computeIfAbsent(clazz, ignoreKey(KeepClassInfo::newEmptyJoiner))
+          .merge(minimumKeepInfo);
+    }
+  }
+
+  private void applyMinimumKeepInfo(ProgramField field) {
+    KeepFieldInfo.Joiner minimumKeepInfoForField = minimumKeepFieldInfo.remove(field);
+    if (minimumKeepInfoForField != null) {
+      keepInfo.joinField(field, info -> info.merge(minimumKeepInfoForField));
+    }
+  }
+
+  private void applyMinimumKeepInfoWhenLive(
+      ProgramField field, KeepFieldInfo.Joiner minimumKeepInfo) {
+    if (liveFields.contains(field)) {
+      keepInfo.joinField(field, info -> info.merge(minimumKeepInfo));
+    } else {
+      minimumKeepFieldInfo
+          .computeIfAbsent(field, ignoreKey(KeepFieldInfo::newEmptyJoiner))
+          .merge(minimumKeepInfo);
+    }
+  }
+
+  private void recordDependentMinimumKeepInfo(
+      EnqueuerEvent preconditionEvent, ProgramField field, KeepFieldInfo.Joiner minimumKeepInfo) {
+    if (isPreconditionForMinimumKeepInfoSatisfied(preconditionEvent)) {
+      applyMinimumKeepInfoWhenLive(field, minimumKeepInfo);
+    } else {
+      dependentMinimumKeepFieldInfo
+          .computeIfAbsent(preconditionEvent, ignoreKey(ProgramFieldMap::create))
+          .computeIfAbsent(field, ignoreKey(KeepFieldInfo::newEmptyJoiner))
+          .merge(minimumKeepInfo);
+    }
+  }
+
+  private void applyMinimumKeepInfo(ProgramMethod method) {
+    KeepMethodInfo.Joiner minimumKeepInfoForMethod = minimumKeepMethodInfo.remove(method);
+    if (minimumKeepInfoForMethod != null) {
+      keepInfo.joinMethod(method, info -> info.merge(minimumKeepInfoForMethod));
+    }
+  }
+
+  private void applyMinimumKeepInfoWhenLiveOrTargeted(
+      ProgramMethod method, KeepMethodInfo.Joiner minimumKeepInfo) {
+    if (liveMethods.contains(method) || targetedMethods.contains(method)) {
+      keepInfo.joinMethod(method, info -> info.merge(minimumKeepInfo));
+    } else {
+      minimumKeepMethodInfo
+          .computeIfAbsent(method, ignoreKey(KeepMethodInfo::newEmptyJoiner))
+          .merge(minimumKeepInfo);
+    }
+  }
+
+  private void recordDependentMinimumKeepInfo(
+      EnqueuerEvent preconditionEvent,
+      ProgramMethod method,
+      KeepMethodInfo.Joiner minimumKeepInfo) {
+    if (isPreconditionForMinimumKeepInfoSatisfied(preconditionEvent)) {
+      applyMinimumKeepInfoWhenLiveOrTargeted(method, minimumKeepInfo);
+    } else {
+      dependentMinimumKeepMethodInfo
+          .computeIfAbsent(preconditionEvent, ignoreKey(ProgramMethodMap::create))
+          .computeIfAbsent(method, ignoreKey(KeepMethodInfo::newEmptyJoiner))
+          .merge(minimumKeepInfo);
+    }
+  }
+
+  private void applyMinimumKeepInfoDependentOn(EnqueuerEvent precondition) {
+    Map<DexProgramClass, KeepClassInfo.Joiner> minimumKeepClassInfoDependentOnPrecondition =
+        dependentMinimumKeepClassInfo.remove(precondition);
+    if (minimumKeepClassInfoDependentOnPrecondition != null) {
+      minimumKeepClassInfoDependentOnPrecondition.forEach(this::applyMinimumKeepInfoWhenLive);
+    }
+
+    ProgramFieldMap<KeepFieldInfo.Joiner> minimumKeepFieldInfoDependentOnPrecondition =
+        dependentMinimumKeepFieldInfo.remove(precondition);
+    if (minimumKeepFieldInfoDependentOnPrecondition != null) {
+      minimumKeepFieldInfoDependentOnPrecondition.forEach(this::applyMinimumKeepInfoWhenLive);
+    }
+
+    ProgramMethodMap<KeepMethodInfo.Joiner> minimumKeepMethodInfoDependentOnPrecondition =
+        dependentMinimumKeepMethodInfo.remove(precondition);
+    if (minimumKeepMethodInfoDependentOnPrecondition != null) {
+      minimumKeepMethodInfoDependentOnPrecondition.forEach(
+          this::applyMinimumKeepInfoWhenLiveOrTargeted);
+    }
+  }
+
   private void keepClassWithRules(DexProgramClass clazz, Set<ProguardKeepRuleBase> rules) {
     keepInfo.joinClass(clazz, info -> applyKeepRules(clazz, rules, info));
   }
 
-  private void keepMethodWithRules(ProgramMethod method, Set<ProguardKeepRuleBase> rules) {
-    keepInfo.joinMethod(method, info -> applyKeepRules(method, rules, info));
-  }
-
   private void keepFieldWithRules(ProgramField field, Set<ProguardKeepRuleBase> rules) {
     keepInfo.joinField(field, info -> applyKeepRules(field, rules, info));
   }
 
+  private void keepMethodWithRules(ProgramMethod method, Set<ProguardKeepRuleBase> rules) {
+    keepInfo.joinMethod(method, info -> applyKeepRules(method, rules, info));
+  }
+
   private void applyKeepRules(
       ProgramDefinition definition,
       Set<ProguardKeepRuleBase> rules,
@@ -3114,12 +3327,15 @@
           joiner.requireAccessModificationForRepackaging();
         }
       }
-      if (!modifiers.allowsObfuscation) {
-        joiner.disallowMinification();
-      }
       if (!modifiers.allowsAccessModification) {
         joiner.disallowAccessModification();
       }
+      if (!modifiers.allowsAnnotationRemoval) {
+        joiner.disallowAnnotationRemoval();
+      }
+      if (!modifiers.allowsObfuscation) {
+        joiner.disallowMinification();
+      }
     }
   }
 
@@ -3129,17 +3345,19 @@
     private Map<DexMethod, MethodProcessingContext> methodProcessingContexts =
         new ConcurrentHashMap<>();
 
-    List<ProgramMethod> desugaredMethods = new LinkedList<>();
+    private final List<ProgramMethod> desugaredMethods = new LinkedList<>();
 
-    Map<DexMethod, ProgramMethod> liveMethods = new IdentityHashMap<>();
+    private final Map<DexMethod, ProgramMethod> liveMethods = new ConcurrentHashMap<>();
 
-    Map<DexType, DexClasspathClass> syntheticClasspathClasses = new IdentityHashMap<>();
+    private final Map<DexType, DexClasspathClass> syntheticClasspathClasses =
+        new ConcurrentHashMap<>();
 
-    Map<DexProgramClass, List<DexClass>> injectedInterfaces = new IdentityHashMap<>();
+    private final Map<DexProgramClass, Set<DexClass>> injectedInterfaces =
+        new ConcurrentHashMap<>();
 
     // Subset of live methods that need have keep requirements.
-    List<Pair<ProgramMethod, Consumer<KeepMethodInfo.Joiner>>> liveMethodsWithKeepActions =
-        new ArrayList<>();
+    private final List<Pair<ProgramMethod, Consumer<KeepMethodInfo.Joiner>>>
+        liveMethodsWithKeepActions = new ArrayList<>();
 
     SyntheticAdditions(ProcessorContext processorContext) {
       this.processorContext = processorContext;
@@ -3154,7 +3372,8 @@
       boolean empty =
           desugaredMethods.isEmpty()
               && liveMethods.isEmpty()
-              && syntheticClasspathClasses.isEmpty();
+              && syntheticClasspathClasses.isEmpty()
+              && injectedInterfaces.isEmpty();
       assert !empty || liveMethodsWithKeepActions.isEmpty();
       return empty;
     }
@@ -3164,6 +3383,10 @@
       assert old == null;
     }
 
+    public void addLiveMethods(Iterable<ProgramMethod> methods) {
+      methods.forEach(this::addLiveMethod);
+    }
+
     public void addLiveMethod(ProgramMethod method) {
       DexMethod signature = method.getDefinition().getReference();
       assert !liveMethods.containsKey(signature);
@@ -3171,8 +3394,8 @@
     }
 
     public void injectInterface(DexProgramClass clazz, DexClass newInterface) {
-      List<DexClass> newInterfaces =
-          injectedInterfaces.computeIfAbsent(clazz, ignored -> new ArrayList<>());
+      Set<DexClass> newInterfaces =
+          injectedInterfaces.computeIfAbsent(clazz, ignored -> Sets.newConcurrentHashSet());
       newInterfaces.add(newInterface);
     }
 
@@ -3535,7 +3758,7 @@
 
     // Generate first the callbacks since they may require extra wrappers.
     ProgramMethodSet callbacks = desugaredLibraryWrapperAnalysis.generateCallbackMethods();
-    callbacks.forEach(additions::addLiveMethod);
+    additions.addLiveMethods(callbacks);
 
     // Generate wrappers on classpath so types are defined.
     desugaredLibraryWrapperAnalysis.generateWrappers(additions::addLiveClasspathClass);
@@ -3645,8 +3868,8 @@
         Set<DexEncodedMethod> reachableNotLive = Sets.difference(allLive, liveMethods.getItems());
         Log.debug(getClass(), "%s methods are reachable but not live", reachableNotLive.size());
         Log.info(getClass(), "Only reachable: %s", reachableNotLive);
-        SetView<DexEncodedMethod> targetedButNotLive = Sets
-            .difference(targetedMethods.getItems(), liveMethods.getItems());
+        SetView<DexEncodedMethod> targetedButNotLive =
+            Sets.difference(targetedMethods.getItems(), liveMethods.getItems());
         Log.debug(getClass(), "%s methods are targeted but not live", targetedButNotLive.size());
         Log.info(getClass(), "Targeted but not live: %s", targetedButNotLive);
       }
@@ -3655,15 +3878,14 @@
     }
   }
 
-  private void postProcessingDesugaring() {
+  private void postProcessingDesugaring() throws ExecutionException {
     SyntheticAdditions syntheticAdditions =
         new SyntheticAdditions(appView.createProcessorContext());
 
     R8PostProcessingDesugaringEventConsumer eventConsumer =
-        CfPostProcessingDesugaringEventConsumer.createForR8(
-            appView, syntheticAdditions::addLiveMethod, syntheticAdditions);
-    CfPostProcessingDesugaringCollection.create(appView, desugaring.getRetargetingInfo())
-        .postProcessingDesugaring(eventConsumer);
+        CfPostProcessingDesugaringEventConsumer.createForR8(appView, syntheticAdditions);
+    CfPostProcessingDesugaringCollection.create(appView, null, desugaring.getRetargetingInfo())
+        .postProcessingDesugaring(eventConsumer, executorService);
 
     if (syntheticAdditions.isEmpty()) {
       return;
@@ -3687,7 +3909,7 @@
   }
 
   private long getNumberOfLiveItems() {
-    long result = liveTypes.items.size();
+    long result = liveTypes.getItems().size();
     result += liveMethods.items.size();
     result += liveFields.fields.size();
     return result;
@@ -3718,28 +3940,18 @@
             enqueueRootItems(dependentItems);
           }
         });
-    consequentRootSet.dependentSoftPinned.forEach(
-        (reference, dependentItems) -> {
-          if (isLiveProgramReference(reference)) {
-            dependentItems.forEachReference(
-                item -> {
-                  if (isLiveProgramReference(item)) {
-                    keepInfo.joinInfo(item, appView, Joiner::pin);
-                  }
-                });
-          }
-        });
 
     // TODO(b/132600955): This modifies the root set. Should the consequent be persistent?
     rootSet.addConsequentRootSet(consequentRootSet, addNoShrinking);
-    if (mode.isInitialTreeShaking()) {
-      for (DexReference reference : consequentRootSet.noObfuscation) {
-        keepInfo.evaluateRule(reference, appView, Joiner::disallowMinification);
-      }
-      consequentRootSet.softPinned.forEachReference(
-          reference -> keepInfo.evaluateRule(reference, appView, Joiner::pin));
-    }
+
     enqueueRootItems(consequentRootSet.noShrinking);
+
+    consequentRootSet.forEachMinimumKeepInfo(
+        appView,
+        this::recordDependentMinimumKeepInfo,
+        this::recordDependentMinimumKeepInfo,
+        this::recordDependentMinimumKeepInfo);
+
     // Check for compatibility rules indicating that the holder must be implicitly kept.
     if (forceProguardCompatibility) {
       consequentRootSet.dependentKeepClassCompatRule.forEach(
@@ -3752,19 +3964,6 @@
     }
   }
 
-  private boolean isLiveProgramReference(DexReference reference) {
-    if (reference.isDexType()) {
-      DexProgramClass clazz =
-          DexProgramClass.asProgramClassOrNull(appInfo().definitionFor(reference.asDexType()));
-      return clazz != null && isTypeLive(clazz);
-    }
-    DexMember<?, ?> member = reference.asDexMember();
-    DexProgramClass holder =
-        DexProgramClass.asProgramClassOrNull(appInfo().definitionFor(member.holder));
-    ProgramMember<?, ?> programMember = member.lookupOnProgramClass(holder);
-    return programMember != null && isMemberLive(programMember.getDefinition());
-  }
-
   private ConsequentRootSet computeDelayedInterfaceMethodSyntheticBridges() {
     RootSetBuilder builder = RootSet.builder(appView, subtypingInfo);
     for (DelayedRootSetActionItem delayedRootSetActionItem : rootSet.delayedRootSetActionItems) {
@@ -3810,10 +4009,16 @@
     action.getAction().accept(builder);
   }
 
-  void retainAnnotationForFinalTreeShaking(List<DexAnnotation> annotations) {
+  void retainAnnotationForFinalTreeShaking(List<MatchedAnnotation> matchedAnnotations) {
     assert mode.isInitialTreeShaking();
     if (annotationRemoverBuilder != null) {
-      annotations.forEach(annotationRemoverBuilder::retainAnnotation);
+      for (MatchedAnnotation matchedAnnotation : matchedAnnotations) {
+        annotationRemoverBuilder.retainAnnotation(matchedAnnotation.getAnnotation());
+        workList.enqueueTraceAnnotationAction(
+            matchedAnnotation.getAnnotatedItem(),
+            matchedAnnotation.getAnnotation(),
+            matchedAnnotation.getAnnotatedKind());
+      }
     }
   }
 
@@ -3979,7 +4184,6 @@
   }
 
   void traceMethodDefinitionExcludingCode(ProgramMethod method) {
-    checkDefinitionForSoftPinning(method);
     markReferencedTypesAsLive(method);
     processAnnotations(method);
     method
@@ -3987,6 +4191,9 @@
         .getParameterAnnotations()
         .forEachAnnotation(
             annotation -> processAnnotation(method, annotation, AnnotatedKind.PARAMETER));
+
+    // Update keep info.
+    applyMinimumKeepInfo(method);
   }
 
   private void traceNonDesugaredCode(ProgramMethod method) {
@@ -4006,22 +4213,6 @@
     analyses.forEach(analysis -> analysis.processTracedCode(method, registry));
   }
 
-  private void checkDefinitionForSoftPinning(ProgramDefinition definition) {
-    DexReference reference = definition.getReference();
-    Set<ProguardKeepRuleBase> softPinRules = rootSet.softPinned.getRulesForReference(reference);
-    if (softPinRules != null) {
-      assert softPinRules.stream().noneMatch(r -> r.getModifiers().allowsOptimization);
-      keepInfo.joinInfo(reference, appInfo, Joiner::pin);
-    }
-    // Identify dependent soft pinning.
-    MutableItemsWithRules items = rootSet.dependentSoftPinned.get(definition.getContextType());
-    if (items != null && items.containsReference(reference)) {
-      assert items.getRulesForReference(reference).stream()
-          .noneMatch(r -> r.getModifiers().allowsOptimization);
-      keepInfo.joinInfo(reference, appInfo, Joiner::pin);
-    }
-  }
-
   private void markReferencedTypesAsLive(ProgramMethod method) {
     markTypeAsLive(method.getHolder(), method);
     markParameterAndReturnTypesAsLive(method);
@@ -4538,30 +4729,6 @@
     }
   }
 
-  private static class SetWithReason<T> {
-
-    private final Set<T> items = Sets.newIdentityHashSet();
-
-    private final BiConsumer<T, KeepReason> register;
-
-    public SetWithReason(BiConsumer<T, KeepReason> register) {
-      this.register = register;
-    }
-
-    boolean add(T item, KeepReason reason) {
-      register.accept(item, reason);
-      return items.add(item);
-    }
-
-    boolean contains(T item) {
-      return items.contains(item);
-    }
-
-    Set<T> getItems() {
-      return Collections.unmodifiableSet(items);
-    }
-  }
-
   private class AnnotationReferenceMarker implements IndexedItemCollection {
 
     private final ProgramDefinition context;
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.java
new file mode 100644
index 0000000..6f68a59
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.java
@@ -0,0 +1,126 @@
+// 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.shaking;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+
+public abstract class EnqueuerEvent {
+
+  public boolean isClassEvent() {
+    return false;
+  }
+
+  public ClassEnqueuerEvent asClassEvent() {
+    return null;
+  }
+
+  public boolean isLiveClassEvent() {
+    return false;
+  }
+
+  public LiveClassEnqueuerEvent asLiveClassEvent() {
+    return null;
+  }
+
+  public boolean isInstantiatedClassEvent() {
+    return false;
+  }
+
+  public InstantiatedClassEnqueuerEvent asInstantiatedClassEvent() {
+    return null;
+  }
+
+  public abstract static class ClassEnqueuerEvent extends EnqueuerEvent {
+
+    private final DexType clazz;
+
+    public ClassEnqueuerEvent(DexProgramClass clazz) {
+      this.clazz = clazz.getType();
+    }
+
+    public DexType getType() {
+      return clazz;
+    }
+
+    @Override
+    public boolean isClassEvent() {
+      return true;
+    }
+
+    @Override
+    public ClassEnqueuerEvent asClassEvent() {
+      return this;
+    }
+  }
+
+  public static class LiveClassEnqueuerEvent extends ClassEnqueuerEvent {
+
+    public LiveClassEnqueuerEvent(DexProgramClass clazz) {
+      super(clazz);
+    }
+
+    @Override
+    public boolean isLiveClassEvent() {
+      return true;
+    }
+
+    @Override
+    public LiveClassEnqueuerEvent asLiveClassEvent() {
+      return this;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (obj == this) {
+        return true;
+      }
+      if (obj instanceof LiveClassEnqueuerEvent) {
+        LiveClassEnqueuerEvent event = (LiveClassEnqueuerEvent) obj;
+        return getType() == event.getType();
+      }
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return (getType().hashCode() << 1) | 0;
+    }
+  }
+
+  public static class InstantiatedClassEnqueuerEvent extends ClassEnqueuerEvent {
+
+    public InstantiatedClassEnqueuerEvent(DexProgramClass clazz) {
+      super(clazz);
+    }
+
+    @Override
+    public boolean isInstantiatedClassEvent() {
+      return true;
+    }
+
+    @Override
+    public InstantiatedClassEnqueuerEvent asInstantiatedClassEvent() {
+      return this;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (obj == this) {
+        return true;
+      }
+      if (obj instanceof InstantiatedClassEnqueuerEvent) {
+        InstantiatedClassEnqueuerEvent event = (InstantiatedClassEnqueuerEvent) obj;
+        return getType() == event.getType();
+      }
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return (getType().hashCode() << 1) | 1;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index 38e40dd..4768437 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -191,6 +193,24 @@
     }
   }
 
+  static class TraceAnnotationAction extends EnqueuerAction {
+    private final ProgramDefinition annotatedItem;
+    private final DexAnnotation annotation;
+    private final AnnotatedKind annotatedKind;
+
+    TraceAnnotationAction(
+        ProgramDefinition annotatedItem, DexAnnotation annotation, AnnotatedKind annotatedKind) {
+      this.annotatedItem = annotatedItem;
+      this.annotation = annotation;
+      this.annotatedKind = annotatedKind;
+    }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      enqueuer.processAnnotation(annotatedItem, annotation, annotatedKind);
+    }
+  }
+
   static class TraceCodeAction extends EnqueuerAction {
     private final ProgramMethod method;
 
@@ -332,6 +352,9 @@
 
   abstract void enqueueMarkFieldKeptAction(ProgramField field, KeepReasonWitness witness);
 
+  abstract void enqueueTraceAnnotationAction(
+      ProgramDefinition annotatedItem, DexAnnotation annotation, AnnotatedKind annotatedKind);
+
   public abstract void enqueueTraceCodeAction(ProgramMethod method);
 
   public abstract void enqueueTraceConstClassAction(DexType type, ProgramMethod context);
@@ -431,6 +454,12 @@
     }
 
     @Override
+    void enqueueTraceAnnotationAction(
+        ProgramDefinition annotatedItem, DexAnnotation annotation, AnnotatedKind annotatedKind) {
+      queue.add(new TraceAnnotationAction(annotatedItem, annotation, annotatedKind));
+    }
+
+    @Override
     public void enqueueTraceCodeAction(ProgramMethod method) {
       queue.add(new TraceCodeAction(method));
     }
@@ -543,6 +572,12 @@
     }
 
     @Override
+    void enqueueTraceAnnotationAction(
+        ProgramDefinition annotatedItem, DexAnnotation annotation, AnnotatedKind annotatedKind) {
+      throw attemptToEnqueue();
+    }
+
+    @Override
     public void enqueueTraceCodeAction(ProgramMethod method) {
 
       throw attemptToEnqueue();
diff --git a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
index e509230..1eeaa21 100644
--- a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
@@ -6,10 +6,14 @@
 /** Globally controlled settings that affect the default values for kept items. */
 public interface GlobalKeepInfoConfiguration {
 
+  boolean isAnnotationRemovalEnabled();
+
   boolean isTreeShakingEnabled();
 
   boolean isMinificationEnabled();
 
+  boolean isOptimizationEnabled();
+
   boolean isAccessModificationEnabled();
 
   boolean isRepackagingEnabled();
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index aa6844e..d5c8c68 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -8,6 +8,8 @@
 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.DexClassAndField;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -258,10 +260,14 @@
     // combination as the size of member rules to satisfy.
     for (Set<DexDefinition> combination :
         Sets.combinations(filteredMembers, memberKeepRules.size())) {
-      Collection<DexEncodedField> fieldsInCombination =
-          DexDefinition.filterDexEncodedField(combination.stream()).collect(Collectors.toList());
-      Collection<DexEncodedMethod> methodsInCombination =
-          DexDefinition.filterDexEncodedMethod(combination.stream()).collect(Collectors.toList());
+      Collection<DexClassAndField> fieldsInCombination =
+          DexDefinition.filterDexEncodedField(
+                  combination.stream(), field -> DexClassAndField.create(targetClass, field))
+              .collect(Collectors.toList());
+      Collection<DexClassAndMethod> methodsInCombination =
+          DexDefinition.filterDexEncodedMethod(
+                  combination.stream(), method -> DexClassAndMethod.create(targetClass, method))
+              .collect(Collectors.toList());
       // Member rules are combined as AND logic: if found unsatisfied member rule, this
       // combination of live members is not a good fit.
       boolean satisfied =
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
index a12ca25..dc4a931 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
@@ -27,6 +27,10 @@
     return BOTTOM;
   }
 
+  public static Joiner newEmptyJoiner() {
+    return bottom().joiner();
+  }
+
   private KeepClassInfo(Builder builder) {
     super(builder);
   }
@@ -52,7 +56,8 @@
       GlobalKeepInfoConfiguration configuration, boolean kotlinMetadataKept) {
     return !kotlinMetadataKept
         || !isPinned()
-        || !configuration.isKeepRuntimeVisibleAnnotationsEnabled();
+        || !configuration.isKeepRuntimeVisibleAnnotationsEnabled()
+        || isAnnotationRemovalAllowed(configuration);
   }
 
   public static boolean isKotlinMetadataClassKept(AppView<?> appView) {
@@ -110,7 +115,7 @@
 
     @Override
     public boolean isEqualTo(KeepClassInfo other) {
-      return true;
+      return internalIsEqualTo(other);
     }
 
     @Override
@@ -126,6 +131,17 @@
     }
 
     @Override
+    public Joiner asClassJoiner() {
+      return this;
+    }
+
+    @Override
+    public Joiner merge(Joiner joiner) {
+      // Should be extended to merge the fields of this class in case any are added.
+      return super.merge(joiner);
+    }
+
+    @Override
     Joiner self() {
       return this;
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
index bdfb7cc..0c54589 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
@@ -20,6 +20,10 @@
     return BOTTOM;
   }
 
+  public static Joiner newEmptyJoiner() {
+    return bottom().joiner();
+  }
+
   private KeepFieldInfo(Builder builder) {
     super(builder);
   }
@@ -73,7 +77,7 @@
 
     @Override
     public boolean isEqualTo(KeepFieldInfo other) {
-      return true;
+      return internalIsEqualTo(other);
     }
 
     @Override
@@ -89,6 +93,17 @@
     }
 
     @Override
+    public Joiner asFieldJoiner() {
+      return this;
+    }
+
+    @Override
+    public Joiner merge(Joiner joiner) {
+      // Should be extended to merge the fields of this class in case any are added.
+      return super.merge(joiner);
+    }
+
+    @Override
     Joiner self() {
       return this;
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
index acf36aa..b795aab 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -6,39 +6,71 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.shaking.KeepInfo.Builder;
+import java.util.function.Consumer;
 
 /** Keep information that can be associated with any item, i.e., class, method or field. */
 public abstract class KeepInfo<B extends Builder<B, K>, K extends KeepInfo<B, K>> {
 
   private final boolean pinned;
-  private final boolean allowMinification;
   private final boolean allowAccessModification;
+  private final boolean allowAnnotationRemoval;
+  private final boolean allowMinification;
+  private final boolean allowOptimization;
+  private final boolean allowShrinking;
   private final boolean requireAccessModificationForRepackaging;
 
   private KeepInfo(
       boolean pinned,
-      boolean allowMinification,
       boolean allowAccessModification,
+      boolean allowAnnotationRemoval,
+      boolean allowMinification,
+      boolean allowOptimization,
+      boolean allowShrinking,
       boolean requireAccessModificationForRepackaging) {
     this.pinned = pinned;
-    this.allowMinification = allowMinification;
     this.allowAccessModification = allowAccessModification;
+    this.allowAnnotationRemoval = allowAnnotationRemoval;
+    this.allowMinification = allowMinification;
+    this.allowOptimization = allowOptimization;
+    this.allowShrinking = allowShrinking;
     this.requireAccessModificationForRepackaging = requireAccessModificationForRepackaging;
   }
 
   KeepInfo(B builder) {
     this(
         builder.isPinned(),
-        builder.isMinificationAllowed(),
         builder.isAccessModificationAllowed(),
+        builder.isAnnotationRemovalAllowed(),
+        builder.isMinificationAllowed(),
+        builder.isOptimizationAllowed(),
+        builder.isShrinkingAllowed(),
         builder.isAccessModificationRequiredForRepackaging());
   }
 
   abstract B builder();
 
-  /** True if an item must be present in the output. */
+  /**
+   * True if an item may have all of its annotations removed.
+   *
+   * <p>If this returns false, some annotations may still be removed if the configuration does not
+   * keep all annotation attributes.
+   */
+  public boolean isAnnotationRemovalAllowed(GlobalKeepInfoConfiguration configuration) {
+    return configuration.isAnnotationRemovalEnabled() && internalIsAnnotationRemovalAllowed();
+  }
+
+  boolean internalIsAnnotationRemovalAllowed() {
+    return allowAnnotationRemoval;
+  }
+
+  /**
+   * True if an item must be present in the output.
+   *
+   * @deprecated Prefer task dependent predicates.
+   */
+  @Deprecated
   public boolean isPinned() {
-    return pinned;
+    return pinned || !allowOptimization;
   }
 
   /**
@@ -56,6 +88,34 @@
   }
 
   /**
+   * True if an item may be optimized (i.e., the item is not soft pinned).
+   *
+   * <p>This method requires knowledge of the global configuration as that can override the concrete
+   * value on a given item.
+   */
+  public boolean isOptimizationAllowed(GlobalKeepInfoConfiguration configuration) {
+    return configuration.isOptimizationEnabled() && internalIsOptimizationAllowed();
+  }
+
+  boolean internalIsOptimizationAllowed() {
+    return allowOptimization;
+  }
+
+  /**
+   * True if an item is subject to shrinking (i.e., tree shaking).
+   *
+   * <p>This method requires knowledge of the global configuration as that can override the concrete
+   * value on a given item.
+   */
+  public boolean isShrinkingAllowed(GlobalKeepInfoConfiguration configuration) {
+    return configuration.isTreeShakingEnabled() && internalIsShrinkingAllowed();
+  }
+
+  boolean internalIsShrinkingAllowed() {
+    return allowShrinking;
+  }
+
+  /**
    * True if an item may be repackaged.
    *
    * <p>This method requires knowledge of the global configuration as that can override the concrete
@@ -133,7 +193,11 @@
     // An item is less, aka, lower in the lattice, if each of its attributes is at least as
     // permissive of that on other.
     return (!pinned || other.isPinned())
-        && (allowAccessModification || !other.internalIsAccessModificationAllowed());
+        && (allowAccessModification || !other.internalIsAccessModificationAllowed())
+        && (allowAnnotationRemoval || !other.internalIsAnnotationRemovalAllowed())
+        && (allowMinification || !other.internalIsMinificationAllowed())
+        && (allowOptimization || !other.internalIsOptimizationAllowed())
+        && (allowShrinking || !other.internalIsShrinkingAllowed());
   }
 
   /** Builder to construct an arbitrary keep info object. */
@@ -151,8 +215,11 @@
 
     private K original;
     private boolean pinned;
-    private boolean allowMinification;
     private boolean allowAccessModification;
+    private boolean allowAnnotationRemoval;
+    private boolean allowMinification;
+    private boolean allowOptimization;
+    private boolean allowShrinking;
     private boolean requireAccessModificationForRepackaging;
 
     Builder() {
@@ -162,25 +229,34 @@
     Builder(K original) {
       this.original = original;
       pinned = original.isPinned();
-      allowMinification = original.internalIsMinificationAllowed();
       allowAccessModification = original.internalIsAccessModificationAllowed();
+      allowAnnotationRemoval = original.internalIsAnnotationRemovalAllowed();
+      allowMinification = original.internalIsMinificationAllowed();
+      allowOptimization = original.internalIsOptimizationAllowed();
+      allowShrinking = original.internalIsShrinkingAllowed();
       requireAccessModificationForRepackaging =
           original.internalIsAccessModificationRequiredForRepackaging();
     }
 
     B makeTop() {
       pin();
-      disallowMinification();
-      requireAccessModificationForRepackaging();
       disallowAccessModification();
+      disallowAnnotationRemoval();
+      disallowMinification();
+      disallowOptimization();
+      disallowShrinking();
+      requireAccessModificationForRepackaging();
       return self();
     }
 
     B makeBottom() {
       unpin();
-      allowMinification();
-      unsetRequireAccessModificationForRepackaging();
       allowAccessModification();
+      allowAnnotationRemoval();
+      allowMinification();
+      allowOptimization();
+      allowShrinking();
+      unsetRequireAccessModificationForRepackaging();
       return self();
     }
 
@@ -199,23 +275,21 @@
       return doBuild();
     }
 
-    private boolean internalIsEqualTo(K other) {
+    boolean internalIsEqualTo(K other) {
       return isPinned() == other.isPinned()
-          && isMinificationAllowed() == other.internalIsMinificationAllowed()
-          && isAccessModificationRequiredForRepackaging()
-              == other.internalIsAccessModificationRequiredForRepackaging()
           && isAccessModificationAllowed() == other.internalIsAccessModificationAllowed()
-          && isEqualTo(other);
+          && isAnnotationRemovalAllowed() == other.internalIsAnnotationRemovalAllowed()
+          && isMinificationAllowed() == other.internalIsMinificationAllowed()
+          && isOptimizationAllowed() == other.internalIsOptimizationAllowed()
+          && isShrinkingAllowed() == other.internalIsShrinkingAllowed()
+          && isAccessModificationRequiredForRepackaging()
+              == other.internalIsAccessModificationRequiredForRepackaging();
     }
 
     public boolean isPinned() {
       return pinned;
     }
 
-    public boolean isMinificationAllowed() {
-      return allowMinification;
-    }
-
     public boolean isAccessModificationRequiredForRepackaging() {
       return requireAccessModificationForRepackaging;
     }
@@ -224,6 +298,22 @@
       return allowAccessModification;
     }
 
+    public boolean isAnnotationRemovalAllowed() {
+      return allowAnnotationRemoval;
+    }
+
+    public boolean isMinificationAllowed() {
+      return allowMinification;
+    }
+
+    public boolean isOptimizationAllowed() {
+      return allowOptimization;
+    }
+
+    public boolean isShrinkingAllowed() {
+      return allowShrinking;
+    }
+
     public B setPinned(boolean pinned) {
       this.pinned = pinned;
       return self();
@@ -250,6 +340,32 @@
       return setAllowMinification(false);
     }
 
+    public B setAllowOptimization(boolean allowOptimization) {
+      this.allowOptimization = allowOptimization;
+      return self();
+    }
+
+    public B allowOptimization() {
+      return setAllowOptimization(true);
+    }
+
+    public B disallowOptimization() {
+      return setAllowOptimization(false);
+    }
+
+    public B setAllowShrinking(boolean allowShrinking) {
+      this.allowShrinking = allowShrinking;
+      return self();
+    }
+
+    public B allowShrinking() {
+      return setAllowShrinking(true);
+    }
+
+    public B disallowShrinking() {
+      return setAllowShrinking(false);
+    }
+
     public B setRequireAccessModificationForRepackaging(
         boolean requireAccessModificationForRepackaging) {
       this.requireAccessModificationForRepackaging = requireAccessModificationForRepackaging;
@@ -276,6 +392,19 @@
     public B disallowAccessModification() {
       return setAllowAccessModification(false);
     }
+
+    public B setAllowAnnotationRemoval(boolean allowAnnotationRemoval) {
+      this.allowAnnotationRemoval = allowAnnotationRemoval;
+      return self();
+    }
+
+    public B allowAnnotationRemoval() {
+      return setAllowAnnotationRemoval(true);
+    }
+
+    public B disallowAnnotationRemoval() {
+      return setAllowAnnotationRemoval(false);
+    }
   }
 
   /** Joiner to construct monotonically increasing keep info object. */
@@ -284,12 +413,35 @@
 
     abstract J self();
 
-    private final Builder<B, K> builder;
+    final Builder<B, K> builder;
 
     Joiner(Builder<B, K> builder) {
       this.builder = builder;
     }
 
+    public J applyIf(boolean condition, Consumer<J> thenConsumer) {
+      if (condition) {
+        thenConsumer.accept(self());
+      }
+      return self();
+    }
+
+    public KeepClassInfo.Joiner asClassJoiner() {
+      return null;
+    }
+
+    public KeepFieldInfo.Joiner asFieldJoiner() {
+      return null;
+    }
+
+    public KeepMethodInfo.Joiner asMethodJoiner() {
+      return null;
+    }
+
+    public boolean isBottom() {
+      return builder.isEqualTo(builder.getBottomInfo());
+    }
+
     public boolean isTop() {
       return builder.isEqualTo(builder.getTopInfo());
     }
@@ -304,21 +456,50 @@
       return self();
     }
 
-    public J disallowMinification() {
-      builder.disallowMinification();
-      return self();
-    }
-
     public J disallowAccessModification() {
       builder.disallowAccessModification();
       return self();
     }
 
+    public J disallowAnnotationRemoval() {
+      builder.disallowAnnotationRemoval();
+      return self();
+    }
+
+    public J disallowMinification() {
+      builder.disallowMinification();
+      return self();
+    }
+
+    public J disallowOptimization() {
+      builder.disallowOptimization();
+      return self();
+    }
+
+    public J disallowShrinking() {
+      builder.disallowShrinking();
+      return self();
+    }
+
     public J requireAccessModificationForRepackaging() {
       builder.requireAccessModificationForRepackaging();
       return self();
     }
 
+    public J merge(J joiner) {
+      Builder<B, K> builder = joiner.builder;
+      applyIf(builder.isPinned(), Joiner::pin);
+      applyIf(!builder.isAccessModificationAllowed(), Joiner::disallowAccessModification);
+      applyIf(!builder.isAnnotationRemovalAllowed(), Joiner::disallowAnnotationRemoval);
+      applyIf(!builder.isMinificationAllowed(), Joiner::disallowMinification);
+      applyIf(!builder.isOptimizationAllowed(), Joiner::disallowOptimization);
+      applyIf(!builder.isShrinkingAllowed(), Joiner::disallowShrinking);
+      applyIf(
+          builder.isAccessModificationRequiredForRepackaging(),
+          Joiner::requireAccessModificationForRepackaging);
+      return self();
+    }
+
     public K join() {
       K joined = builder.build();
       K original = builder.original;
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
index ddd5bfc..71068d2 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -4,8 +4,11 @@
 package com.android.tools.r8.shaking;
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMember;
@@ -22,17 +25,25 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.shaking.KeepFieldInfo.Joiner;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.MapUtils;
 import com.google.common.collect.Streams;
-import java.util.ArrayList;
 import java.util.IdentityHashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
 
 // Non-mutable collection of keep information pertaining to a program.
 public abstract class KeepInfoCollection {
 
+  abstract void forEachRuleInstance(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      BiConsumer<DexProgramClass, KeepClassInfo.Joiner> classRuleInstanceConsumer,
+      BiConsumer<ProgramField, KeepFieldInfo.Joiner> fieldRuleInstanceConsumer,
+      BiConsumer<ProgramMethod, KeepMethodInfo.Joiner> methodRuleInstanceConsumer);
+
   // TODO(b/157538235): This should not be bottom.
   private static KeepClassInfo keepInfoForNonProgramClass() {
     return KeepClassInfo.bottom();
@@ -48,8 +59,6 @@
     return KeepFieldInfo.bottom();
   }
 
-  abstract Map<DexReference, List<Consumer<KeepInfo.Joiner<?, ?, ?>>>> getRuleInstances();
-
   /**
    * Base accessor for keep info on a class.
    *
@@ -195,13 +204,17 @@
     private final Map<DexField, KeepFieldInfo> keepFieldInfo;
 
     // Map of applied rules for which keys may need to be mutated.
-    private final Map<DexReference, List<Consumer<KeepInfo.Joiner<?, ?, ?>>>> ruleInstances;
+    private final Map<DexType, KeepClassInfo.Joiner> classRuleInstances;
+    private final Map<DexField, KeepFieldInfo.Joiner> fieldRuleInstances;
+    private final Map<DexMethod, KeepMethodInfo.Joiner> methodRuleInstances;
 
     MutableKeepInfoCollection() {
       this(
           new IdentityHashMap<>(),
           new IdentityHashMap<>(),
           new IdentityHashMap<>(),
+          new IdentityHashMap<>(),
+          new IdentityHashMap<>(),
           new IdentityHashMap<>());
     }
 
@@ -209,11 +222,15 @@
         Map<DexType, KeepClassInfo> keepClassInfo,
         Map<DexMethod, KeepMethodInfo> keepMethodInfo,
         Map<DexField, KeepFieldInfo> keepFieldInfo,
-        Map<DexReference, List<Consumer<KeepInfo.Joiner<?, ?, ?>>>> ruleInstances) {
+        Map<DexType, KeepClassInfo.Joiner> classRuleInstances,
+        Map<DexField, KeepFieldInfo.Joiner> fieldRuleInstances,
+        Map<DexMethod, KeepMethodInfo.Joiner> methodRuleInstances) {
       this.keepClassInfo = keepClassInfo;
       this.keepMethodInfo = keepMethodInfo;
       this.keepFieldInfo = keepFieldInfo;
-      this.ruleInstances = ruleInstances;
+      this.classRuleInstances = classRuleInstances;
+      this.fieldRuleInstances = fieldRuleInstances;
+      this.methodRuleInstances = methodRuleInstances;
     }
 
     public void removeKeepInfoForPrunedItems(Set<DexType> removedClasses) {
@@ -265,42 +282,96 @@
             KeepFieldInfo previous = newFieldInfo.put(newField, info);
             assert previous == null;
           });
-      Map<DexReference, List<Consumer<KeepInfo.Joiner<?, ?, ?>>>> newRuleInstances =
-          new IdentityHashMap<>(ruleInstances.size());
-      ruleInstances.forEach(
-          (reference, consumers) -> {
-            DexReference newReference;
-            if (reference.isDexType()) {
-              DexType newType = lens.lookupType(reference.asDexType());
-              if (!newType.isClassType()) {
-                assert newType.isIntType() : "Expected only enum unboxing type changes.";
-                return;
-              }
-              newReference = newType;
-            } else if (reference.isDexMethod()) {
-              newReference = lens.getRenamedMethodSignature(reference.asDexMethod());
-            } else {
-              assert reference.isDexField();
-              newReference = lens.getRenamedFieldSignature(reference.asDexField());
-            }
-            newRuleInstances.put(newReference, consumers);
-          });
       return new MutableKeepInfoCollection(
-          newClassInfo, newMethodInfo, newFieldInfo, newRuleInstances);
+          newClassInfo,
+          newMethodInfo,
+          newFieldInfo,
+          rewriteRuleInstances(
+              classRuleInstances,
+              clazz -> {
+                DexType rewritten = lens.lookupType(clazz);
+                if (rewritten.isClassType()) {
+                  return rewritten;
+                }
+                assert rewritten.isIntType();
+                return null;
+              },
+              KeepClassInfo::newEmptyJoiner),
+          rewriteRuleInstances(
+              fieldRuleInstances, lens::getRenamedFieldSignature, KeepFieldInfo::newEmptyJoiner),
+          rewriteRuleInstances(
+              methodRuleInstances,
+              lens::getRenamedMethodSignature,
+              KeepMethodInfo::newEmptyJoiner));
+    }
+
+    private static <R, J extends KeepInfo.Joiner<J, ?, ?>> Map<R, J> rewriteRuleInstances(
+        Map<R, J> ruleInstances, Function<R, R> rewriter, Supplier<J> newEmptyJoiner) {
+      return MapUtils.transform(
+          ruleInstances,
+          IdentityHashMap::new,
+          rewriter,
+          Function.identity(),
+          (joiner, otherJoiner) -> newEmptyJoiner.get().merge(joiner).merge(otherJoiner));
     }
 
     @Override
-    Map<DexReference, List<Consumer<KeepInfo.Joiner<?, ?, ?>>>> getRuleInstances() {
-      return ruleInstances;
+    void forEachRuleInstance(
+        AppView<? extends AppInfoWithClassHierarchy> appView,
+        BiConsumer<DexProgramClass, KeepClassInfo.Joiner> classRuleInstanceConsumer,
+        BiConsumer<ProgramField, KeepFieldInfo.Joiner> fieldRuleInstanceConsumer,
+        BiConsumer<ProgramMethod, KeepMethodInfo.Joiner> methodRuleInstanceConsumer) {
+      classRuleInstances.forEach(
+          (type, ruleInstance) -> {
+            DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+            if (clazz != null) {
+              classRuleInstanceConsumer.accept(clazz, ruleInstance);
+            }
+          });
+      fieldRuleInstances.forEach(
+          (fieldReference, ruleInstance) -> {
+            DexProgramClass holder =
+                asProgramClassOrNull(appView.definitionFor(fieldReference.getHolderType()));
+            ProgramField field = holder.lookupProgramField(fieldReference);
+            if (field != null) {
+              fieldRuleInstanceConsumer.accept(field, ruleInstance);
+            }
+          });
+      methodRuleInstances.forEach(
+          (methodReference, ruleInstance) -> {
+            DexProgramClass holder =
+                asProgramClassOrNull(appView.definitionFor(methodReference.getHolderType()));
+            ProgramMethod method = holder.lookupProgramMethod(methodReference);
+            if (method != null) {
+              methodRuleInstanceConsumer.accept(method, ruleInstance);
+            }
+          });
     }
 
-    void evaluateRule(
-        DexReference reference,
-        DexDefinitionSupplier definitions,
-        Consumer<KeepInfo.Joiner<?, ?, ?>> fn) {
-      joinInfo(reference, definitions, fn);
-      if (!getInfo(reference, definitions).isBottom()) {
-        ruleInstances.computeIfAbsent(reference, k -> new ArrayList<>()).add(fn);
+    void evaluateClassRule(DexProgramClass clazz, KeepClassInfo.Joiner minimumKeepInfo) {
+      if (!minimumKeepInfo.isBottom()) {
+        joinClass(clazz, joiner -> joiner.merge(minimumKeepInfo));
+        classRuleInstances
+            .computeIfAbsent(clazz.getType(), ignoreKey(KeepClassInfo::newEmptyJoiner))
+            .merge(minimumKeepInfo);
+      }
+    }
+
+    void evaluateFieldRule(ProgramField field, KeepFieldInfo.Joiner minimumKeepInfo) {
+      if (!minimumKeepInfo.isBottom()) {
+        joinField(field, joiner -> joiner.merge(minimumKeepInfo));
+        fieldRuleInstances
+            .computeIfAbsent(field.getReference(), ignoreKey(KeepFieldInfo::newEmptyJoiner))
+            .merge(minimumKeepInfo);
+      }
+    }
+
+    void evaluateMethodRule(ProgramMethod method, KeepMethodInfo.Joiner minimumKeepInfo) {
+      if (!minimumKeepInfo.isBottom()) {
+        joinMethod(method, joiner -> joiner.merge(minimumKeepInfo));
+        methodRuleInstances
+            .computeIfAbsent(method.getReference(), ignoreKey(KeepMethodInfo::newEmptyJoiner))
+            .merge(minimumKeepInfo);
       }
     }
 
@@ -321,9 +392,10 @@
       return keepFieldInfo.getOrDefault(field.getReference(), KeepFieldInfo.bottom());
     }
 
-    public void joinClass(DexProgramClass clazz, Consumer<KeepClassInfo.Joiner> fn) {
+    public void joinClass(DexProgramClass clazz, Consumer<? super KeepClassInfo.Joiner> fn) {
       KeepClassInfo info = getClassInfo(clazz);
       if (info.isTop()) {
+        assert info == KeepClassInfo.top();
         return;
       }
       KeepClassInfo.Joiner joiner = info.joiner();
@@ -334,34 +406,6 @@
       }
     }
 
-    public void joinInfo(
-        DexReference reference,
-        DexDefinitionSupplier definitions,
-        Consumer<KeepInfo.Joiner<?, ?, ?>> fn) {
-      if (reference.isDexType()) {
-        DexType type = reference.asDexType();
-        DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(type));
-        if (clazz != null) {
-          joinClass(clazz, fn::accept);
-        }
-      } else if (reference.isDexMethod()) {
-        DexMethod method = reference.asDexMethod();
-        DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(method.holder));
-        ProgramMethod definition = method.lookupOnProgramClass(clazz);
-        if (definition != null) {
-          joinMethod(definition, fn::accept);
-        }
-      } else {
-        assert reference.isDexField();
-        DexField field = reference.asDexField();
-        DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(field.holder));
-        ProgramField definition = field.lookupOnProgramClass(clazz);
-        if (definition != null) {
-          joinField(definition, fn::accept);
-        }
-      }
-    }
-
     public void keepClass(DexProgramClass clazz) {
       joinClass(clazz, KeepInfo.Joiner::top);
     }
@@ -370,9 +414,10 @@
       joinClass(clazz, KeepInfo.Joiner::pin);
     }
 
-    public void joinMethod(ProgramMethod method, Consumer<KeepMethodInfo.Joiner> fn) {
+    public void joinMethod(ProgramMethod method, Consumer<? super KeepMethodInfo.Joiner> fn) {
       KeepMethodInfo info = getMethodInfo(method);
-      if (info == KeepMethodInfo.top()) {
+      if (info.isTop()) {
+        assert info == KeepMethodInfo.top();
         return;
       }
       KeepMethodInfo.Joiner joiner = info.joiner();
@@ -423,9 +468,10 @@
       }
     }
 
-    public void joinField(ProgramField field, Consumer<KeepFieldInfo.Joiner> fn) {
+    public void joinField(ProgramField field, Consumer<? super KeepFieldInfo.Joiner> fn) {
       KeepFieldInfo info = getFieldInfo(field);
       if (info.isTop()) {
+        assert info == KeepFieldInfo.top();
         return;
       }
       Joiner joiner = info.joiner();
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
index ab243b6..1669a40 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
@@ -20,6 +20,10 @@
     return BOTTOM;
   }
 
+  public static Joiner newEmptyJoiner() {
+    return bottom().joiner();
+  }
+
   private KeepMethodInfo(Builder builder) {
     super(builder);
   }
@@ -73,7 +77,7 @@
 
     @Override
     public boolean isEqualTo(KeepMethodInfo other) {
-      return true;
+      return internalIsEqualTo(other);
     }
 
     @Override
@@ -89,6 +93,17 @@
     }
 
     @Override
+    public Joiner asMethodJoiner() {
+      return this;
+    }
+
+    @Override
+    public Joiner merge(Joiner joiner) {
+      // Should be extended to merge the fields of this class in case any are added.
+      return super.merge(joiner);
+    }
+
+    @Override
     Joiner self() {
       return this;
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 49c4a1e..948caf2 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -388,6 +388,12 @@
                   // TODO(b/189807246): This should be removed.
                   modifiers.setAllowsOptimization(true);
                   modifiers.setAllowsObfuscation(isObfuscating());
+
+                  // In non-compatibility mode, adding -dontoptimize does not cause all annotations
+                  // to be retained.
+                  if (!forceProguardCompatibility && isShrinking()) {
+                    modifiers.setAllowsAnnotationRemoval(true);
+                  }
                 });
         addRule(rule);
         this.keepAllRule = rule;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index ff71812..3072667 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -1024,6 +1024,10 @@
             builder.getModifiersBuilder().setAllowsObfuscation(true);
           } else if (acceptString("accessmodification")) {
             builder.getModifiersBuilder().setAllowsAccessModification(true);
+          } else if (allowTestOptions) {
+            if (acceptString("annotationremoval")) {
+              builder.getModifiersBuilder().setAllowsAnnotationRemoval(true);
+            }
           }
         } else if (acceptString("includedescriptorclasses")) {
           builder.getModifiersBuilder().setIncludeDescriptorClasses(true);
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
index a02117d..3021c88 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
@@ -7,6 +7,7 @@
   public static class Builder {
 
     private boolean allowsAccessModification = false;
+    private boolean allowsAnnotationRemoval = false;
     private boolean allowsShrinking = false;
     private boolean allowsOptimization = false;
     private boolean allowsObfuscation = false;
@@ -19,6 +20,11 @@
       return this;
     }
 
+    public Builder setAllowsAnnotationRemoval(boolean allowsAnnotationRemoval) {
+      this.allowsAnnotationRemoval = allowsAnnotationRemoval;
+      return this;
+    }
+
     public void setAllowsShrinking(boolean allowsShrinking) {
       this.allowsShrinking = allowsShrinking;
     }
@@ -39,6 +45,7 @@
     ProguardKeepRuleModifiers build() {
       return new ProguardKeepRuleModifiers(
           allowsAccessModification,
+          allowsAnnotationRemoval,
           allowsShrinking,
           allowsOptimization,
           allowsObfuscation,
@@ -47,6 +54,7 @@
   }
 
   public final boolean allowsAccessModification;
+  public final boolean allowsAnnotationRemoval;
   public final boolean allowsShrinking;
   public final boolean allowsOptimization;
   public final boolean allowsObfuscation;
@@ -54,11 +62,13 @@
 
   private ProguardKeepRuleModifiers(
       boolean allowsAccessModification,
+      boolean allowsAnnotationRemoval,
       boolean allowsShrinking,
       boolean allowsOptimization,
       boolean allowsObfuscation,
       boolean includeDescriptorClasses) {
     this.allowsAccessModification = allowsAccessModification;
+    this.allowsAnnotationRemoval = allowsAnnotationRemoval;
     this.allowsShrinking = allowsShrinking;
     this.allowsOptimization = allowsOptimization;
     this.allowsObfuscation = allowsObfuscation;
@@ -72,6 +82,15 @@
     return new Builder();
   }
 
+  public boolean isBottom() {
+    return allowsAccessModification
+        && allowsAnnotationRemoval
+        && allowsObfuscation
+        && allowsOptimization
+        && allowsShrinking
+        && !includeDescriptorClasses;
+  }
+
   @Override
   public boolean equals(Object o) {
     if (!(o instanceof ProguardKeepRuleModifiers)) {
@@ -79,6 +98,7 @@
     }
     ProguardKeepRuleModifiers that = (ProguardKeepRuleModifiers) o;
     return allowsAccessModification == that.allowsAccessModification
+        && allowsAnnotationRemoval == that.allowsAnnotationRemoval
         && allowsShrinking == that.allowsShrinking
         && allowsOptimization == that.allowsOptimization
         && allowsObfuscation == that.allowsObfuscation
@@ -88,16 +108,18 @@
   @Override
   public int hashCode() {
     return (allowsAccessModification ? 1 : 0)
-        | (allowsShrinking ? 2 : 0)
-        | (allowsOptimization ? 4 : 0)
-        | (allowsObfuscation ? 8 : 0)
-        | (includeDescriptorClasses ? 16 : 0);
+        | (allowsAnnotationRemoval ? 2 : 0)
+        | (allowsShrinking ? 4 : 0)
+        | (allowsOptimization ? 8 : 0)
+        | (allowsObfuscation ? 16 : 0)
+        | (includeDescriptorClasses ? 32 : 0);
   }
 
   @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
     appendWithComma(builder, allowsAccessModification, "allowaccessmodification");
+    appendWithComma(builder, allowsAnnotationRemoval, "allowannotationremoval");
     appendWithComma(builder, allowsObfuscation, "allowobfuscation");
     appendWithComma(builder, allowsShrinking, "allowshrinking");
     appendWithComma(builder, allowsOptimization, "allowoptimization");
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index 2aba15e..562f6b0 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -181,7 +181,7 @@
   }
 
   public boolean matches(
-      DexEncodedField field,
+      DexClassAndField field,
       AppView<?> appView,
       Consumer<AnnotationMatchResult> matchedAnnotationsConsumer,
       DexStringCache stringCache) {
@@ -192,8 +192,8 @@
       case ALL_FIELDS:
         {
           // Access flags check.
-          if (!getAccessFlags().containsAll(field.accessFlags)
-              || !getNegatedAccessFlags().containsNone(field.accessFlags)) {
+          if (!getAccessFlags().containsAll(field.getAccessFlags())
+              || !getNegatedAccessFlags().containsNone(field.getAccessFlags())) {
             break;
           }
           // Annotations check.
@@ -209,8 +209,8 @@
             break;
           }
           // Access flags check.
-          if (!getAccessFlags().containsAll(field.accessFlags)
-              || !getNegatedAccessFlags().containsNone(field.accessFlags)) {
+          if (!getAccessFlags().containsAll(field.getAccessFlags())
+              || !getNegatedAccessFlags().containsNone(field.getAccessFlags())) {
             break;
           }
           // Type check.
@@ -233,7 +233,7 @@
   }
 
   public boolean matches(
-      DexEncodedMethod method,
+      DexClassAndMethod method,
       AppView<?> appView,
       Consumer<AnnotationMatchResult> matchedAnnotationsConsumer,
       DexStringCache stringCache) {
@@ -241,7 +241,7 @@
         appView.graphLens().getOriginalMethodSignature(method.getReference());
     switch (getRuleType()) {
       case ALL_METHODS:
-        if (method.isClassInitializer()) {
+        if (method.getDefinition().isClassInitializer()) {
           break;
         }
         // Fall through for all other methods.
@@ -249,8 +249,8 @@
       case ALL:
         {
           // Access flags check.
-          if (!getAccessFlags().containsAll(method.accessFlags)
-              || !getNegatedAccessFlags().containsNone(method.accessFlags)) {
+          if (!getAccessFlags().containsAll(method.getAccessFlags())
+              || !getNegatedAccessFlags().containsNone(method.getAccessFlags())) {
             break;
           }
           // Annotations check.
@@ -260,7 +260,7 @@
 
       case METHOD:
         // Check return type.
-        if (!type.matches(originalSignature.proto.returnType, appView)) {
+        if (!type.matches(originalSignature.getReturnType(), appView)) {
           break;
         }
         // Fall through for access flags, name and arguments.
@@ -275,8 +275,8 @@
             break;
           }
           // Access flags check.
-          if (!getAccessFlags().containsAll(method.accessFlags)
-              || !getNegatedAccessFlags().containsNone(method.accessFlags)) {
+          if (!getAccessFlags().containsAll(method.getAccessFlags())
+              || !getNegatedAccessFlags().containsNone(method.getAccessFlags())) {
             break;
           }
           // Annotations check.
@@ -289,7 +289,7 @@
           if (arguments.size() == 1 && arguments.get(0).isTripleDotPattern()) {
             return true;
           }
-          DexType[] parameters = originalSignature.proto.parameters.values;
+          DexType[] parameters = originalSignature.getParameters().values;
           if (parameters.length != arguments.size()) {
             break;
           }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index e4890a0..28094a1 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -5,6 +5,8 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.utils.LensUtils.rewriteAndApplyIfNotPrimitiveType;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+import static java.util.Collections.emptyMap;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.AssumeNoSideEffectsRuleForObjectMembersDiagnostic;
@@ -12,9 +14,14 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
+import com.android.tools.r8.graph.Definition;
 import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexClassAndMember;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -29,6 +36,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMember;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
@@ -39,7 +47,11 @@
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AnnotationMatchResult.AnnotationsIgnoredMatchResult;
 import com.android.tools.r8.shaking.AnnotationMatchResult.ConcreteAnnotationMatchResult;
+import com.android.tools.r8.shaking.AnnotationMatchResult.MatchedAnnotation;
 import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
+import com.android.tools.r8.shaking.EnqueuerEvent.InstantiatedClassEnqueuerEvent;
+import com.android.tools.r8.shaking.EnqueuerEvent.LiveClassEnqueuerEvent;
+import com.android.tools.r8.shaking.KeepInfo.Joiner;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.Consumer3;
 import com.android.tools.r8.utils.InternalOptions;
@@ -48,6 +60,7 @@
 import com.android.tools.r8.utils.PredicateSet;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.TriConsumer;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -83,6 +96,39 @@
 
 public class RootSetUtils {
 
+  static void modifyMinimumKeepInfo(
+      Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfo,
+      DexDefinition definition,
+      Consumer<Joiner<?, ?, ?>> consumer) {
+    modifyMinimumKeepInfo(minimumKeepInfo, definition.getReference(), consumer);
+  }
+
+  static void modifyMinimumKeepInfo(
+      Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfo,
+      DexReference reference,
+      Consumer<Joiner<?, ?, ?>> consumer) {
+    Joiner<?, ?, ?> joiner =
+        minimumKeepInfo.computeIfAbsent(
+            reference,
+            key ->
+                key.apply(
+                    clazz -> KeepClassInfo.newEmptyJoiner(),
+                    field -> KeepFieldInfo.newEmptyJoiner(),
+                    method -> KeepMethodInfo.newEmptyJoiner()));
+    consumer.accept(joiner);
+    assert !joiner.isBottom();
+  }
+
+  static void modifyDependentMinimumKeepInfo(
+      Map<EnqueuerEvent, Map<DexReference, KeepInfo.Joiner<?, ?, ?>>> dependentMinimumKeepInfo,
+      EnqueuerEvent event,
+      DexDefinition dependent,
+      Consumer<Joiner<?, ?, ?>> consumer) {
+    Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfoForDependants =
+        dependentMinimumKeepInfo.computeIfAbsent(event, ignoreKey(IdentityHashMap::new));
+    modifyMinimumKeepInfo(minimumKeepInfoForDependants, dependent, consumer);
+  }
+
   public static class RootSetBuilder {
 
     private final AppView<? extends AppInfoWithClassHierarchy> appView;
@@ -90,8 +136,10 @@
     private final DirectMappedDexApplication application;
     private final Iterable<? extends ProguardConfigurationRule> rules;
     private final MutableItemsWithRules noShrinking = new MutableItemsWithRules();
-    private final MutableItemsWithRules softPinned = new MutableItemsWithRules();
-    private final Set<DexReference> noObfuscation = Sets.newIdentityHashSet();
+    private final Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfo =
+        new IdentityHashMap<>();
+    private final Map<EnqueuerEvent, Map<DexReference, KeepInfo.Joiner<?, ?, ?>>>
+        dependentMinimumKeepInfo = new HashMap<>();
     private final LinkedHashMap<DexReference, DexReference> reasonAsked = new LinkedHashMap<>();
     private final LinkedHashMap<DexReference, DexReference> checkDiscarded = new LinkedHashMap<>();
     private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
@@ -112,8 +160,6 @@
     private final Set<DexReference> neverPropagateValue = Sets.newIdentityHashSet();
     private final Map<DexReference, MutableItemsWithRules> dependentNoShrinking =
         new IdentityHashMap<>();
-    private final Map<DexReference, MutableItemsWithRules> dependentSoftPinned =
-        new IdentityHashMap<>();
     private final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule =
         new IdentityHashMap<>();
     private final Map<DexReference, ProguardMemberRule> mayHaveSideEffects =
@@ -184,7 +230,7 @@
       }
 
       Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
-      Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier;
+      Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier;
       if (rule instanceof ProguardKeepRule) {
         if (clazz.isNotProgramClass()) {
           return;
@@ -192,7 +238,7 @@
         switch (((ProguardKeepRule) rule).getType()) {
           case KEEP_CLASS_MEMBERS:
             // Members mentioned at -keepclassmembers always depend on their holder.
-            preconditionSupplier = ImmutableMap.of(definition -> true, clazz);
+            preconditionSupplier = ImmutableMap.of(definition -> true, clazz.asProgramClass());
             markMatchingVisibleMethods(
                 clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
             markMatchingVisibleFields(
@@ -210,7 +256,8 @@
               // Static members in -keep are pinned no matter what.
               preconditionSupplier.put(DexDefinition::isStaticMember, null);
               // Instance members may need to be kept even though the holder is not instantiated.
-              preconditionSupplier.put(definition -> !definition.isStaticMember(), clazz);
+              preconditionSupplier.put(
+                  definition -> !definition.isStaticMember(), clazz.asProgramClass());
             } else {
               // Members mentioned at -keep should always be pinned as long as that -keep rule is
               // not triggered conditionally.
@@ -231,10 +278,16 @@
       if (rule instanceof ProguardIfRule) {
         throw new Unreachable("-if rule will be evaluated separately, not here.");
       } else if (rule instanceof ProguardCheckDiscardRule) {
-        if (memberKeepRules.isEmpty()) {
+        if (!clazz.isProgramClass()) {
+          appView
+              .reporter()
+              .warning(
+                  new StringDiagnostic(
+                      "The rule `" + rule + "` matches a class not in the program."));
+        } else if (memberKeepRules.isEmpty()) {
           markClass(clazz, rule, ifRule);
         } else {
-          preconditionSupplier = ImmutableMap.of((definition -> true), clazz);
+          preconditionSupplier = ImmutableMap.of((definition -> true), clazz.asProgramClass());
           markMatchingVisibleMethods(
               clazz, memberKeepRules, rule, preconditionSupplier, true, ifRule);
           markMatchingVisibleFields(
@@ -346,11 +399,10 @@
       assert Sets.intersection(neverInline, alwaysInline).isEmpty()
               && Sets.intersection(neverInline, forceInline).isEmpty()
           : "A method cannot be marked as both -neverinline and -forceinline/-alwaysinline.";
-      assert appView.options().isMinificationEnabled() || noObfuscation.isEmpty();
       return new RootSet(
           noShrinking,
-          softPinned,
-          noObfuscation,
+          minimumKeepInfo,
+          dependentMinimumKeepInfo,
           ImmutableList.copyOf(reasonAsked.values()),
           ImmutableList.copyOf(checkDiscarded.values()),
           alwaysInline,
@@ -373,7 +425,6 @@
           noSideEffects,
           assumedValues,
           dependentNoShrinking,
-          dependentSoftPinned,
           dependentKeepClassCompatRule,
           identifierNameStrings,
           ifRules,
@@ -446,23 +497,23 @@
           neverInlineDueToSingleCaller,
           neverClassInline,
           noShrinking,
-          softPinned,
-          noObfuscation,
+          minimumKeepInfo,
+          dependentMinimumKeepInfo,
           dependentNoShrinking,
-          dependentSoftPinned,
           dependentKeepClassCompatRule,
           Lists.newArrayList(delayedRootSetActionItems));
     }
 
-    private static DexDefinition testAndGetPrecondition(
+    private static DexProgramClass testAndGetPrecondition(
         DexDefinition definition,
-        Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
+        Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier) {
       if (preconditionSupplier == null) {
         return null;
       }
-      DexDefinition precondition = null;
+      DexProgramClass precondition = null;
       boolean conditionEverMatched = false;
-      for (Entry<Predicate<DexDefinition>, DexDefinition> entry : preconditionSupplier.entrySet()) {
+      for (Entry<Predicate<DexDefinition>, DexProgramClass> entry :
+          preconditionSupplier.entrySet()) {
         if (entry.getKey().test(definition)) {
           precondition = entry.getValue();
           conditionEverMatched = true;
@@ -479,7 +530,7 @@
         DexClass clazz,
         Collection<ProguardMemberRule> memberKeepRules,
         ProguardConfigurationRule rule,
-        Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+        Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier,
         boolean includeLibraryClasses,
         ProguardIfRule ifRule) {
       Set<Wrapper<DexMethod>> methodsMarked =
@@ -492,24 +543,17 @@
           break;
         }
         // In compat mode traverse all direct methods in the hierarchy.
-        currentClass
-            .getMethodCollection()
-            .forEachDirectMethodMatching(
-                method ->
-                    currentClass == clazz
-                        || (method.isStatic() && !method.isPrivate() && !method.isInitializer())
-                        || options.forceProguardCompatibility,
-                method -> {
-                  DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
-                  markMethod(method, memberKeepRules, methodsMarked, rule, precondition, ifRule);
-                });
-        currentClass
-            .virtualMethods()
-            .forEach(
-                method -> {
-                  DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
-                  markMethod(method, memberKeepRules, methodsMarked, rule, precondition, ifRule);
-                });
+        currentClass.forEachClassMethodMatching(
+            method ->
+                method.belongsToVirtualPool()
+                    || currentClass == clazz
+                    || (method.isStatic() && !method.isPrivate() && !method.isInitializer())
+                    || options.forceProguardCompatibility,
+            method -> {
+              DexProgramClass precondition =
+                  testAndGetPrecondition(method.getDefinition(), preconditionSupplier);
+              markMethod(method, memberKeepRules, methodsMarked, rule, precondition, ifRule);
+            });
         if (currentClass.superType != null) {
           DexClass dexClass = application.definitionFor(currentClass.superType);
           if (dexClass != null) {
@@ -539,7 +583,7 @@
       private final DexProgramClass originalClazz;
       private final Collection<ProguardMemberRule> memberKeepRules;
       private final ProguardConfigurationRule context;
-      private final Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier;
+      private final Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier;
       private final ProguardIfRule ifRule;
       private final Set<Wrapper<DexMethod>> seenMethods = Sets.newHashSet();
       private final Set<DexType> seenTypes = Sets.newIdentityHashSet();
@@ -548,7 +592,7 @@
           DexProgramClass originalClazz,
           Collection<ProguardMemberRule> memberKeepRules,
           ProguardConfigurationRule context,
-          Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+          Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier,
           ProguardIfRule ifRule) {
         assert context.isProguardKeepRule();
         assert !context.asProguardKeepRule().getModifiers().allowsShrinking;
@@ -582,21 +626,24 @@
         if (originalClazz == clazz) {
           return;
         }
-        for (DexEncodedMethod method : clazz.virtualMethods()) {
-          // Check if we already added this.
-          Wrapper<DexMethod> wrapped = MethodSignatureEquivalence.get().wrap(method.getReference());
-          if (!seenMethods.add(wrapped)) {
-            continue;
-          }
-          for (ProguardMemberRule rule : memberKeepRules) {
-            if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
-              tryAndKeepMethodOnClass(method, rule);
-            }
-          }
-        }
+        clazz.forEachClassMethodMatching(
+            DexEncodedMethod::belongsToVirtualPool,
+            method -> {
+              // Check if we already added this.
+              Wrapper<DexMethod> wrapped =
+                  MethodSignatureEquivalence.get().wrap(method.getReference());
+              if (!seenMethods.add(wrapped)) {
+                return;
+              }
+              for (ProguardMemberRule rule : memberKeepRules) {
+                if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
+                  tryAndKeepMethodOnClass(method, rule);
+                }
+              }
+            });
       }
 
-      private void tryAndKeepMethodOnClass(DexEncodedMethod method, ProguardMemberRule rule) {
+      private void tryAndKeepMethodOnClass(DexClassAndMethod method, ProguardMemberRule rule) {
         SingleResolutionResult resolutionResult =
             appView
                 .appInfo()
@@ -637,7 +684,7 @@
                         context,
                         rule);
                   }
-                  DexDefinition precondition =
+                  DexProgramClass precondition =
                       testAndGetPrecondition(methodToKeep.getDefinition(), preconditionSupplier);
                   rootSetBuilder.addItemToSets(
                       methodToKeep.getDefinition(), context, rule, precondition, ifRule);
@@ -655,7 +702,7 @@
         DexClass clazz,
         Collection<ProguardMemberRule> memberKeepRules,
         ProguardConfigurationRule rule,
-        Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+        Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier,
         boolean onlyIncludeProgramClasses,
         ProguardIfRule ifRule) {
       Set<DexType> visited = new HashSet<>();
@@ -676,13 +723,13 @@
         if (!onlyIncludeProgramClasses && currentClazz.isNotProgramClass()) {
           continue;
         }
-        currentClazz
-            .virtualMethods()
-            .forEach(
-                method -> {
-                  DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
-                  markMethod(method, memberKeepRules, null, rule, precondition, ifRule);
-                });
+        currentClazz.forEachClassMethodMatching(
+            DexEncodedMethod::belongsToVirtualPool,
+            method -> {
+              DexProgramClass precondition =
+                  testAndGetPrecondition(method.getDefinition(), preconditionSupplier);
+              markMethod(method, memberKeepRules, null, rule, precondition, ifRule);
+            });
         worklist.addAll(subtypingInfo.allImmediateSubtypes(currentClazz.type));
       }
     }
@@ -691,11 +738,12 @@
         DexClass clazz,
         Collection<ProguardMemberRule> memberKeepRules,
         ProguardConfigurationRule rule,
-        Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+        Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier,
         ProguardIfRule ifRule) {
-      clazz.forEachMethod(
+      clazz.forEachClassMethod(
           method -> {
-            DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
+            DexProgramClass precondition =
+                testAndGetPrecondition(method.getDefinition(), preconditionSupplier);
             markMethod(method, memberKeepRules, null, rule, precondition, ifRule);
           });
     }
@@ -704,16 +752,17 @@
         DexClass clazz,
         Collection<ProguardMemberRule> memberKeepRules,
         ProguardConfigurationRule rule,
-        Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+        Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier,
         boolean includeLibraryClasses,
         ProguardIfRule ifRule) {
       while (clazz != null) {
         if (!includeLibraryClasses && clazz.isNotProgramClass()) {
           return;
         }
-        clazz.forEachField(
+        clazz.forEachClassField(
             field -> {
-              DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
+              DexProgramClass precondition =
+                  testAndGetPrecondition(field.getDefinition(), preconditionSupplier);
               markField(field, memberKeepRules, rule, precondition, ifRule);
             });
         clazz = clazz.superType == null ? null : application.definitionFor(clazz.superType);
@@ -724,11 +773,12 @@
         DexClass clazz,
         Collection<ProguardMemberRule> memberKeepRules,
         ProguardConfigurationRule rule,
-        Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+        Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier,
         ProguardIfRule ifRule) {
-      clazz.forEachField(
+      clazz.forEachClassField(
           field -> {
-            DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
+            DexProgramClass precondition =
+                testAndGetPrecondition(field.getDefinition(), preconditionSupplier);
             markField(field, memberKeepRules, rule, precondition, ifRule);
           });
     }
@@ -924,15 +974,13 @@
      * account.
      */
     private boolean ruleSatisfied(ProguardMemberRule rule, DexClass clazz) {
-      return ruleSatisfiedByMethods(rule, clazz.directMethods())
-          || ruleSatisfiedByMethods(rule, clazz.virtualMethods())
-          || ruleSatisfiedByFields(rule, clazz.staticFields())
-          || ruleSatisfiedByFields(rule, clazz.instanceFields());
+      return ruleSatisfiedByMethods(rule, clazz.classMethods())
+          || ruleSatisfiedByFields(rule, clazz.classFields());
     }
 
-    boolean ruleSatisfiedByMethods(ProguardMemberRule rule, Iterable<DexEncodedMethod> methods) {
+    boolean ruleSatisfiedByMethods(ProguardMemberRule rule, Iterable<DexClassAndMethod> methods) {
       if (rule.getRuleType().includesMethods()) {
-        for (DexEncodedMethod method : methods) {
+        for (DexClassAndMethod method : methods) {
           if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
             return true;
           }
@@ -941,9 +989,9 @@
       return false;
     }
 
-    boolean ruleSatisfiedByFields(ProguardMemberRule rule, Iterable<DexEncodedField> fields) {
+    boolean ruleSatisfiedByFields(ProguardMemberRule rule, Iterable<DexClassAndField> fields) {
       if (rule.getRuleType().includesFields()) {
-        for (DexEncodedField field : fields) {
+        for (DexClassAndField field : fields) {
           if (rule.matches(field, appView, this::handleMatchedAnnotation, dexStringCache)) {
             return true;
           }
@@ -954,25 +1002,34 @@
 
     static AnnotationMatchResult containsAllAnnotations(
         List<ProguardTypeMatcher> annotationMatchers, DexClass clazz) {
-      return containsAllAnnotations(annotationMatchers, clazz.annotations());
+      return containsAllAnnotations(
+          annotationMatchers, clazz, clazz.annotations(), AnnotatedKind.TYPE);
     }
 
     static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
         boolean containsAllAnnotations(
             List<ProguardTypeMatcher> annotationMatchers,
-            DexEncodedMember<D, R> member,
+            DexClassAndMember<D, R> member,
             Consumer<AnnotationMatchResult> matchedAnnotationsConsumer) {
       AnnotationMatchResult annotationMatchResult =
-          containsAllAnnotations(annotationMatchers, member.annotations());
+          containsAllAnnotations(
+              annotationMatchers,
+              member,
+              member.getAnnotations(),
+              member.isField() ? AnnotatedKind.FIELD : AnnotatedKind.METHOD);
       if (annotationMatchResult != null) {
         matchedAnnotationsConsumer.accept(annotationMatchResult);
         return true;
       }
-      if (member.isDexEncodedMethod()) {
-        DexEncodedMethod method = member.asDexEncodedMethod();
-        for (int i = 0; i < method.parameterAnnotationsList.size(); i++) {
+      if (member.isMethod()) {
+        DexClassAndMethod method = member.asMethod();
+        for (int i = 0; i < method.getParameterAnnotations().size(); i++) {
           annotationMatchResult =
-              containsAllAnnotations(annotationMatchers, method.parameterAnnotationsList.get(i));
+              containsAllAnnotations(
+                  annotationMatchers,
+                  method,
+                  method.getParameterAnnotation(i),
+                  AnnotatedKind.PARAMETER);
           if (annotationMatchResult != null) {
             matchedAnnotationsConsumer.accept(annotationMatchResult);
             return true;
@@ -983,18 +1040,25 @@
     }
 
     private static AnnotationMatchResult containsAllAnnotations(
-        List<ProguardTypeMatcher> annotationMatchers, DexAnnotationSet annotations) {
+        List<ProguardTypeMatcher> annotationMatchers,
+        Definition annotatedItem,
+        DexAnnotationSet annotations,
+        AnnotatedKind annotatedKind) {
       if (annotationMatchers.isEmpty()) {
         return AnnotationsIgnoredMatchResult.getInstance();
       }
-      List<DexAnnotation> matchedAnnotations = new ArrayList<>();
+      List<MatchedAnnotation> matchedAnnotations = new ArrayList<>();
       for (ProguardTypeMatcher annotationMatcher : annotationMatchers) {
         DexAnnotation matchedAnnotation =
             getFirstAnnotationThatMatches(annotationMatcher, annotations);
         if (matchedAnnotation == null) {
           return null;
         }
-        matchedAnnotations.add(matchedAnnotation);
+        if (annotatedItem.isProgramDefinition()) {
+          matchedAnnotations.add(
+              new MatchedAnnotation(
+                  annotatedItem.asProgramDefinition(), matchedAnnotation, annotatedKind));
+        }
       }
       return new ConcreteAnnotationMatchResult(matchedAnnotations);
     }
@@ -1010,11 +1074,11 @@
     }
 
     private void markMethod(
-        DexEncodedMethod method,
+        DexClassAndMethod method,
         Collection<ProguardMemberRule> rules,
         Set<Wrapper<DexMethod>> methodsMarked,
         ProguardConfigurationRule context,
-        DexDefinition precondition,
+        DexProgramClass precondition,
         ProguardIfRule ifRule) {
       if (methodsMarked != null
           && methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.getReference()))) {
@@ -1030,23 +1094,23 @@
           if (methodsMarked != null) {
             methodsMarked.add(MethodSignatureEquivalence.get().wrap(method.getReference()));
           }
-          addItemToSets(method, context, rule, precondition, ifRule);
+          addItemToSets(method.getDefinition(), context, rule, precondition, ifRule);
         }
       }
     }
 
     private void markField(
-        DexEncodedField field,
+        DexClassAndField field,
         Collection<ProguardMemberRule> rules,
         ProguardConfigurationRule context,
-        DexDefinition precondition,
+        DexProgramClass precondition,
         ProguardIfRule ifRule) {
       for (ProguardMemberRule rule : rules) {
         if (rule.matches(field, appView, this::handleMatchedAnnotation, dexStringCache)) {
           if (Log.ENABLED) {
             Log.verbose(getClass(), "Marking field `%s` due to `%s { %s }`.", field, context, rule);
           }
-          addItemToSets(field, context, rule, precondition, ifRule);
+          addItemToSets(field.getDefinition(), context, rule, precondition, ifRule);
         }
       }
     }
@@ -1058,7 +1122,12 @@
       addItemToSets(clazz, rule, null, null, ifRule);
     }
 
-    private void includeDescriptor(DexDefinition item, DexType type, ProguardKeepRuleBase context) {
+    // TODO(b/192636793): This needs to use the precondition.
+    private void includeDescriptor(
+        DexDefinition item,
+        DexType type,
+        ProguardKeepRuleBase context,
+        DexProgramClass precondition) {
       if (type.isVoidType()) {
         return;
       }
@@ -1068,30 +1137,33 @@
       if (type.isPrimitiveType()) {
         return;
       }
-      DexClass definition = appView.definitionFor(type);
-      if (definition == null || definition.isNotProgramClass()) {
+      DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+      if (clazz == null) {
         return;
       }
+
       // Keep the type if the item is also kept.
       dependentNoShrinking
           .computeIfAbsent(item.getReference(), x -> new MutableItemsWithRules())
           .addClassWithRule(type, context);
-      // Unconditionally add to no-obfuscation, as that is only checked for surviving items.
+
+      // Disable minification for the type.
       if (appView.options().isMinificationEnabled()) {
-        noObfuscation.add(type);
+        modifyMinimumKeepInfo(minimumKeepInfo, clazz, Joiner::disallowMinification);
       }
     }
 
-    private void includeDescriptorClasses(DexDefinition item, ProguardKeepRuleBase context) {
+    private void includeDescriptorClasses(
+        DexDefinition item, ProguardKeepRuleBase context, DexProgramClass precondition) {
       if (item.isDexEncodedMethod()) {
-        DexMethod method = item.asDexEncodedMethod().getReference();
-        includeDescriptor(item, method.proto.returnType, context);
-        for (DexType value : method.proto.parameters.values) {
-          includeDescriptor(item, value, context);
+        DexEncodedMethod method = item.asDexEncodedMethod();
+        includeDescriptor(item, method.getReturnType(), context, precondition);
+        for (DexType value : method.getParameters()) {
+          includeDescriptor(item, value, context, precondition);
         }
       } else if (item.isDexEncodedField()) {
-        DexField field = item.asDexEncodedField().getReference();
-        includeDescriptor(item, field.type, context);
+        DexEncodedField field = item.asDexEncodedField();
+        includeDescriptor(item, field.getType(), context, precondition);
       } else {
         assert item.isDexClass();
       }
@@ -1101,7 +1173,7 @@
         DexDefinition item,
         ProguardConfigurationRule context,
         ProguardMemberRule rule,
-        DexDefinition precondition,
+        DexProgramClass precondition,
         ProguardIfRule ifRule) {
       if (context instanceof ProguardKeepRule) {
         if (item.isDexEncodedField()) {
@@ -1150,8 +1222,14 @@
 
         // The reason for keeping should link to the conditional rule as a whole, if present.
         ProguardKeepRuleBase keepRule = ifRule != null ? ifRule : (ProguardKeepRuleBase) context;
+
         // The modifiers are specified on the actual keep rule (ie, the consequent/context).
         ProguardKeepRuleModifiers modifiers = ((ProguardKeepRule) context).getModifiers();
+        if (modifiers.isBottom()) {
+          // This rule is a no-op.
+          return;
+        }
+
         // In compatibility mode, for a match on instance members a referenced class becomes live.
         if (options.forceProguardCompatibility
             && !modifiers.allowsShrinking
@@ -1164,6 +1242,10 @@
             context.markAsUsed();
           }
         }
+
+        // TODO(b/192636793): Remove the noShrinking and dependentNoShrinking collections. A
+        //  prerequisite for this is that the ProguardKeepRule instances are added to the KeepInfo,
+        //  since this is needed for the -whyareyoukeeping graph.
         if (!modifiers.allowsShrinking) {
           if (precondition != null) {
             dependentNoShrinking
@@ -1173,27 +1255,63 @@
             noShrinking.addReferenceWithRule(item.getReference(), keepRule);
           }
           context.markAsUsed();
-        } else if (!modifiers.allowsOptimization) {
-          if (precondition != null) {
-            dependentSoftPinned
-                .computeIfAbsent(precondition.getReference(), x -> new MutableItemsWithRules())
-                .addReferenceWithRule(item.getReference(), keepRule);
-          } else {
-            softPinned.addReferenceWithRule(item.getReference(), keepRule);
-          }
         }
-        if (!modifiers.allowsOptimization) {
-          // The -dontoptimize flag has only effect through the keep all rule, but we still
-          // need to mark the rule as used.
+
+        EnqueuerEvent preconditionEvent;
+        if (precondition != null) {
+          preconditionEvent =
+              item.getAccessFlags().isStatic()
+                  ? new LiveClassEnqueuerEvent(precondition)
+                  : new InstantiatedClassEnqueuerEvent(precondition);
+        } else {
+          preconditionEvent = null;
+        }
+
+        if (appView.options().isAnnotationRemovalEnabled() && !modifiers.allowsAnnotationRemoval) {
+          if (precondition != null) {
+            modifyDependentMinimumKeepInfo(
+                dependentMinimumKeepInfo,
+                preconditionEvent,
+                item,
+                Joiner::disallowAnnotationRemoval);
+          } else {
+            modifyMinimumKeepInfo(minimumKeepInfo, item, Joiner::disallowAnnotationRemoval);
+          }
           context.markAsUsed();
         }
 
         if (appView.options().isMinificationEnabled() && !modifiers.allowsObfuscation) {
-          noObfuscation.add(item.getReference());
+          if (precondition != null) {
+            modifyDependentMinimumKeepInfo(
+                dependentMinimumKeepInfo, preconditionEvent, item, Joiner::disallowMinification);
+          } else {
+            modifyMinimumKeepInfo(minimumKeepInfo, item, Joiner::disallowMinification);
+          }
           context.markAsUsed();
         }
+
+        if (appView.options().isOptimizationEnabled() && !modifiers.allowsOptimization) {
+          if (precondition != null) {
+            modifyDependentMinimumKeepInfo(
+                dependentMinimumKeepInfo, preconditionEvent, item, Joiner::disallowOptimization);
+          } else {
+            modifyMinimumKeepInfo(minimumKeepInfo, item, Joiner::disallowOptimization);
+          }
+          context.markAsUsed();
+        }
+
+        if (appView.options().isShrinking() && !modifiers.allowsShrinking) {
+          if (precondition != null) {
+            modifyDependentMinimumKeepInfo(
+                dependentMinimumKeepInfo, preconditionEvent, item, Joiner::disallowShrinking);
+          } else {
+            modifyMinimumKeepInfo(minimumKeepInfo, item, Joiner::disallowShrinking);
+          }
+          context.markAsUsed();
+        }
+
         if (modifiers.includeDescriptorClasses) {
-          includeDescriptorClasses(item, keepRule);
+          includeDescriptorClasses(item, keepRule, precondition);
           context.markAsUsed();
         }
       } else if (context instanceof ProguardAssumeMayHaveSideEffectsRule) {
@@ -1409,10 +1527,9 @@
     final Set<DexMethod> neverInlineDueToSingleCaller;
     final Set<DexType> neverClassInline;
     final MutableItemsWithRules noShrinking;
-    final MutableItemsWithRules softPinned;
-    final Set<DexReference> noObfuscation;
+    final Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfo;
+    final Map<EnqueuerEvent, Map<DexReference, KeepInfo.Joiner<?, ?, ?>>> dependentMinimumKeepInfo;
     final Map<DexReference, MutableItemsWithRules> dependentNoShrinking;
-    final Map<DexReference, MutableItemsWithRules> dependentSoftPinned;
     final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
     final List<DelayedRootSetActionItem> delayedRootSetActionItems;
 
@@ -1421,20 +1538,18 @@
         Set<DexMethod> neverInlineDueToSingleCaller,
         Set<DexType> neverClassInline,
         MutableItemsWithRules noShrinking,
-        MutableItemsWithRules softPinned,
-        Set<DexReference> noObfuscation,
+        Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfo,
+        Map<EnqueuerEvent, Map<DexReference, KeepInfo.Joiner<?, ?, ?>>> dependentMinimumKeepInfo,
         Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
-        Map<DexReference, MutableItemsWithRules> dependentSoftPinned,
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
         List<DelayedRootSetActionItem> delayedRootSetActionItems) {
       this.neverInline = neverInline;
       this.neverInlineDueToSingleCaller = neverInlineDueToSingleCaller;
       this.neverClassInline = neverClassInline;
       this.noShrinking = noShrinking;
-      this.softPinned = softPinned;
-      this.noObfuscation = noObfuscation;
+      this.minimumKeepInfo = minimumKeepInfo;
+      this.dependentMinimumKeepInfo = dependentMinimumKeepInfo;
       this.dependentNoShrinking = dependentNoShrinking;
-      this.dependentSoftPinned = dependentSoftPinned;
       this.dependentKeepClassCompatRule = dependentKeepClassCompatRule;
       this.delayedRootSetActionItems = delayedRootSetActionItems;
     }
@@ -1543,6 +1658,54 @@
     Set<ProguardKeepRuleBase> getDependentKeepClassCompatRule(DexType type) {
       return dependentKeepClassCompatRule.get(type);
     }
+
+    public void forEachMinimumKeepInfo(
+        AppView<? extends AppInfoWithClassHierarchy> appView,
+        TriConsumer<EnqueuerEvent, DexProgramClass, KeepClassInfo.Joiner> classConsumer,
+        TriConsumer<EnqueuerEvent, ProgramField, KeepFieldInfo.Joiner> fieldConsumer,
+        TriConsumer<EnqueuerEvent, ProgramMethod, KeepMethodInfo.Joiner> methodConsumer) {
+      internalForEachMinimumKeepInfo(
+          appView, minimumKeepInfo, null, classConsumer, fieldConsumer, methodConsumer);
+      dependentMinimumKeepInfo.forEach(
+          (precondition, minimumKeepInfoForDependents) ->
+              internalForEachMinimumKeepInfo(
+                  appView,
+                  minimumKeepInfoForDependents,
+                  precondition,
+                  classConsumer,
+                  fieldConsumer,
+                  methodConsumer));
+    }
+
+    private static void internalForEachMinimumKeepInfo(
+        AppView<? extends AppInfoWithClassHierarchy> appView,
+        Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfo,
+        EnqueuerEvent precondition,
+        TriConsumer<EnqueuerEvent, DexProgramClass, KeepClassInfo.Joiner> classConsumer,
+        TriConsumer<EnqueuerEvent, ProgramField, KeepFieldInfo.Joiner> fieldConsumer,
+        TriConsumer<EnqueuerEvent, ProgramMethod, KeepMethodInfo.Joiner> methodConsumer) {
+      minimumKeepInfo.forEach(
+          (reference, joiner) -> {
+            DexProgramClass contextClass =
+                asProgramClassOrNull(appView.definitionFor(reference.getContextType()));
+            if (contextClass != null) {
+              reference.accept(
+                  clazz -> classConsumer.accept(precondition, contextClass, joiner.asClassJoiner()),
+                  fieldReference -> {
+                    ProgramField field = contextClass.lookupProgramField(fieldReference);
+                    if (field != null) {
+                      fieldConsumer.accept(precondition, field, joiner.asFieldJoiner());
+                    }
+                  },
+                  methodReference -> {
+                    ProgramMethod method = contextClass.lookupProgramMethod(methodReference);
+                    if (method != null) {
+                      methodConsumer.accept(precondition, method, joiner.asMethodJoiner());
+                    }
+                  });
+            }
+          });
+    }
   }
 
   abstract static class ItemsWithRules {
@@ -1596,8 +1759,7 @@
   static class MutableItemsWithRules extends ItemsWithRules {
 
     private static final ItemsWithRules EMPTY =
-        new MutableItemsWithRules(
-            Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap());
+        new MutableItemsWithRules(emptyMap(), emptyMap(), emptyMap());
 
     final Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules;
     final Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules;
@@ -1834,8 +1996,8 @@
 
     private RootSet(
         MutableItemsWithRules noShrinking,
-        MutableItemsWithRules softPinned,
-        Set<DexReference> noObfuscation,
+        Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfo,
+        Map<EnqueuerEvent, Map<DexReference, KeepInfo.Joiner<?, ?, ?>>> dependentMinimumKeepInfo,
         ImmutableList<DexReference> reasonAsked,
         ImmutableList<DexReference> checkDiscarded,
         Set<DexMethod> alwaysInline,
@@ -1858,7 +2020,6 @@
         Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects,
         Map<DexMember<?, ?>, ProguardMemberRule> assumedValues,
         Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
-        Map<DexReference, MutableItemsWithRules> dependentSoftPinned,
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
         Set<DexReference> identifierNameStrings,
         Set<ProguardIfRule> ifRules,
@@ -1868,10 +2029,9 @@
           neverInlineDueToSingleCaller,
           neverClassInline,
           noShrinking,
-          softPinned,
-          noObfuscation,
+          minimumKeepInfo,
+          dependentMinimumKeepInfo,
           dependentNoShrinking,
-          dependentSoftPinned,
           dependentKeepClassCompatRule,
           delayedRootSetActionItems);
       this.reasonAsked = reasonAsked;
@@ -1916,12 +2076,10 @@
       neverInline.addAll(consequentRootSet.neverInline);
       neverInlineDueToSingleCaller.addAll(consequentRootSet.neverInlineDueToSingleCaller);
       neverClassInline.addAll(consequentRootSet.neverClassInline);
-      noObfuscation.addAll(consequentRootSet.noObfuscation);
       if (addNoShrinking) {
         noShrinking.addAll(consequentRootSet.noShrinking);
       }
       addDependentItems(consequentRootSet.dependentNoShrinking, dependentNoShrinking);
-      addDependentItems(consequentRootSet.dependentSoftPinned, dependentSoftPinned);
       consequentRootSet.dependentKeepClassCompatRule.forEach(
           (type, rules) ->
               dependentKeepClassCompatRule
@@ -1941,32 +2099,6 @@
                   .putAll(dependence));
     }
 
-    public void copy(DexReference original, DexReference rewritten) {
-      if (noShrinking.containsReference(original)) {
-        noShrinking.putReferenceWithRules(rewritten, noShrinking.getRulesForReference(original));
-      }
-      if (noObfuscation.contains(original)) {
-        noObfuscation.add(rewritten);
-      }
-      if (original.isDexMember()) {
-        assert rewritten.isDexMember();
-        DexMember<?, ?> originalMember = original.asDexMember();
-        if (noSideEffects.containsKey(originalMember)) {
-          noSideEffects.put(rewritten.asDexMember(), noSideEffects.get(originalMember));
-        }
-        if (assumedValues.containsKey(originalMember)) {
-          assumedValues.put(rewritten.asDexMember(), assumedValues.get(originalMember));
-        }
-      }
-    }
-
-    public void prune(DexReference reference) {
-      noShrinking.removeReference(reference);
-      noObfuscation.remove(reference);
-      noSideEffects.remove(reference);
-      assumedValues.remove(reference);
-    }
-
     public void pruneDeadItems(DexDefinitionSupplier definitions, Enqueuer enqueuer) {
       pruneDeadReferences(noUnusedInterfaceRemoval, definitions, enqueuer);
       pruneDeadReferences(noVerticalClassMerging, definitions, enqueuer);
@@ -2007,13 +2139,8 @@
           });
     }
 
-    public void move(DexReference original, DexReference rewritten) {
-      copy(original, rewritten);
-      prune(original);
-    }
-
     void shouldNotBeMinified(DexReference reference) {
-      noObfuscation.add(reference);
+      modifyMinimumKeepInfo(minimumKeepInfo, reference, Joiner::disallowMinification);
     }
 
     public boolean verifyKeptFieldsAreAccessedAndLive(AppInfoWithLiveness appInfo) {
@@ -2154,7 +2281,6 @@
       StringBuilder builder = new StringBuilder();
       builder.append("RootSet");
       builder.append("\nnoShrinking: " + noShrinking.size());
-      builder.append("\nnoObfuscation: " + noObfuscation.size());
       builder.append("\nreasonAsked: " + reasonAsked.size());
       builder.append("\ncheckDiscarded: " + checkDiscarded.size());
       builder.append("\nnoSideEffects: " + noSideEffects.size());
@@ -2209,10 +2335,9 @@
         Set<DexMethod> neverInlineDueToSingleCaller,
         Set<DexType> neverClassInline,
         MutableItemsWithRules noShrinking,
-        MutableItemsWithRules softPinned,
-        Set<DexReference> noObfuscation,
+        Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfo,
+        Map<EnqueuerEvent, Map<DexReference, KeepInfo.Joiner<?, ?, ?>>> dependentMinimumKeepInfo,
         Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
-        Map<DexReference, MutableItemsWithRules> dependentSoftPinned,
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
         List<DelayedRootSetActionItem> delayedRootSetActionItems) {
       super(
@@ -2220,10 +2345,9 @@
           neverInlineDueToSingleCaller,
           neverClassInline,
           noShrinking,
-          softPinned,
-          noObfuscation,
+          minimumKeepInfo,
+          dependentMinimumKeepInfo,
           dependentNoShrinking,
-          dependentSoftPinned,
           dependentKeepClassCompatRule,
           delayedRootSetActionItems);
     }
@@ -2275,8 +2399,8 @@
         List<DelayedRootSetActionItem> delayedRootSetActionItems) {
       super(
           noShrinking,
-          new MutableItemsWithRules(),
-          Collections.emptySet(),
+          emptyMap(),
+          emptyMap(),
           reasonAsked,
           checkDiscarded,
           Collections.emptySet(),
@@ -2295,12 +2419,11 @@
           Collections.emptySet(),
           Collections.emptySet(),
           Collections.emptySet(),
-          Collections.emptyMap(),
-          Collections.emptyMap(),
-          Collections.emptyMap(),
+          emptyMap(),
+          emptyMap(),
+          emptyMap(),
           dependentNoShrinking,
-          Collections.emptyMap(),
-          Collections.emptyMap(),
+          emptyMap(),
           Collections.emptySet(),
           ifRules,
           delayedRootSetActionItems);
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 6f390ea..061eb02 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -29,7 +29,16 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature.ClassSignatureBuilder;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.GenericSignatureContextBuilder;
+import com.android.tools.r8.graph.GenericSignatureContextBuilder.TypeParameterContext;
+import com.android.tools.r8.graph.GenericSignatureCorrectnessHelper;
+import com.android.tools.r8.graph.GenericSignaturePartialTypeArgumentApplier;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
@@ -56,6 +65,7 @@
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.CollectionUtils;
 import com.android.tools.r8.utils.FieldSignatureEquivalence;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.OptionalBool;
@@ -302,6 +312,7 @@
     }
 
     // The set of targets that must remain for proper resolution error cases should not be merged.
+    // TODO(b/192821424): Can be removed if handled.
     for (DexMethod method : appInfo.getFailedMethodResolutionTargets()) {
       markTypeAsPinned(method.holder, AbortReason.RESOLUTION_FOR_METHODS_MAY_CHANGE);
     }
@@ -1078,6 +1089,9 @@
         return false;
       }
 
+      // Rewrite generic signatures before we merge fields.
+      rewriteGenericSignatures(target, source, directMethods.values(), virtualMethods.values());
+
       // Step 2: Merge fields
       Set<DexString> existingFieldNames = new HashSet<>();
       for (DexEncodedField field : target.fields()) {
@@ -1138,9 +1152,148 @@
       // Step 4: Record merging.
       mergedClasses.put(source.type, target.type);
       assert !abortMerge;
+      assert GenericSignatureCorrectnessHelper.createForVerification(
+              appView, GenericSignatureContextBuilder.createForSingleClass(appView, target))
+          .evaluateSignaturesForClass(target)
+          .isValid();
       return true;
     }
 
+    /**
+     * The rewriting of generic signatures is pretty simple, but require some bookkeeping. We take
+     * the arguments to the base type:
+     *
+     * <pre>
+     *   class Sub<X> extends Base<X, String>
+     * </pre>
+     *
+     * for
+     *
+     * <pre>
+     *   class Base<T,R> extends OtherBase<T> implements I<R> {
+     *     T t() { ... };
+     *   }
+     * </pre>
+     *
+     * and substitute T -> X and R -> String
+     */
+    private void rewriteGenericSignatures(
+        DexProgramClass target,
+        DexProgramClass source,
+        Collection<DexEncodedMethod> directMethods,
+        Collection<DexEncodedMethod> virtualMethods) {
+      ClassSignature targetSignature = target.getClassSignature();
+      if (targetSignature.hasNoSignature()) {
+        // Null out all source signatures that is moved, but do not clear out the class since this
+        // could be referred to by other generic signatures.
+        // TODO(b/147504070): If merging classes with enclosing/innerclasses, this needs to be
+        //  reconsidered.
+        directMethods.forEach(DexEncodedMethod::clearGenericSignature);
+        virtualMethods.forEach(DexEncodedMethod::clearGenericSignature);
+        source.fields().forEach(DexEncodedMember::clearGenericSignature);
+        return;
+      }
+      GenericSignaturePartialTypeArgumentApplier classApplier =
+          getGenericSignatureArgumentApplier(target, source);
+      if (classApplier == null) {
+        target.clearClassSignature();
+        target.members().forEach(DexEncodedMember::clearGenericSignature);
+        return;
+      }
+      // We could generate a substitution map.
+      ClassSignature rewrittenSource = classApplier.visitClassSignature(source.getClassSignature());
+      // The variables in the class signature is now rewritten to use the targets argument.
+      ClassSignatureBuilder builder = ClassSignature.builder();
+      builder.addFormalTypeParameters(targetSignature.getFormalTypeParameters());
+      if (!source.isInterface()) {
+        if (rewrittenSource.hasSignature()) {
+          builder.setSuperClassSignature(rewrittenSource.superClassSignature());
+        } else {
+          builder.setSuperClassSignature(new ClassTypeSignature(source.superType));
+        }
+      } else {
+        builder.setSuperClassSignature(targetSignature.superClassSignature());
+      }
+      // Compute the seen set for interfaces to add. This is similar to the merging of interfaces
+      // but allow us to maintain the type arguments.
+      Set<DexType> seenInterfaces = new HashSet<>();
+      if (source.isInterface()) {
+        seenInterfaces.add(source.type);
+      }
+      for (ClassTypeSignature iFace : targetSignature.superInterfaceSignatures()) {
+        if (seenInterfaces.add(iFace.type())) {
+          builder.addInterface(iFace);
+        }
+      }
+      if (rewrittenSource.hasSignature()) {
+        for (ClassTypeSignature iFace : rewrittenSource.superInterfaceSignatures()) {
+          if (!seenInterfaces.contains(iFace.type())) {
+            builder.addInterface(iFace);
+          }
+        }
+      } else {
+        // Synthesize raw uses of interfaces to align with the actual class
+        for (DexType iFace : source.interfaces) {
+          if (!seenInterfaces.contains(iFace)) {
+            builder.addInterface(new ClassTypeSignature(iFace));
+          }
+        }
+      }
+      target.setClassSignature(builder.build());
+
+      // Go through all type-variable references for members and update them.
+      CollectionUtils.forEach(
+          method -> {
+            MethodTypeSignature methodSignature = method.getGenericSignature();
+            if (methodSignature.hasNoSignature()) {
+              return;
+            }
+            method.setGenericSignature(
+                classApplier
+                    .buildForMethod(methodSignature.getFormalTypeParameters())
+                    .visitMethodSignature(methodSignature));
+          },
+          directMethods,
+          virtualMethods);
+
+      source.forEachField(
+          field -> {
+            if (field.getGenericSignature().hasNoSignature()) {
+              return;
+            }
+            field.setGenericSignature(
+                classApplier.visitFieldTypeSignature(field.getGenericSignature()));
+          });
+    }
+
+    private GenericSignaturePartialTypeArgumentApplier getGenericSignatureArgumentApplier(
+        DexProgramClass target, DexProgramClass source) {
+      assert target.getClassSignature().hasSignature();
+      // We can assert proper structure below because the generic signature validator has run
+      // before and pruned invalid signatures.
+      List<FieldTypeSignature> genericArgumentsToSuperType =
+          target.getClassSignature().getGenericArgumentsToSuperType(source.type);
+      if (genericArgumentsToSuperType == null) {
+        assert false : "Type should be present in generic signature";
+        return null;
+      }
+      List<FormalTypeParameter> formals = source.getClassSignature().getFormalTypeParameters();
+      if (genericArgumentsToSuperType.size() != formals.size()) {
+        assert false : "Invalid argument count to formals";
+        return null;
+      }
+      Map<String, FieldTypeSignature> substitutionMap = new HashMap<>();
+      for (int i = 0; i < formals.size(); i++) {
+        // It is OK to override a generic type variable so we just use put.
+        substitutionMap.put(formals.get(i).getName(), genericArgumentsToSuperType.get(i));
+      }
+      return GenericSignaturePartialTypeArgumentApplier.build(
+          appView,
+          TypeParameterContext.empty().addPrunedSubstitutions(substitutionMap),
+          (type1, type2) -> true,
+          type -> true);
+    }
+
     private boolean restoreDebuggingState(Stream<DexEncodedMethod> toBeDiscarded) {
       toBeDiscarded.forEach(
           method -> {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
index 467c168..f2732f6 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
@@ -122,7 +122,7 @@
         }
       }
     }
-    DexMethod newMethod = methodMap.get(previous.getReference());
+    DexMethod newMethod = methodMap.apply(previous.getReference());
     if (newMethod == null) {
       return previous;
     }
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 f9bdee5..d187792 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -319,6 +319,9 @@
       assert verifyAllHaveSameFeature(types, LegacySyntheticReference::getFeatureSplit);
       return types.get(0).getFeatureSplit();
     }
+    if (isSyntheticOfKind(type, SyntheticKind.ENUM_UNBOXING_SHARED_UTILITY_CLASS)) {
+      return FeatureSplit.BASE;
+    }
     List<SynthesizingContext> contexts = getSynthesizingContexts(type);
     if (contexts.isEmpty()) {
       return null;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index edee559..9f87beb 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -25,7 +25,8 @@
    */
   public enum SyntheticKind {
     // Class synthetics.
-    ENUM_UNBOXING_SHARED_UTILITY_CLASS("$EnumUnboxingSharedUtility", 24, false, true),
+    ENUM_UNBOXING_LOCAL_UTILITY_CLASS("$EnumUnboxingLocalUtility", 24, false, true),
+    ENUM_UNBOXING_SHARED_UTILITY_CLASS("$EnumUnboxingSharedUtility", 25, false, true),
     RECORD_TAG("", 1, false, true, true),
     COMPANION_CLASS("$-CC", 2, false, true),
     EMULATED_INTERFACE_CLASS("$-EL", 3, false, true),
@@ -39,6 +40,7 @@
     HORIZONTAL_INIT_TYPE_ARGUMENT_2(SYNTHETIC_CLASS_SEPARATOR + "IA$2", 7, false, true),
     HORIZONTAL_INIT_TYPE_ARGUMENT_3(SYNTHETIC_CLASS_SEPARATOR + "IA$3", 8, false, true),
     // Method synthetics.
+    ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD("CheckNotZero", 27, true),
     RECORD_HELPER("Record", 9, true),
     BACKPORT("Backport", 10, true),
     STATIC_INTERFACE_CALL("StaticInterfaceCall", 11, true),
@@ -49,7 +51,8 @@
     THROW_NSME("ThrowNSME", 16, true),
     TWR_CLOSE_RESOURCE("TwrCloseResource", 17, true),
     SERVICE_LOADER("ServiceLoad", 18, true),
-    OUTLINE("Outline", 19, true);
+    OUTLINE("Outline", 19, true),
+    API_CONVERSION("APIConversion", 26, true);
 
     static {
       assert verifyNoOverlappingIds();
diff --git a/src/main/java/com/android/tools/r8/utils/CollectionUtils.java b/src/main/java/com/android/tools/r8/utils/CollectionUtils.java
index 6584f77..91b0638 100644
--- a/src/main/java/com/android/tools/r8/utils/CollectionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/CollectionUtils.java
@@ -7,12 +7,21 @@
 import com.google.common.collect.ImmutableSet;
 import java.util.Collection;
 import java.util.Set;
+import java.util.function.Consumer;
 
 public class CollectionUtils {
+
   public static <T> Set<T> mergeSets(Collection<T> first, Collection<T> second) {
     ImmutableSet.Builder<T> builder = ImmutableSet.builder();
     builder.addAll(first);
     builder.addAll(second);
     return builder.build();
   }
+
+  @SafeVarargs
+  public static <T> void forEach(Consumer<T> consumer, Collection<T>... collections) {
+    for (Collection<T> collection : collections) {
+      collection.forEach(consumer);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/ImmutableArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ImmutableArrayUtils.java
new file mode 100644
index 0000000..d7c7950
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ImmutableArrayUtils.java
@@ -0,0 +1,14 @@
+// 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.utils;
+
+public class ImmutableArrayUtils {
+
+  public static <T> T[] set(T[] array, int index, T element) {
+    T[] clone = array.clone();
+    clone[index] = element;
+    return clone;
+  }
+}
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 e82e04d..c17a6ac 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -102,6 +102,9 @@
   // This makes life easier when running R8 in a debugger.
   public static final boolean DETERMINISTIC_DEBUGGING = false;
 
+  // Use a MethodCollection where most interleavings between reading and mutating is caught.
+  public static final boolean USE_METHOD_COLLECTION_CONCURRENCY_CHECKED = false;
+
   public enum LineNumberOptimization {
     OFF,
     ON
@@ -223,6 +226,7 @@
     enableValuePropagation = false;
     enableSideEffectAnalysis = false;
     enableTreeShakingOfLibraryMethodOverrides = false;
+    enableInitializedClassesAnalysis = false;
     callSiteOptimizationOptions.disableOptimization();
     horizontalClassMergerOptions.setRestrictToSynthetics();
   }
@@ -584,6 +588,11 @@
   }
 
   @Override
+  public boolean isAnnotationRemovalEnabled() {
+    return !isForceProguardCompatibilityEnabled();
+  }
+
+  @Override
   public boolean isTreeShakingEnabled() {
     return isShrinking();
   }
@@ -594,6 +603,11 @@
   }
 
   @Override
+  public boolean isOptimizationEnabled() {
+    return isOptimizing();
+  }
+
+  @Override
   public boolean isRepackagingEnabled() {
     return proguardConfiguration.getPackageObfuscationMode().isSome()
         && (isMinifying() || testing.repackageWithNoMinification);
diff --git a/src/main/java/com/android/tools/r8/utils/IterableUtils.java b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
index 89063c4..68a99e3 100644
--- a/src/main/java/com/android/tools/r8/utils/IterableUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
@@ -62,10 +62,11 @@
     return Collections.unmodifiableList(list);
   }
 
-  public static <T> T findOrDefault(Iterable<T> iterable, Predicate<T> predicate, T defaultValue) {
+  public static <T, R extends T> R findOrDefault(
+      Iterable<T> iterable, Predicate<T> predicate, R defaultValue) {
     for (T element : iterable) {
       if (predicate.test(element)) {
-        return element;
+        return (R) element;
       }
     }
     return defaultValue;
diff --git a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
index 3ecdc4f..9420440 100644
--- a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
@@ -155,6 +155,12 @@
     removeIf((Iterator<Instruction>) iterator, predicate);
   }
 
+  public static void skip(InstructionIterator iterator, int times) {
+    for (int i = 0; i < times; i++) {
+      iterator.next();
+    }
+  }
+
   public static <T> boolean allRemainingMatch(ListIterator<T> iterator, Predicate<T> predicate) {
     return !anyRemainingMatch(iterator, remaining -> !predicate.test(remaining));
   }
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 5781f91..91bfbe6 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -48,6 +48,17 @@
     return result != null ? result : defaultValue;
   }
 
+  public static <T> List<T> filter(List<T> list, Predicate<? super T> predicate) {
+    ArrayList<T> filtered = new ArrayList<>(list.size());
+    list.forEach(
+        t -> {
+          if (predicate.test(t)) {
+            filtered.add(t);
+          }
+        });
+    return filtered;
+  }
+
   public static <T> T first(List<T> list) {
     return list.get(0);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/MapUtils.java b/src/main/java/com/android/tools/r8/utils/MapUtils.java
index fc39146..2460a3c 100644
--- a/src/main/java/com/android/tools/r8/utils/MapUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapUtils.java
@@ -32,25 +32,6 @@
     return ignore -> supplier.get();
   }
 
-  public static <K, V> Map<K, V> map(
-      Map<K, V> map,
-      IntFunction<Map<K, V>> factory,
-      Function<K, K> keyMapping,
-      Function<V, V> valueMapping,
-      BiFunction<V, V, V> valueMerger) {
-    Map<K, V> result = factory.apply(map.size());
-    map.forEach(
-        (key, value) -> {
-          K newKey = keyMapping.apply(key);
-          V newValue = valueMapping.apply(value);
-          V existingValue = result.put(newKey, newValue);
-          if (existingValue != null) {
-            result.put(newKey, valueMerger.apply(existingValue, newValue));
-          }
-        });
-    return result;
-  }
-
   public static <K, V> IdentityHashMap<K, V> newIdentityHashMap(BiForEachable<K, V> forEachable) {
     IdentityHashMap<K, V> map = new IdentityHashMap<>();
     forEachable.forEach(map::put);
@@ -65,4 +46,26 @@
     return StringUtils.join(
         ",", map.entrySet(), entry -> entry.getKey() + ":" + entry.getValue(), BraceType.TUBORG);
   }
+
+  public static <K1, V1, K2, V2> Map<K2, V2> transform(
+      Map<K1, V1> map,
+      IntFunction<Map<K2, V2>> factory,
+      Function<K1, K2> keyMapping,
+      Function<V1, V2> valueMapping,
+      BiFunction<V2, V2, V2> valueMerger) {
+    Map<K2, V2> result = factory.apply(map.size());
+    map.forEach(
+        (key, value) -> {
+          K2 newKey = keyMapping.apply(key);
+          if (newKey == null) {
+            return;
+          }
+          V2 newValue = valueMapping.apply(value);
+          V2 existingValue = result.put(newKey, newValue);
+          if (existingValue != null) {
+            result.put(newKey, valueMerger.apply(existingValue, newValue));
+          }
+        });
+    return result;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramFieldEquivalence.java b/src/main/java/com/android/tools/r8/utils/ProgramFieldEquivalence.java
new file mode 100644
index 0000000..193df46
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ProgramFieldEquivalence.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.graph.ProgramField;
+import com.google.common.base.Equivalence;
+
+public class ProgramFieldEquivalence extends Equivalence<ProgramField> {
+
+  private static final ProgramFieldEquivalence INSTANCE = new ProgramFieldEquivalence();
+
+  private ProgramFieldEquivalence() {}
+
+  public static ProgramFieldEquivalence get() {
+    return INSTANCE;
+  }
+
+  @Override
+  protected boolean doEquivalent(ProgramField field, ProgramField other) {
+    return field.getDefinition() == other.getDefinition();
+  }
+
+  @Override
+  protected int doHash(ProgramField field) {
+    return field.getReference().hashCode();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/SetUtils.java b/src/main/java/com/android/tools/r8/utils/SetUtils.java
index 070e63f..01b58e7 100644
--- a/src/main/java/com/android/tools/r8/utils/SetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -13,6 +13,15 @@
 
 public class SetUtils {
 
+  public static <T> boolean containsAnyOf(Set<T> set, Iterable<T> elements) {
+    for (T element : elements) {
+      if (set.contains(element)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   public static <T> Set<T> newIdentityHashSet(T element) {
     Set<T> result = Sets.newIdentityHashSet();
     result.add(element);
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyHashMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyHashMap.java
index 66b2375..ec8f2ad 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyHashMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyHashMap.java
@@ -78,6 +78,11 @@
   }
 
   @Override
+  public K getKeyOrDefault(V value, K defaultValue) {
+    return inverse.getOrDefault(value, defaultValue);
+  }
+
+  @Override
   public Set<K> getKeys(V value) {
     K key = inverse.get(value);
     return key != null ? Collections.singleton(key) : Collections.emptySet();
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyMap.java
index addb1ed..4353088 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyMap.java
@@ -22,4 +22,6 @@
   Set<V> getOrDefault(Object key, Set<V> defaultValue);
 
   K getKey(V value);
+
+  K getKeyOrDefault(V value, K defaultValue);
 }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldMap.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldMap.java
new file mode 100644
index 0000000..24f7e05
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldMap.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.collections;
+
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.utils.ProgramFieldEquivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+
+public class ProgramFieldMap<V> extends ProgramMemberMap<ProgramField, V> {
+
+  private ProgramFieldMap(Supplier<Map<Wrapper<ProgramField>, V>> backingFactory) {
+    super(backingFactory);
+  }
+
+  public static <V> ProgramFieldMap<V> create() {
+    return new ProgramFieldMap<>(HashMap::new);
+  }
+
+  @Override
+  Wrapper<ProgramField> wrap(ProgramField method) {
+    return ProgramFieldEquivalence.get().wrap(method);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
new file mode 100644
index 0000000..43daf74
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.collections;
+
+import com.android.tools.r8.graph.ProgramMember;
+import com.google.common.base.Equivalence.Wrapper;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public abstract class ProgramMemberMap<K extends ProgramMember<?, ?>, V> {
+
+  private final Map<Wrapper<K>, V> backing;
+
+  ProgramMemberMap(Supplier<Map<Wrapper<K>, V>> backingFactory) {
+    backing = backingFactory.get();
+  }
+
+  public void clear() {
+    backing.clear();
+  }
+
+  public V computeIfAbsent(K member, Function<K, V> fn) {
+    return backing.computeIfAbsent(wrap(member), key -> fn.apply(key.get()));
+  }
+
+  public void forEach(BiConsumer<K, V> consumer) {
+    backing.forEach((wrapper, value) -> consumer.accept(wrapper.get(), value));
+  }
+
+  public V get(K member) {
+    return backing.get(wrap(member));
+  }
+
+  public boolean isEmpty() {
+    return backing.isEmpty();
+  }
+
+  public V put(K member, V value) {
+    Wrapper<K> wrapper = wrap(member);
+    return backing.put(wrapper, value);
+  }
+
+  public V remove(K member) {
+    return backing.remove(wrap(member));
+  }
+
+  public void removeIf(BiPredicate<K, V> predicate) {
+    backing.entrySet().removeIf(entry -> predicate.test(entry.getKey().get(), entry.getValue()));
+  }
+
+  abstract Wrapper<K> wrap(K member);
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java
index d49928a..a3e1b6b 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java
@@ -8,50 +8,26 @@
 import com.android.tools.r8.utils.ProgramMethodEquivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import java.util.HashMap;
-import java.util.LinkedHashMap;
 import java.util.Map;
-import java.util.function.BiConsumer;
-import java.util.function.Function;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Supplier;
 
-public class ProgramMethodMap<V> {
-
-  private final Map<Wrapper<ProgramMethod>, V> backing;
+public class ProgramMethodMap<V> extends ProgramMemberMap<ProgramMethod, V> {
 
   private ProgramMethodMap(Supplier<Map<Wrapper<ProgramMethod>, V>> backingFactory) {
-    backing = backingFactory.get();
+    super(backingFactory);
   }
 
   public static <V> ProgramMethodMap<V> create() {
     return new ProgramMethodMap<>(HashMap::new);
   }
 
-  public static <V> ProgramMethodMap<V> createLinked() {
-    return new ProgramMethodMap<>(LinkedHashMap::new);
+  public static <V> ProgramMethodMap<V> createConcurrent() {
+    return new ProgramMethodMap<>(ConcurrentHashMap::new);
   }
 
-  public void clear() {
-    backing.clear();
-  }
-
-  public V computeIfAbsent(ProgramMethod method, Function<ProgramMethod, V> fn) {
-    return backing.computeIfAbsent(wrap(method), key -> fn.apply(key.get()));
-  }
-
-  public void forEach(BiConsumer<ProgramMethod, V> consumer) {
-    backing.forEach((wrapper, value) -> consumer.accept(wrapper.get(), value));
-  }
-
-  public boolean isEmpty() {
-    return backing.isEmpty();
-  }
-
-  public V put(ProgramMethod method, V value) {
-    Wrapper<ProgramMethod> wrapper = ProgramMethodEquivalence.get().wrap(method);
-    return backing.put(wrapper, value);
-  }
-
-  private static Wrapper<ProgramMethod> wrap(ProgramMethod method) {
+  @Override
+  Wrapper<ProgramMethod> wrap(ProgramMethod method) {
     return ProgramMethodEquivalence.get().wrap(method);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
index ec2b927..468c1e9 100644
--- a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
+++ b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
@@ -31,7 +31,7 @@
   public enum KotlinCompilerVersion {
     KOTLINC_1_3_72("kotlin-compiler-1.3.72"),
     KOTLINC_1_4_20("kotlin-compiler-1.4.20"),
-    KOTLINC_1_5_0_M2("kotlin-compiler-1.5.0-M2");
+    KOTLINC_1_5_0("kotlin-compiler-1.5.0");
 
     private final String folder;
 
diff --git a/src/test/java/com/android/tools/r8/KotlinTestParameters.java b/src/test/java/com/android/tools/r8/KotlinTestParameters.java
index 57658e1..7cfcc07 100644
--- a/src/test/java/com/android/tools/r8/KotlinTestParameters.java
+++ b/src/test/java/com/android/tools/r8/KotlinTestParameters.java
@@ -93,7 +93,7 @@
           // KotlinTargetVersion java 6 is deprecated from kotlinc 1.5 and forward, no need to run
           // tests on that target.
           if (targetVersion != KotlinTargetVersion.JAVA_6
-              || kotlinc.isNot(KotlinCompilerVersion.KOTLINC_1_5_0_M2)) {
+              || kotlinc.isNot(KotlinCompilerVersion.KOTLINC_1_5_0)) {
             testParameters.add(new KotlinTestParameters(kotlinc, targetVersion, index++));
           }
         }
diff --git a/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java b/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
index 737b0ff..396e9e0 100644
--- a/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
+++ b/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
@@ -187,6 +187,10 @@
     return new Builder();
   }
 
+  public static LibraryDesugaringTestConfiguration forApiLevel(AndroidApiLevel apiLevel) {
+    return LibraryDesugaringTestConfiguration.builder().setMinApi(apiLevel).build();
+  }
+
   public boolean isEnabled() {
     return this != DISABLED;
   }
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 211df9c..1e1b55f 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -436,10 +436,6 @@
     return allowStderrMessages();
   }
 
-  public T enableCoreLibraryDesugaring(AndroidApiLevel minAPILevel) {
-    return enableCoreLibraryDesugaring(minAPILevel, null);
-  }
-
   public T enableCoreLibraryDesugaring(
       AndroidApiLevel minApiLevel, StringConsumer keepRuleConsumer) {
     return enableCoreLibraryDesugaring(
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index f3108e5..0a7edb3 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -5,7 +5,7 @@
 
 import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
 import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_4_20;
-import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0_M2;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0;
 import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.JAVA_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.isDexFile;
@@ -124,8 +124,8 @@
   public static final String JAVA_CLASSES_DIR = BUILD_DIR + "classes/java/";
   public static final String JDK_11_TESTS_CLASSES_DIR = JAVA_CLASSES_DIR + "jdk11Tests/";
 
-  public static final String ASM_JAR = BUILD_DIR + "deps/asm-9.0.jar";
-  public static final String ASM_UTIL_JAR = BUILD_DIR + "deps/asm-util-9.0.jar";
+  public static final String ASM_JAR = BUILD_DIR + "deps/asm-9.2.jar";
+  public static final String ASM_UTIL_JAR = BUILD_DIR + "deps/asm-util-9.2.jar";
 
   public static final Path API_SAMPLE_JAR = Paths.get("tests", "r8_api_usage_sample.jar");
 
@@ -2169,12 +2169,12 @@
     return new KotlinCompiler(KOTLINC_1_4_20);
   }
 
-  public static KotlinCompiler getKotlinC_1_5_0_m2() {
-    return new KotlinCompiler(KOTLINC_1_5_0_M2);
+  public static KotlinCompiler getKotlinC_1_5_0() {
+    return new KotlinCompiler(KOTLINC_1_5_0);
   }
 
   public static KotlinCompiler[] getKotlinCompilers() {
-    return new KotlinCompiler[] {getKotlinC_1_3_72(), getKotlinC_1_4_20(), getKotlinC_1_5_0_m2()};
+    return new KotlinCompiler[] {getKotlinC_1_3_72(), getKotlinC_1_4_20(), getKotlinC_1_5_0()};
   }
 
   public static void disassemble(AndroidApp app, PrintStream ps) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelDesugaredLibraryReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelDesugaredLibraryReferenceTest.java
index 38f7b45..c3c1940 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelDesugaredLibraryReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelDesugaredLibraryReferenceTest.java
@@ -41,10 +41,10 @@
   @Test
   public void testClockR8() throws Exception {
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-    Method printZone = CustomLibClass.class.getDeclaredMethod("printZone");
+    Method printZone = DesugaredLibUser.class.getDeclaredMethod("printZone");
     Method main = Executor.class.getDeclaredMethod("main", String[].class);
     testForR8(parameters.getBackend())
-        .addProgramClasses(Executor.class, CustomLibClass.class)
+        .addProgramClasses(Executor.class, DesugaredLibUser.class)
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
         .addKeepMainRule(Executor.class)
         .setMinApi(parameters.getApiLevel())
@@ -54,8 +54,7 @@
             ApiModelingTestHelper.addTracedApiReferenceLevelCallBack(
                 (reference, apiLevel) -> {
                   if (reference.equals(Reference.methodFromMethod(printZone))) {
-                    // TODO(b/191617445): This should probably always be parameters.getApiLevel()
-                    assertEquals(AndroidApiLevel.O.max(parameters.getApiLevel()), apiLevel);
+                    assertEquals(parameters.getApiLevel(), apiLevel);
                   }
                 }))
         .compile()
@@ -66,20 +65,17 @@
             shrinkDesugaredLibrary)
         .run(parameters.getRuntime(), Executor.class)
         .assertSuccessWithOutputLines("Z")
-        // TODO(b/191617445): We should always be able to inline
-        .inspect(
-            ApiModelingTestHelper.verifyThat(parameters, printZone)
-                .inlinedIntoFromApiLevel(main, AndroidApiLevel.O));
+        .inspect(ApiModelingTestHelper.verifyThat(parameters, printZone).inlinedInto(main));
   }
 
   static class Executor {
 
     public static void main(String[] args) {
-      CustomLibClass.printZone();
+      DesugaredLibUser.printZone();
     }
   }
 
-  static class CustomLibClass {
+  static class DesugaredLibUser {
 
     public static void printZone() {
       System.out.println(Clock.systemUTC().getZone());
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoDesugaredLibraryReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoDesugaredLibraryReferenceTest.java
new file mode 100644
index 0000000..e752764
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoDesugaredLibraryReferenceTest.java
@@ -0,0 +1,93 @@
+// 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.apimodel;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Method;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ApiModelNoDesugaredLibraryReferenceTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public ApiModelNoDesugaredLibraryReferenceTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    Method printZone = LibUser.class.getDeclaredMethod("printPath");
+    Method main = Executor.class.getDeclaredMethod("main", String[].class);
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Executor.class, LibUser.class)
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+        .addKeepMainRule(Executor.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(
+            ApiModelingTestHelper.addTracedApiReferenceLevelCallBack(
+                (reference, apiLevel) -> {
+                  if (reference.equals(Reference.methodFromMethod(printZone))) {
+                    assertEquals(AndroidApiLevel.O.max(parameters.getApiLevel()), apiLevel);
+                  }
+                }))
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary, parameters.getApiLevel(), keepRuleConsumer.get(), false)
+        .run(parameters.getRuntime(), Executor.class)
+        .applyIf(
+            parameters.getDexRuntimeVersion().isDalvik(),
+            result -> result.assertSuccessWithOutputLines("java.nio.file.Paths"))
+        .applyIf(
+            parameters.getDexRuntimeVersion().isInRangeInclusive(Version.V5_1_1, Version.V7_0_0),
+            result ->
+                result.assertSuccessWithOutputLines("Failed resolution of: Ljava/nio/file/Paths;"))
+        .applyIf(
+            parameters.getDexRuntimeVersion().isNewerThan(Version.V7_0_0),
+            result -> result.assertSuccessWithOutputLines("~"))
+        .inspect(
+            ApiModelingTestHelper.verifyThat(parameters, printZone)
+                .inlinedIntoFromApiLevel(main, AndroidApiLevel.O));
+  }
+
+  static class Executor {
+
+    public static void main(String[] args) {
+      try {
+        LibUser.printPath();
+      } catch (Throwable t) {
+        System.out.println(t.getMessage());
+      }
+    }
+  }
+
+  static class LibUser {
+
+    public static void printPath() {
+      // java.nio.Path is not (yet) library desugared.
+      System.out.println(Paths.get("~"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalMissingLookupTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalMissingLookupTest.java
new file mode 100644
index 0000000..e62a40e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalMissingLookupTest.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.classmerging.horizontal;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class HorizontalMissingLookupTest extends HorizontalClassMergingTestBase {
+
+  public HorizontalMissingLookupTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, A.class)
+        .addProgramClassFileData(transformer(B.class).removeMethodsWithName("foo").transform())
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A::baz", "B::bar");
+  }
+
+  public static class A {
+
+    @NeverInline
+    public static void baz() {
+      System.out.println("A::baz");
+    }
+  }
+
+  public static class B {
+    // Will be removed.
+    public static void foo() {
+      System.out.println("B::foo");
+    }
+
+    @NeverInline
+    public static void bar() {
+      System.out.println("B::bar");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      A.baz();
+      if (args.length > 0) {
+        B.foo();
+      }
+      B.bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
index b2010d7..f7921ff 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
@@ -8,6 +8,7 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -43,7 +44,8 @@
         .addKeepMainRule(TestClass.class)
         .addVerticallyMergedClassesInspector(
             inspector -> inspector.assertMergedIntoSubtype(A.class))
-        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(
+            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/desugar/ConcurrencyTest.java b/src/test/java/com/android/tools/r8/desugar/ConcurrencyTest.java
new file mode 100644
index 0000000..380127b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/ConcurrencyTest.java
@@ -0,0 +1,371 @@
+// 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.desugar;
+
+import static com.android.tools.r8.TestRuntime.CfVm.JDK11;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.objectweb.asm.Opcodes;
+
+// Test for reproducing b/192310793.
+@RunWith(Parameterized.class)
+public class ConcurrencyTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withCfRuntimesEndingAtExcluding(JDK11)
+            .withDexRuntimes()
+            .withApiLevel(AndroidApiLevel.B)
+            .build());
+  }
+
+  public Collection<Class<?>> getClasses() {
+    return ImmutableList.of(Main.class);
+  }
+
+  public Collection<byte[]> getTransformedClasses() throws Exception {
+    ClassFileTransformer transformer =
+        withNest(Host.class)
+            .setVersion(CfVersion.V1_8)
+            .transformMethodInsnInMethod(
+                "callPrivate",
+                ((opcode, owner, name, descriptor, isInterface, continuation) -> {
+                  continuation.visitMethodInsn(
+                      name.equals("hello") ? Opcodes.INVOKEVIRTUAL : opcode,
+                      owner,
+                      name,
+                      descriptor,
+                      isInterface);
+                }));
+    for (String s : new String[] {"a", "b", "c", "d", "e"}) {
+      for (int i = 0; i < 10; i++) {
+        transformer.setPrivate(Host.class.getDeclaredMethod(s + "0" + i));
+      }
+    }
+
+    return ImmutableList.of(
+        transformer.transform(),
+        withNest(A.class).transform(),
+        withNest(B.class).transform(),
+        withNest(C.class).transform(),
+        withNest(D.class).transform(),
+        withNest(E.class).transform());
+  }
+
+  private ClassFileTransformer withNest(Class<?> clazz) throws Exception {
+    return transformer(clazz).setNest(Host.class, A.class, B.class, C.class, D.class, E.class);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    try {
+      testForD8(parameters.getBackend())
+          .addProgramClasses(getClasses())
+          .addProgramClassFileData(getTransformedClasses())
+          .compile();
+    } catch (CompilationFailedException e) {
+      if (e.getCause() instanceof ArrayIndexOutOfBoundsException) {
+        // TODO(b/192310793): This should not happen.
+        return;
+      }
+      throw e;
+    }
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assumeTrue(parameters.getBackend().isDex());
+
+    try {
+      testForR8(parameters.getBackend())
+          .addProgramClasses(getClasses())
+          .addProgramClassFileData(getTransformedClasses())
+          .setMinApi(parameters.getApiLevel())
+          .addKeepAllClassesRule()
+          .compile();
+    } catch (CompilationFailedException e) {
+      if (e.getCause() instanceof AssertionError
+          && e.getCause()
+              .getStackTrace()[0]
+              .getClassName()
+              .equals(
+                  "com.android.tools.r8.ir.desugar.NonEmptyCfInstructionDesugaringCollection")) {
+        // TODO(b/192446461): This should not happen.
+        return;
+      }
+      throw e;
+    }
+  }
+
+  static class Host {
+    /* will be private */ void a00() {}
+    /* will be private */ void a01() {}
+    /* will be private */ void a02() {}
+    /* will be private */ void a03() {}
+    /* will be private */ void a04() {}
+    /* will be private */ void a05() {}
+    /* will be private */ void a06() {}
+    /* will be private */ void a07() {}
+    /* will be private */ void a08() {}
+    /* will be private */ void a09() {}
+
+    /* will be private */ void b00() {}
+    /* will be private */ void b01() {}
+    /* will be private */ void b02() {}
+    /* will be private */ void b03() {}
+    /* will be private */ void b04() {}
+    /* will be private */ void b05() {}
+    /* will be private */ void b06() {}
+    /* will be private */ void b07() {}
+    /* will be private */ void b08() {}
+    /* will be private */ void b09() {}
+
+    /* will be private */ void c00() {}
+    /* will be private */ void c01() {}
+    /* will be private */ void c02() {}
+    /* will be private */ void c03() {}
+    /* will be private */ void c04() {}
+    /* will be private */ void c05() {}
+    /* will be private */ void c06() {}
+    /* will be private */ void c07() {}
+    /* will be private */ void c08() {}
+    /* will be private */ void c09() {}
+
+    /* will be private */ void d00() {}
+    /* will be private */ void d01() {}
+    /* will be private */ void d02() {}
+    /* will be private */ void d03() {}
+    /* will be private */ void d04() {}
+    /* will be private */ void d05() {}
+    /* will be private */ void d06() {}
+    /* will be private */ void d07() {}
+    /* will be private */ void d08() {}
+    /* will be private */ void d09() {}
+
+    /* will be private */ void e00() {}
+    /* will be private */ void e01() {}
+    /* will be private */ void e02() {}
+    /* will be private */ void e03() {}
+    /* will be private */ void e04() {}
+    /* will be private */ void e05() {}
+    /* will be private */ void e06() {}
+    /* will be private */ void e07() {}
+    /* will be private */ void e08() {}
+    /* will be private */ void e09() {}
+
+    private void hello() {}
+
+    public static void callPrivate() {
+      // The private method "hello" is called with invokevirtual.
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+    }
+  }
+
+  static class A {
+    public void foo() {
+      // Will be virtual invoke to private methods.
+      new Host().a00();
+      new Host().a01();
+      new Host().a02();
+      new Host().a03();
+      new Host().a04();
+      new Host().a05();
+      new Host().a06();
+      new Host().a07();
+      new Host().a08();
+      new Host().a09();
+    }
+  }
+
+  static class B {
+    public void foo() {
+      // Will be virtual invoke to private methods.
+      new Host().b00();
+      new Host().b01();
+      new Host().b02();
+      new Host().b03();
+      new Host().b04();
+      new Host().b05();
+      new Host().b06();
+      new Host().b07();
+      new Host().b08();
+      new Host().b09();
+    }
+  }
+
+  static class C {
+    public void foo() {
+      // Will be virtual invoke to private methods.
+      new Host().c00();
+      new Host().c01();
+      new Host().c02();
+      new Host().c03();
+      new Host().c04();
+      new Host().c05();
+      new Host().c06();
+      new Host().c07();
+      new Host().c08();
+      new Host().c09();
+    }
+  }
+
+  static class D {
+    public void foo() {
+      // Will be virtual invoke to private methods.
+      new Host().d00();
+      new Host().d01();
+      new Host().d02();
+      new Host().d03();
+      new Host().d04();
+      new Host().d05();
+      new Host().d06();
+      new Host().d07();
+      new Host().d08();
+      new Host().d09();
+    }
+  }
+
+  static class E {
+    public void foo() {
+      // Will be virtual invoke to private methods.
+      new Host().e00();
+      new Host().e01();
+      new Host().e02();
+      new Host().e03();
+      new Host().e04();
+      new Host().e05();
+      new Host().e06();
+      new Host().e07();
+      new Host().e08();
+      new Host().e09();
+    }
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      new A().foo();
+      new B().foo();
+      new C().foo();
+      new D().foo();
+      new E().foo();
+      Host.callPrivate();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java
index 6e82466..72cdc59 100644
--- a/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java
@@ -13,6 +13,7 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
@@ -72,7 +73,8 @@
               rtWithoutConsumer
                   ? ToolHelper.getAndroidJar(AndroidApiLevel.B)
                   : ToolHelper.getAndroidJar(AndroidApiLevel.P))
-          .enableCoreLibraryDesugaring(parameters.getApiLevel())
+          .enableCoreLibraryDesugaring(
+              LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
           .compileWithExpectedDiagnostics(
               diagnostics -> {
                 if (rtWithoutConsumer) {
@@ -86,8 +88,6 @@
                   diagnostics.assertNoMessages();
                 }
               })
-          .addDesugaredCoreLibraryRunClassPath(
-              this::buildDesugaredLibrary, parameters.getApiLevel())
           .run(parameters.getRuntime(), TestClass.class)
           .assertSuccessWithOutput(EXPECTED);
       assertFalse(rtWithoutConsumer);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
index ddebca5..fdca331 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
@@ -126,7 +126,7 @@
       Path desugaredLib =
           getDesugaredLibraryInCF(parameters, this::configurationForLibraryCompilation);
 
-      // Run on the JVM with desuagred library on classpath.
+      // Run on the JVM with desugared library on classpath.
       testForJvm()
           .addProgramFiles(jar)
           .addRunClasspathFiles(desugaredLib)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java
index 4a4ee82..a67f335 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRunResult;
@@ -82,10 +83,9 @@
           .addProgramClasses(getClasses())
           .addProgramClassFileData(getTransforms())
           .setMinApi(parameters.getApiLevel())
-          .enableCoreLibraryDesugaring(parameters.getApiLevel())
+          .enableCoreLibraryDesugaring(
+              LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
           .compile()
-          .addDesugaredCoreLibraryRunClassPath(
-              this::buildDesugaredLibrary, parameters.getApiLevel())
           .run(parameters.getRuntime(), Main.class)
           .apply(this::checkResult);
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibraryTest.java
index e597f8e..691ef4a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibraryTest.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime;
@@ -83,10 +84,9 @@
           .setMinApi(parameters.getApiLevel())
           .addProgramClasses(CLASSES)
           .addProgramClassFileData(getTransforms())
-          .enableCoreLibraryDesugaring(parameters.getApiLevel())
+          .enableCoreLibraryDesugaring(
+              LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
           .compile()
-          .addDesugaredCoreLibraryRunClassPath(
-              this::buildDesugaredLibrary, parameters.getApiLevel())
           .run(parameters.getRuntime(), Main.class)
           .assertFailureWithErrorThatMatches(getExpectedError());
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java
index 085cd28..1f4d791 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertNotNull;
 
 import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime;
@@ -69,10 +70,9 @@
           .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
           .setMinApi(parameters.getApiLevel())
           .addInnerClasses(DefaultMethodOverrideInLibraryTest.class)
-          .enableCoreLibraryDesugaring(parameters.getApiLevel())
+          .enableCoreLibraryDesugaring(
+              LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
           .compile()
-          .addDesugaredCoreLibraryRunClassPath(
-              this::buildDesugaredLibrary, parameters.getApiLevel())
           .run(parameters.getRuntime(), Main.class)
           .apply(this::checkResult);
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java
index 6eb1418..2cc0f87 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java
@@ -10,12 +10,15 @@
 import static org.hamcrest.CoreMatchers.containsString;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
+import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.errors.DesugaredLibraryMismatchDiagnostic;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.Box;
 import java.nio.file.Path;
 import java.util.Collection;
 import org.junit.Test;
@@ -60,7 +63,7 @@
           .addProgramFiles(libraryDex)
           .addProgramClasses(TestRunner.class)
           .setMinApi(apiLevel)
-          .enableCoreLibraryDesugaring(apiLevel)
+          .enableCoreLibraryDesugaring(LibraryDesugaringTestConfiguration.forApiLevel(apiLevel))
           .compileWithExpectedDiagnostics(
               diagnostics -> {
                 diagnostics.assertNoInfos();
@@ -69,12 +72,8 @@
                         containsString(
                             "The compilation is slowed down due to a mix of class file and dex"
                                 + " file inputs in the context of desugared library.")));
-                if (apiLevel.isLessThan(AndroidApiLevel.O)) {
-                  diagnostics.assertErrorsMatch(
-                      diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
-                } else {
-                  diagnostics.assertNoMessages();
-                }
+                diagnostics.assertErrorsMatch(
+                    diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
               });
 
     } catch (CompilationFailedException e) {
@@ -97,7 +96,7 @@
         .addProgramFiles(desugaredLibrary)
         .addProgramClasses(TestRunner.class)
         .setMinApi(apiLevel)
-        .enableCoreLibraryDesugaring(apiLevel)
+        .enableCoreLibraryDesugaring(LibraryDesugaringTestConfiguration.forApiLevel(apiLevel))
         .compile();
   }
 
@@ -125,7 +124,7 @@
         .addProgramFiles(desugaredLibraryDex)
         .addProgramClasses(TestRunner.class)
         .setMinApi(apiLevel)
-        .enableCoreLibraryDesugaring(apiLevel)
+        .enableCoreLibraryDesugaring(LibraryDesugaringTestConfiguration.forApiLevel(apiLevel))
         .compile();
   }
 
@@ -136,7 +135,7 @@
         testForD8(Backend.CF)
             .addProgramClasses(Library.class)
             .setMinApi(apiLevel)
-            .enableCoreLibraryDesugaring(apiLevel)
+            .enableCoreLibraryDesugaring(LibraryDesugaringTestConfiguration.forApiLevel(apiLevel))
             .compile()
             .writeToZip();
 
@@ -148,13 +147,9 @@
           .setMinApi(apiLevel)
           .compileWithExpectedDiagnostics(
               diagnostics -> {
-                if (apiLevel.isLessThan(AndroidApiLevel.O)) {
-                  diagnostics.assertOnlyErrors();
-                  diagnostics.assertErrorsMatch(
-                      diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
-                } else {
-                  diagnostics.assertNoMessages();
-                }
+                diagnostics.assertOnlyErrors();
+                diagnostics.assertErrorsMatch(
+                    diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
               });
     } catch (CompilationFailedException e) {
     }
@@ -168,7 +163,7 @@
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
             .addProgramClasses(Library.class)
             .setMinApi(apiLevel)
-            .enableCoreLibraryDesugaring(apiLevel)
+            .enableCoreLibraryDesugaring(LibraryDesugaringTestConfiguration.forApiLevel(apiLevel))
             .compile()
             .writeToZip();
 
@@ -187,13 +182,9 @@
           .setMinApi(apiLevel)
           .compileWithExpectedDiagnostics(
               diagnostics -> {
-                if (apiLevel.isLessThan(AndroidApiLevel.O)) {
-                  diagnostics.assertOnlyErrors();
-                  diagnostics.assertErrorsMatch(
-                      diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
-                } else {
-                  diagnostics.assertNoMessages();
-                }
+                diagnostics.assertOnlyErrors();
+                diagnostics.assertErrorsMatch(
+                    diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
               });
     } catch (CompilationFailedException e) {
     }
@@ -203,24 +194,30 @@
   public void testMergeDifferentLibraryDesugarVersions() throws Exception {
     // DEX code with library desugaring using a desugared library configuration with a
     // different identifier.
-    String identifier = "my-identifier";
+    Box<DesugaredLibraryConfiguration> box = new Box<>();
     Path libraryDex =
         testForD8(Backend.DEX)
-            .applyIf(
-                apiLevel.isLessThan(AndroidApiLevel.O),
-                builder ->
-                    builder.addOptionsModification(
-                        options ->
-                            options.desugaredLibraryConfiguration =
-                                DesugaredLibraryConfiguration.builder(
-                                        options.dexItemFactory(),
-                                        options.reporter,
-                                        Origin.unknown())
-                                    .setDesugaredLibraryIdentifier(identifier)
-                                    .build()))
             .addProgramClasses(Library.class)
             .setMinApi(apiLevel)
-            .enableCoreLibraryDesugaring(apiLevel)
+            .enableCoreLibraryDesugaring(
+                LibraryDesugaringTestConfiguration.builder()
+                    .setMinApi(apiLevel)
+                    // Minimal configuration with a different identifier.
+                    .addDesugaredLibraryConfiguration(
+                        StringResource.fromString(
+                            "{"
+                                + "\"configuration_format_version\":3,"
+                                + "\"group_id\":\"my_group\","
+                                + "\"artifact_id\":\"my_artifact\","
+                                + "\"version\":\"1.0.9\","
+                                + "\"synthesized_library_classes_package_prefix\":\"my_prefix\","
+                                + "\"required_compilation_api_level\":\"30\","
+                                + "\"common_flags\":[],"
+                                + "\"library_flags\":[],"
+                                + "\"program_flags\":[]"
+                                + "}",
+                            Origin.unknown()))
+                    .build())
             .compile()
             .writeToZip();
 
@@ -239,15 +236,11 @@
           .setMinApi(apiLevel)
           .compileWithExpectedDiagnostics(
               diagnostics -> {
-                if (apiLevel.isLessThan(AndroidApiLevel.O)) {
-                  diagnostics.assertOnlyErrors();
-                  diagnostics.assertErrorsMatch(
-                      allOf(
-                          diagnosticType(DesugaredLibraryMismatchDiagnostic.class),
-                          diagnosticMessage(containsString(identifier))));
-                } else {
-                  diagnostics.assertNoMessages();
-                }
+                diagnostics.assertOnlyErrors();
+                diagnostics.assertErrorsMatch(
+                    allOf(
+                        diagnosticType(DesugaredLibraryMismatchDiagnostic.class),
+                        diagnosticMessage(containsString("my_group:my_artifact:1.0.9"))));
               });
     } catch (CompilationFailedException e) {
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DisableDesugarTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DisableDesugarTest.java
index a3c1e0f..f686167 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DisableDesugarTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DisableDesugarTest.java
@@ -9,6 +9,7 @@
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -48,7 +49,8 @@
           .addInnerClasses(DisableDesugarTest.class)
           .setMinApi(parameters.getApiLevel())
           .disableDesugaring()
-          .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+          .enableCoreLibraryDesugaring(
+              LibraryDesugaringTestConfiguration.forApiLevel(AndroidApiLevel.B))
           .compileWithExpectedDiagnostics(this::checkExpectedDiagnostics);
     } catch (CompilationFailedException e) {
       // Expected compilation failed.
@@ -65,7 +67,8 @@
           .addKeepMainRule(TestClass.class)
           .setMinApi(parameters.getApiLevel())
           .disableDesugaring()
-          .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+          .enableCoreLibraryDesugaring(
+              LibraryDesugaringTestConfiguration.forApiLevel(AndroidApiLevel.B))
           .compileWithExpectedDiagnostics(this::checkExpectedDiagnostics);
     } catch (CompilationFailedException e) {
       // Expected compilation failed.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DoubleUtilityClassTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DoubleUtilityClassTest.java
index 8ab1e6b..0737de9 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DoubleUtilityClassTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DoubleUtilityClassTest.java
@@ -7,6 +7,7 @@
 import static org.hamcrest.core.IsNot.not;
 import static org.hamcrest.core.StringContains.containsString;
 
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
@@ -40,11 +41,10 @@
       testForD8()
           .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
           .addProgramClasses(executor)
-          .enableCoreLibraryDesugaring(parameters.getApiLevel())
+          .enableCoreLibraryDesugaring(
+              LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
           .setMinApi(parameters.getApiLevel())
           .compile()
-          .addDesugaredCoreLibraryRunClassPath(
-              this::buildDesugaredLibrary, parameters.getApiLevel())
           .run(parameters.getRuntime(), executor)
           .assertSuccess()
           // Verification error on some Dalvik VMs (4,api 1;4,api 15;4.4,api 1).
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
index e5f51dd..59a6b5b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
@@ -6,6 +6,7 @@
 
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -70,7 +71,8 @@
         testForD8(Backend.CF)
             .addInnerClasses(IterableTest.class)
             .setMinApi(parameters.getApiLevel())
-            .enableCoreLibraryDesugaring(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(
+                LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
             .compile()
             .inspect(this::inspect)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterateTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterateTest.java
index 0726e49..86ef0d5 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterateTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterateTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -46,7 +47,8 @@
         testForD8(Backend.CF)
             .addInnerClasses(IterateTest.class)
             .setMinApi(parameters.getApiLevel())
-            .enableCoreLibraryDesugaring(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(
+                LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
             .compile()
             .writeToZip();
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilOptionalTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilOptionalTest.java
index 1e145c4..ccac378 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilOptionalTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilOptionalTest.java
@@ -9,6 +9,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
@@ -82,10 +83,10 @@
         .addInnerClasses(JavaUtilOptionalTest.class)
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
         .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(
+            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
         .compile()
         .inspect(this::checkRewrittenInvokes)
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(expectedOutput);
   }
@@ -97,9 +98,9 @@
         .addProgramFiles(
             Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR).resolve("backport" + JAR_EXTENSION))
         .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(
+            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
         .compile()
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
         .run(parameters.getRuntime(), "backport.OptionalBackportJava9Main")
         .assertSuccess();
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java
index ed4b6bc..d551a20 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
@@ -37,10 +38,9 @@
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
             .addInnerClasses(LinkedHashSetTest.class)
             .setMinApi(parameters.getApiLevel())
-            .enableCoreLibraryDesugaring(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(
+                LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
             .compile()
-            .addDesugaredCoreLibraryRunClassPath(
-                this::buildDesugaredLibrary, parameters.getApiLevel())
             .run(parameters.getRuntime(), Executor.class)
             .assertSuccess()
             .getStdOut();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
index 1f1b593..dd89992 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.ExtractMarker;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -66,7 +67,11 @@
               .addProgramFiles(buildPart1DesugaredLibrary(), buildPart2NoDesugaredLibrary())
               .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
               .setMinApi(parameters.getApiLevel())
-              .enableCoreLibraryDesugaring(parameters.getApiLevel())
+              .applyIf(
+                  someLibraryDesugaringRequired(),
+                  b ->
+                      b.enableCoreLibraryDesugaring(
+                          LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel())))
               .compileWithExpectedDiagnostics(this::assertError);
       assertFalse(expectError());
     } catch (CompilationFailedException e) {
@@ -75,8 +80,6 @@
     }
     assert !expectError();
     assert compileResult != null;
-    compileResult.addDesugaredCoreLibraryRunClassPath(
-        this::buildDesugaredLibrary, parameters.getApiLevel());
     compileResult
         .run(parameters.getRuntime(), Part1.class)
         .assertSuccessWithOutputLines(JAVA_RESULT);
@@ -110,7 +113,11 @@
             .addProgramFiles(buildPart1DesugaredLibrary(), shrunkenLib)
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
             .setMinApi(parameters.getApiLevel())
-            .enableCoreLibraryDesugaring(parameters.getApiLevel())
+            .applyIf(
+                someLibraryDesugaringRequired(),
+                b ->
+                    b.enableCoreLibraryDesugaring(
+                        LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel())))
             .compile()
             .writeToZip();
 
@@ -217,11 +224,13 @@
             .addProgramClasses(Part2.class)
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
             .setMinApi(parameters.getApiLevel())
-            .enableCoreLibraryDesugaring(parameters.getApiLevel())
+            .applyIf(
+                someLibraryDesugaringRequired(),
+                b ->
+                    b.enableCoreLibraryDesugaring(
+                        LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel())))
             .compile()
-            .inspectDiagnosticMessages(this::assertWarningPresent)
-            .addDesugaredCoreLibraryRunClassPath(
-                this::buildDesugaredLibrary, parameters.getApiLevel());
+            .inspectDiagnosticMessages(this::assertWarningPresent);
     if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
       compileResult
           .run(parameters.getRuntime(), Part1.class)
@@ -240,7 +249,7 @@
   }
 
   private void assertWarningPresent(TestDiagnosticMessages testDiagnosticMessages) {
-    if (parameters.getApiLevel().getLevel() > AndroidApiLevel.N.getLevel()) {
+    if (!someLibraryDesugaringRequired()) {
       return;
     }
     assertTrue(
@@ -254,7 +263,11 @@
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
         .addProgramClasses(Part1.class)
         .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .applyIf(
+            someLibraryDesugaringRequired(),
+            b ->
+                b.enableCoreLibraryDesugaring(
+                    LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel())))
         .compile()
         .writeToZip();
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MinimalInterfaceSuperTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MinimalInterfaceSuperTest.java
index 8de40b0..697253b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MinimalInterfaceSuperTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MinimalInterfaceSuperTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
@@ -49,10 +50,10 @@
         .addInnerClasses(MinimalInterfaceSuperTest.class)
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(
+            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
         .compile()
         .assertNoMessages()
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MonthTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MonthTest.java
index 2b71ee8..df67d51 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MonthTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MonthTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime;
@@ -62,9 +63,9 @@
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
         .addInnerClasses(MonthTest.class)
         .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(
+            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
         .compile()
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutput(getExpectedResult(parameters));
   }
@@ -77,9 +78,9 @@
         .addInnerClasses(MonthTest.class)
         .addKeepMainRule(MonthTest.Main.class)
         .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(
+            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
         .compile()
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutput(getExpectedResult(parameters));
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java
index e2a5d8d..4bee0d3 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java
@@ -9,6 +9,7 @@
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
@@ -94,10 +95,10 @@
     testForD8()
         .addInnerClasses(NeverMergeCoreLibDesugarClasses.class)
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
-        .setMinApi(parameters.getRuntime())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(
+            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
         .compile()
-        .addRunClasspathFiles(buildDesugaredLibrary(parameters.getApiLevel()))
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines("Hello, world!");
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDefaultMethodOverrideInLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDefaultMethodOverrideInLibraryTest.java
index 37f834a..c562357 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDefaultMethodOverrideInLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDefaultMethodOverrideInLibraryTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime;
@@ -74,10 +75,9 @@
           .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
           .setMinApi(parameters.getApiLevel())
           .addInnerClasses(NoDefaultMethodOverrideInLibraryTest.class)
-          .enableCoreLibraryDesugaring(parameters.getApiLevel())
+          .enableCoreLibraryDesugaring(
+              LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
           .compile()
-          .addDesugaredCoreLibraryRunClassPath(
-              this::buildDesugaredLibrary, parameters.getApiLevel())
           .run(parameters.getRuntime(), Main.class)
           .assertSuccessWithOutput(EXPECTED);
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SpliteratorTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SpliteratorTest.java
index 738d9d4..28387d4 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SpliteratorTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SpliteratorTest.java
@@ -6,6 +6,7 @@
 
 import static junit.framework.TestCase.assertTrue;
 
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -54,10 +55,10 @@
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
         .addInnerClasses(SpliteratorTest.class)
         .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(
+            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
         .compile()
         .inspect(this::validateInterfaces)
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutput(
             isJDK11DesugaredLibrary() ? EXPECTED_OUTPUT_JDK11 : EXPECTED_OUTPUT_JDK8);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalClassTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalClassTest.java
index f9d1828..131295c 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalClassTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalClassTest.java
@@ -68,7 +68,9 @@
         .inspectDiagnosticMessages(this::assertDiagnosis)
         .addRunClasspathFiles(customLib)
         .run(parameters.getRuntime(), Executor.class)
-        .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"));
+        .assertFailureWithErrorThatMatches(
+            containsString(
+                "conversion was impossible because of the non convertible type java.time.Year"));
   }
 
   private void assertDiagnosis(TestDiagnosticMessages d) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java
index e2b5c8b..7341aae 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
 
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
@@ -41,9 +42,9 @@
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
         .setMinApi(parameters.getApiLevel())
         .addProgramFiles(extra, convClass)
-        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(
+            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
         .compile()
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
         .run(parameters.getRuntime(), APIConversionClass.class)
         .assertSuccessWithOutput(GMT);
   }
@@ -53,7 +54,8 @@
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
         .setMinApi(parameters.getApiLevel())
         .addProgramClasses(cls)
-        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(
+            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
         .compile()
         .writeToZip();
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionErrorMessageTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionErrorMessageTest.java
index 3976a4f..043e0ff 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionErrorMessageTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionErrorMessageTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
 
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
@@ -79,9 +80,9 @@
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
         .addProgramClasses(Executor.class, MyIntUnaryOperator.class)
         .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(
+            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
         .compile()
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
         .run(parameters.getRuntime(), Executor.class)
         .assertSuccessWithOutput(getExpectedResult());
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIDesugaredLibTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIDesugaredLibTest.java
index 160726b..86bd652 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIDesugaredLibTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIDesugaredLibTest.java
@@ -6,6 +6,7 @@
 
 import static junit.framework.TestCase.assertEquals;
 
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.DexRuntime;
 import com.android.tools.r8.ToolHelper;
@@ -59,7 +60,11 @@
               .setMinApi(AndroidApiLevel.B)
               .addProgramClasses(Executor.class)
               .addLibraryClasses(CustomLibClass.class)
-              .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+              .enableCoreLibraryDesugaring(
+                  LibraryDesugaringTestConfiguration.builder()
+                      .setMinApi(AndroidApiLevel.B)
+                      .dontAddRunClasspath()
+                      .build())
               .compile()
               .addDesugaredCoreLibraryRunClassPath(
                   (AndroidApiLevel api) -> {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SuperAPIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SuperAPIConversionTest.java
index 958e8b4..657115c 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SuperAPIConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SuperAPIConversionTest.java
@@ -8,10 +8,13 @@
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
+import java.nio.file.Path;
 import java.util.List;
 import java.util.Random;
+import java.util.function.Consumer;
 import java.util.stream.IntStream;
 import org.junit.Assume;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -25,6 +28,8 @@
 
   private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
 
+  private static Path CUSTOM_LIB;
+
   @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
   public static List<Object[]> data() {
     return buildParameters(
@@ -36,6 +41,16 @@
     this.parameters = parameters;
   }
 
+  @BeforeClass
+  public static void compileCustomLib() throws Exception {
+    CUSTOM_LIB =
+        testForD8(getStaticTemp())
+            .addProgramClasses(CustomLibClass.class)
+            .setMinApi(MIN_SUPPORTED)
+            .compile()
+            .writeToZip();
+  }
+
   @Test
   public void testAPIConversionNoDesugaring() throws Exception {
     Assume.assumeTrue("No need to test twice", shrinkDesugaredLibrary);
@@ -52,7 +67,7 @@
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
-        .addInnerClasses(SuperAPIConversionTest.class)
+        .addProgramClasses(Executor.class, ParallelRandom.class)
         .setMinApi(parameters.getApiLevel())
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
@@ -71,7 +86,7 @@
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(parameters.getBackend())
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
-        .addInnerClasses(SuperAPIConversionTest.class)
+        .addProgramClasses(Executor.class, ParallelRandom.class)
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Executor.class)
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
@@ -85,6 +100,49 @@
         .assertSuccessWithOutputLines("IntStream$VivifiedWrapper");
   }
 
+  @Test
+  public void testAPIConversionDesugaringD8B192351030() throws Exception {
+    Assume.assumeFalse("TODO(b/189435770): fix", shrinkDesugaredLibrary);
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+        .addLibraryClasses(CustomLibClass.class)
+        .addProgramClasses(ExecutorB192351030.class, A.class, B.class, C.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), ExecutorB192351030.class)
+        .assertSuccessWithOutputLines("Hello, ", "world!", "C");
+  }
+
+  @Test
+  public void testAPIConversionDesugaringR8B192351030() throws Exception {
+    Assume.assumeFalse("TODO(b/189435770): fix", shrinkDesugaredLibrary);
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+        .addLibraryClasses(CustomLibClass.class)
+        .addProgramClasses(ExecutorB192351030.class, A.class, B.class, C.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(ExecutorB192351030.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), ExecutorB192351030.class)
+        .assertSuccessWithOutputLines("Hello, ", "world!", "C");
+  }
+
   static class ParallelRandom extends Random {
 
     @Override
@@ -100,4 +158,38 @@
       System.out.println(intStream.getClass().getSimpleName());
     }
   }
+
+  static class A extends CustomLibClass {
+  }
+
+  static class B extends A {}
+
+  static class C extends B {
+    void test(Consumer<String> consumer) {
+      super.m(consumer);
+    }
+
+    public void m(Consumer<String> consumer) {
+      consumer.accept("C");
+    }
+  }
+
+  static class ExecutorB192351030 {
+
+    public static void main(String[] args) {
+      new C().test(System.out::println);
+      new C().m(System.out::println);
+    }
+  }
+
+  // This class will be put at compilation time as library and on the runtime class path.
+  // This class is convenient for easy testing. Each method plays the role of methods in the
+  // platform APIs for which argument/return values need conversion.
+  static class CustomLibClass {
+
+    public void m(Consumer<String> consumer) {
+      consumer.accept("Hello, ");
+      consumer.accept("world!");
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperPlacementTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperPlacementTest.java
index c4d1f98..c4d2370 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperPlacementTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperPlacementTest.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
@@ -65,7 +66,11 @@
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
         .addProgramClasses(TestClass.class)
         .addAndroidBuildVersion()
-        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(
+            LibraryDesugaringTestConfiguration.builder()
+                .setMinApi(parameters.getApiLevel())
+                .dontAddRunClasspath()
+                .build())
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::assertNoWrappers)
@@ -88,7 +93,8 @@
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
         .addProgramClassesAndInnerClasses(clazz)
         .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(
+            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
         .compile()
         .inspect(this::assertNoWrappers)
         .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
index 537b873..8553034 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.D8TestBuilder;
 import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.R8;
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
@@ -130,7 +131,8 @@
         d8TestBuilder
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
             .setMinApi(parameters.getApiLevel())
-            .enableCoreLibraryDesugaring(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(
+                LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
             .addOptionsModification(opt -> opt.testing.trackDesugaredAPIConversions = true)
             .compile();
     TestDiagnosticMessages diagnosticMessages = compile.getDiagnosticMessages();
@@ -139,7 +141,6 @@
             || diagnosticMessages.getWarnings().stream()
                 .noneMatch(x -> x.getDiagnosticMessage().contains("andThen")));
     return compile
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
         .withArt6Plus64BitsLib()
         .withArtFrameworks();
   }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumWithCheckNotNullUserUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumWithCheckNotNullUserUnboxingTest.java
new file mode 100644
index 0000000..3a28057
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumWithCheckNotNullUserUnboxingTest.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.enumunboxing;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EnumWithCheckNotNullUserUnboxingTest extends EnumUnboxingTestBase {
+
+  @Parameter(0)
+  public EnumKeepRules enumKeepRules;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, keep: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getAllEnumKeepRules(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(
+            "Passed null check (parameter args)",
+            "Passed null check (variable e)",
+            "Passed null check (variable e)",
+            "Passed null check (variable e)");
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      Checker.checkNotNull(args, "parameter args");
+      for (MyEnum e : MyEnum.values()) {
+        Checker.checkNotNull(e, "variable e");
+      }
+    }
+  }
+
+  enum MyEnum {
+    A,
+    B,
+    C
+  }
+
+  static class Checker {
+
+    @NeverInline
+    static <T> T checkNotNull(T t, String msg) {
+      if (t != null) {
+        System.out.println("Passed null check (" + msg + ")");
+        return t;
+      }
+      System.out.println("Expected non-null, got null (" + msg + ")");
+      throw new RuntimeException();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java
index 75306f7..dd835a2 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.google.common.base.Predicates.alwaysFalse;
+import static com.google.common.base.Predicates.alwaysTrue;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -179,7 +180,8 @@
             factory,
             dexType -> dexType.toDescriptorString().equals("Lj$/util/Spliterator$OfPrimitive;"),
             Function.identity(),
-            null);
+            null,
+            alwaysTrue());
     MethodTypeSignature rewritten = rewriter.rewrite(parsedMethodSignature);
     assertNotNull(rewritten);
     assertTrue(rewritten.hasSignature());
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/FieldSignatureTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/FieldSignatureTest.java
index c17ea6b..aafeadf 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/FieldSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/FieldSignatureTest.java
@@ -122,7 +122,8 @@
             "A", "Lfoo/bar/Baz;", Origin.unknown(), factory, new Reporter());
     assertTrue(parsed.hasSignature());
     GenericSignatureTypeRewriter rewriter =
-        new GenericSignatureTypeRewriter(factory, alwaysTrue(), Function.identity(), null);
+        new GenericSignatureTypeRewriter(
+            factory, alwaysTrue(), Function.identity(), null, alwaysTrue());
     FieldTypeSignature rewrittenType = rewriter.rewrite(parsed);
     assertNotNull(rewrittenType);
     assertTrue(rewrittenType.hasNoSignature());
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepAttributesTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepAttributesTest.java
index b091f5e..fe4a189 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepAttributesTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepAttributesTest.java
@@ -111,7 +111,11 @@
       assertEquals("(TO;TM;)TI;", testMethod.getFinalSignatureAttribute());
     } else {
       assertEquals(
-          "(" + descriptor(Supplier.class) + descriptor(Predicate.class) + ")TI;",
+          "(L"
+              + binaryName(Supplier.class)
+              + "<*>;L"
+              + binaryName(Predicate.class)
+              + "<Ljava/lang/Object;>;)TI;",
           testMethod.getFinalSignatureAttribute());
     }
   }
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepReferencesPruneTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepReferencesPruneTest.java
index 177ea63..f412cdf 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepReferencesPruneTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepReferencesPruneTest.java
@@ -9,6 +9,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.graph.genericsignature.testclasses.Foo;
@@ -29,68 +30,73 @@
 @RunWith(Parameterized.class)
 public class GenericSignatureKeepReferencesPruneTest extends TestBase {
 
-  private final String[] EXPECTED =
-      new String[] {
-        Foo.class.getTypeName() + "<java.lang.String>",
-        I.class.getTypeName()
-            + "<java.lang.Integer, "
-            + Foo.class.getTypeName()
-            + "<java.lang.Integer>>",
-        I.class.getTypeName()
-            + "<java.lang.String, "
-            + Foo.class.getTypeName()
-            + "<java.lang.String>>",
-        "Hello world"
-      };
-
-  private final String[] EXPECTED_FULL_MODE =
-      new String[] {
-        "class " + Foo.class.getTypeName(),
-        "interface " + I.class.getTypeName(),
-        "interface " + I.class.getTypeName(),
-        "Hello world"
-      };
-
   private final TestParameters parameters;
   private final boolean isCompat;
+  private final boolean minify;
 
-  @Parameters(name = "{0}, isCompat: {1}")
+  @Parameters(name = "{0}, isCompat: {1}, minify: {2}")
   public static List<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        BooleanUtils.values(),
+        BooleanUtils.values());
   }
 
-  public GenericSignatureKeepReferencesPruneTest(TestParameters parameters, boolean isCompat) {
+  public GenericSignatureKeepReferencesPruneTest(
+      TestParameters parameters, boolean isCompat, boolean minify) {
     this.parameters = parameters;
     this.isCompat = isCompat;
+    this.minify = minify;
   }
 
   @Test
   public void testJvm() throws Exception {
     assumeTrue(parameters.isCfRuntime());
+    assumeTrue(isCompat);
+    assumeTrue(!minify);
     testForJvm()
         .addProgramClasses(I.class, Foo.class, J.class, K.class, L.class)
         .addProgramClassesAndInnerClasses(Main.class)
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(EXPECTED);
+        .assertSuccessWithOutputLines(getExpected(Foo.class.getTypeName(), I.class.getTypeName()));
   }
 
   @Test
   public void testR8() throws Exception {
-    (isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
-        .addProgramClasses(I.class, Foo.class, J.class, K.class, L.class)
-        .addProgramClassesAndInnerClasses(Main.class)
-        .addKeepClassAndMembersRules(Main.class)
-        .setMinApi(parameters.getApiLevel())
-        .addKeepAttributeSignature()
-        .addKeepAttributeInnerClassesAndEnclosingMethod()
-        .enableInliningAnnotations()
-        .enableNeverClassInliningAnnotations()
-        .noMinification()
-        .compile()
-        .inspect(this::inspectSignatures)
-        .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(isCompat ? EXPECTED : EXPECTED_FULL_MODE);
+    R8TestRunResult runResult =
+        (isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
+            .addProgramClasses(I.class, Foo.class, J.class, K.class, L.class)
+            .addProgramClassesAndInnerClasses(Main.class)
+            .addKeepClassAndMembersRules(Main.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepAttributeSignature()
+            .addKeepAttributeInnerClassesAndEnclosingMethod()
+            .enableInliningAnnotations()
+            .enableNeverClassInliningAnnotations()
+            .compile()
+            .inspect(this::inspectSignatures)
+            .run(parameters.getRuntime(), Main.class);
+    runResult.assertSuccess();
+    CodeInspector inspector = runResult.inspector();
+    // Foo and I exists due to the assertions in the inspection.
+    runResult.assertSuccessWithOutputLines(
+        getExpected(
+            inspector.clazz(Foo.class).getFinalName(), inspector.clazz(I.class).getFinalName()));
+  }
+
+  private String[] getExpected(String fooName, String iName) {
+    if (isCompat) {
+      return new String[] {
+        fooName + "<java.lang.String>",
+        iName + "<java.lang.Integer, " + fooName + "<java.lang.Integer>>",
+        iName + "<java.lang.String, " + fooName + "<java.lang.String>>",
+        "Hello world"
+      };
+    } else {
+      return new String[] {
+        "class " + fooName, "interface " + iName, "interface " + iName, "Hello world"
+      };
+    }
   }
 
   private void inspectSignatures(CodeInspector inspector) {
@@ -104,7 +110,7 @@
     assertEquals(
         isCompat
             ? "<T::Ljava/lang/Comparable<TT;>;R:L"
-                + binaryName(Foo.class)
+                + fooClass.getFinalBinaryName()
                 + "<TT;>;>Ljava/lang/Object;"
             : null,
         iClass.getFinalSignatureAttribute());
@@ -113,9 +119,9 @@
     assertEquals(
         isCompat
             ? "Ljava/lang/Object;L"
-                + binaryName(I.class)
+                + iClass.getFinalBinaryName()
                 + "<Ljava/lang/String;L"
-                + binaryName(Foo.class)
+                + fooClass.getFinalBinaryName()
                 + "<Ljava/lang/String;>;>;"
             : null,
         fooInnerClass.getFinalSignatureAttribute());
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
index 1fb82ab..a040b37 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
@@ -13,19 +13,23 @@
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GenericSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.GenericSignatureContextBuilder.TypeParameterContext;
 import com.android.tools.r8.graph.GenericSignaturePartialTypeArgumentApplier;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.BiPredicateUtils;
+import com.android.tools.r8.utils.MapUtils;
 import com.google.common.collect.ImmutableMap;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.BiPredicate;
@@ -113,7 +117,15 @@
         GenericSignaturePartialTypeArgumentApplier.build(
             appView,
             TypeParameterContext.empty()
-                .addPrunedSubstitutions(substitutions)
+                .addPrunedSubstitutions(
+                    MapUtils.transform(
+                        substitutions,
+                        HashMap::new,
+                        s -> s,
+                        ClassTypeSignature::new,
+                        (val1, val2) -> {
+                          throw new Unreachable("No keys should be merged");
+                        }))
                 .addLiveParameters(liveVariables),
             removedLink,
             hasFormalTypeParameters);
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedOuterRecursiveTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedOuterRecursiveTest.java
new file mode 100644
index 0000000..79534a4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedOuterRecursiveTest.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.genericsignature;
+
+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.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class GenericSignaturePrunedOuterRecursiveTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final boolean isCompat;
+
+  @Parameters(name = "{0}, isCompat: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  public GenericSignaturePrunedOuterRecursiveTest(TestParameters parameters, boolean isCompat) {
+    this.parameters = parameters;
+    this.isCompat = isCompat;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    (isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
+        .addInnerClasses(getClass())
+        .addKeepClassAndMembersRules(Foo.class)
+        .addKeepMainRule(Main.class)
+        .addKeepAttributeSignature()
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(options -> options.horizontalClassMergerOptions().disable())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Bar::enclosingMethod")
+        .inspect(this::checkSignatures);
+  }
+
+  public void checkSignatures(CodeInspector inspector) {
+    checkSignature(
+        inspector.clazz(Bar.class.getTypeName() + "$1"),
+        "L" + binaryName(Foo.class) + "<Ljava/util/List<Ljava/lang/Object;>;>;");
+  }
+
+  private void checkSignature(ClassSubject classSubject, String expectedSignature) {
+    assertThat(classSubject, isPresent());
+    assertEquals(isCompat ? expectedSignature : null, classSubject.getFinalSignatureAttribute());
+  }
+
+  public abstract static class Foo<T> {
+
+    void foo(T r) {
+      System.out.println("Hello World");
+    }
+  }
+
+  public static class Bar {
+
+    public static <T extends List<T>> Foo<T> enclosingMethod() {
+      return new Foo<T>() {
+        @Override
+        void foo(T r) {
+          System.out.println("Bar::enclosingMethod");
+        }
+      };
+    }
+
+    public static void run() {
+      enclosingMethod().foo(null);
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      Bar.run();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureVerticalMergeTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureVerticalMergeTest.java
index b8d02f6..a1cce6b 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureVerticalMergeTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureVerticalMergeTest.java
@@ -4,15 +4,17 @@
 
 package com.android.tools.r8.graph.genericsignature;
 
-import static org.hamcrest.CoreMatchers.containsString;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.R8TestRunResult;
 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.reflect.Type;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -33,26 +35,32 @@
     this.parameters = parameters;
   }
 
-  @Test(expected = CompilationFailedException.class)
+  @Test
   public void testR8() throws Exception {
-    testForR8Compat(parameters.getBackend())
-        .addInnerClasses(getClass())
-        .setMinApi(parameters.getApiLevel())
-        .addKeepMainRule(Main.class)
-        .addKeepClassRules(I.class, J.class)
-        .addKeepClassAndMembersRulesWithAllowObfuscation(Base.class)
-        .addKeepAttributeInnerClassesAndEnclosingMethod()
-        .addKeepAttributeSignature()
-        .enableInliningAnnotations()
-        .enableNeverClassInliningAnnotations()
-        .enableNoVerticalClassMergingAnnotations()
-        .addVerticallyMergedClassesInspector(
-            inspector -> inspector.assertMergedIntoSubtype(A.class))
-        .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              diagnostics.assertErrorMessageThatMatches(
-                  containsString("Super type inconsistency in generic signature"));
-            });
+    R8TestRunResult runResult =
+        testForR8Compat(parameters.getBackend())
+            .addInnerClasses(getClass())
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(Main.class)
+            .addKeepClassRules(I.class, J.class)
+            .addKeepClassAndMembersRulesWithAllowObfuscation(Base.class)
+            .addKeepAttributeInnerClassesAndEnclosingMethod()
+            .addKeepAttributeSignature()
+            .enableInliningAnnotations()
+            .enableNeverClassInliningAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
+            .addVerticallyMergedClassesInspector(
+                inspector -> inspector.assertMergedIntoSubtype(A.class))
+            .run(parameters.getRuntime(), Main.class);
+    ClassSubject baseSubject = runResult.inspector().clazz(Base.class);
+    assertThat(baseSubject, isPresent());
+    runResult.assertSuccessWithOutputLines(
+        baseSubject.getFinalName() + "<" + String.class.getTypeName() + ">",
+        J.class.getTypeName() + "<X>",
+        // TODO(b/191871201): This could be `X` if we tracked where type-variables are introduced.
+        I.class.getTypeName() + "<java.lang.Object>",
+        "I::t",
+        "B::r");
   }
 
   public interface I<T> {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index 95c8527..fd13bbf 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -9,7 +9,6 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
@@ -17,13 +16,14 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
-import com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter;
-import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.ZipUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -39,7 +39,6 @@
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
-import java.util.stream.Collectors;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -50,6 +49,7 @@
 public class R8InliningTest extends TestBase {
 
   private static final String DEFAULT_DEX_FILENAME = "classes.dex";
+  private static final String DEFAULT_JAR_FILENAME = "out.jar";
   private static final String DEFAULT_MAP_FILENAME = "proguard.map";
   private static final String NAME = "inlining";
   private static final String KEEP_RULES_FILE = ToolHelper.EXAMPLES_DIR + NAME + "/keep-rules.txt";
@@ -78,18 +78,17 @@
     return outputDir.resolve(DEFAULT_DEX_FILENAME);
   }
 
-  private List<Path> getGeneratedFiles(Path dir) throws IOException {
+  private Path getGeneratedFile(Path dir) {
     if (parameters.isDexRuntime()) {
-      return Collections.singletonList(dir.resolve(Paths.get(DEFAULT_DEX_FILENAME)));
+      return dir.resolve(DEFAULT_DEX_FILENAME);
+    } else {
+      assert parameters.isCfRuntime();
+      return dir.resolve(DEFAULT_JAR_FILENAME);
     }
-    assert parameters.isCfRuntime();
-    return Files.walk(dir)
-        .filter(f -> f.toString().endsWith(".class"))
-        .collect(Collectors.toList());
   }
 
-  private List<Path> getGeneratedFiles() throws IOException {
-    return getGeneratedFiles(outputDir);
+  private Path getGeneratedFile() {
+    return getGeneratedFile(outputDir);
   }
 
   private String getGeneratedProguardMap() {
@@ -110,38 +109,45 @@
 
   private void generateR8Version(Path out, Path mapFile, boolean inlining) throws Exception {
     assert parameters.isDexRuntime() || parameters.isCfRuntime();
-    R8Command.Builder commandBuilder =
-        R8Command.builder()
-            .addProgramFiles(getInputFile())
-            .setOutput(out, outputMode(parameters.getBackend()))
-            .addProguardConfigurationFiles(Paths.get(KEEP_RULES_FILE))
-            .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
-            .setDisableMinification(true);
-    if (mapFile != null) {
-      commandBuilder.setProguardMapOutputPath(mapFile);
-    }
-    if (parameters.isDexRuntime()) {
-      commandBuilder.setMinApiLevel(parameters.getApiLevel().getLevel());
-    }
-    if (allowAccessModification) {
-      commandBuilder.addProguardConfiguration(
-          ImmutableList.of("-allowaccessmodification"), Origin.unknown());
-    }
-    ToolHelper.allowTestProguardOptions(commandBuilder);
-    ToolHelper.runR8(
-        commandBuilder.build(),
-        o -> {
-          // Disable class inlining to prevent that the instantiation of Nullability is removed, and
-          // that the class is therefore made abstract.
-          o.enableClassInlining = false;
-          o.enableInlining = inlining;
-          o.enableInliningOfInvokesWithNullableReceivers = false;
-          o.inliningInstructionLimit = 6;
-          // Tests depend on nullability of receiver and argument in general. Learning very accurate
-          // nullability from actual usage in tests bothers what we want to test.
-          o.callSiteOptimizationOptions().disableTypePropagationForTesting();
-          o.testing.horizontallyMergedClassesConsumer = this::fixInliningNullabilityClass;
-        });
+    testForR8(parameters.getBackend())
+        .addProgramFiles(getInputFile())
+        .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+        .addKeepRuleFiles(Paths.get(KEEP_RULES_FILE))
+        .addOptionsModification(
+            o -> {
+              // Disable class inlining to prevent that the instantiation of Nullability is removed,
+              // and
+              // that the class is therefore made abstract.
+              o.enableClassInlining = false;
+              o.enableInlining = inlining;
+              o.enableInliningOfInvokesWithNullableReceivers = false;
+              o.inliningInstructionLimit = 6;
+              // Tests depend on nullability of receiver and argument in general. Learning very
+              // accurate
+              // nullability from actual usage in tests bothers what we want to test.
+              o.callSiteOptimizationOptions().disableTypePropagationForTesting();
+              o.testing.horizontallyMergedClassesConsumer = this::fixInliningNullabilityClass;
+              o.testing.horizontalClassMergingTarget =
+                  (appView, candidates, target) -> {
+                    for (DexProgramClass candidate : candidates) {
+                      if (SyntheticItemsTestUtils.isEnumUnboxingSharedUtilityClass(
+                          candidate.getClassReference())) {
+                        return candidate;
+                      }
+                    }
+                    return target;
+                  };
+            })
+        .allowAccessModification(allowAccessModification)
+        .enableProguardTestOptions()
+        .noMinification()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .applyIf(
+            parameters.isCfRuntime(),
+            result -> result.writeToZip(out.resolve(DEFAULT_JAR_FILENAME)),
+            result -> result.writeSingleDexOutputToFile(out.resolve(DEFAULT_DEX_FILENAME)))
+        .applyIf(mapFile != null, result -> result.writeProguardMap(mapFile));
   }
 
   @Before
@@ -163,7 +169,7 @@
           ToolHelper.runJava(
                   parameters.getRuntime().asCf(),
                   Collections.singletonList("-noverify"),
-                  Collections.singletonList(outputDir),
+                  Collections.singletonList(outputDir.resolve(DEFAULT_JAR_FILENAME)),
                   "inlining.Inlining")
               .stdout;
     }
@@ -199,7 +205,7 @@
   @Test
   public void checkNoInvokes() throws Throwable {
     CodeInspector inspector =
-        new CodeInspector(getGeneratedFiles(), getGeneratedProguardMap(), null);
+        new CodeInspector(ImmutableList.of(getGeneratedFile()), getGeneratedProguardMap(), null);
     ClassSubject clazz = inspector.clazz("inlining.Inlining");
 
     // Simple constant inlining.
@@ -222,13 +228,9 @@
   }
 
   private long sumOfClassFileSizes(Path dir) throws IOException {
-    long size = 0;
-    for (Path p : getGeneratedFiles(dir)) {
-      if (ZipUtils.isClassFile(p.toString())) {
-        size += p.toFile().length();
-      }
-    }
-    return size;
+    IntBox size = new IntBox();
+    ZipUtils.iter(getGeneratedFile(dir), (a, b) -> size.increment((int) a.getSize()));
+    return size.get();
   }
 
   @Test
@@ -241,11 +243,6 @@
       Path nonInlinedDexFile = nonInlinedOutputDir.resolve(DEFAULT_DEX_FILENAME);
       nonInlinedSize = Files.size(nonInlinedDexFile);
       inlinedSize = Files.size(getGeneratedDexFile());
-      final boolean ALWAYS_DUMP = false; // Used for debugging.
-      if (ALWAYS_DUMP || inlinedSize > nonInlinedSize) {
-        dump(nonInlinedDexFile, "No inlining");
-        dump(getGeneratedDexFile(), "Inlining enabled");
-      }
     } else {
       assert parameters.isCfRuntime();
       nonInlinedSize = sumOfClassFileSizes(nonInlinedOutputDir);
@@ -316,7 +313,7 @@
   @Test
   public void invokeOnNullableReceiver() throws Exception {
     CodeInspector inspector =
-        new CodeInspector(getGeneratedFiles(), getGeneratedProguardMap(), null);
+        new CodeInspector(ImmutableList.of(getGeneratedFile()), getGeneratedProguardMap(), null);
 
     // These constants describe the expected number of invoke instructions calling a possibly
     // inlined method.
@@ -353,7 +350,7 @@
   @Test
   public void invokeOnNonNullReceiver() throws Exception {
     CodeInspector inspector =
-        new CodeInspector(getGeneratedFiles(), getGeneratedProguardMap(), null);
+        new CodeInspector(ImmutableList.of(getGeneratedFile()), getGeneratedProguardMap(), null);
     ClassSubject clazz = inspector.clazz(nullabilityClass);
     assertThat(clazz.uniqueMethodWithName("conditionalOperator"), isAbsent());
 
@@ -369,19 +366,11 @@
       InstructionSubject instruction = iterator.next();
       if (instruction.isInstanceGet()) {
         ++instanceGetCount;
-      } else if (instruction.isInvoke() && !isEnumInvoke(instruction)) {
+      } else if (instruction.isInvoke()) {
         ++invokeCount;
       }
     }
     assertEquals(1, instanceGetCount);
     assertEquals(0, invokeCount);
   }
-
-  private boolean isEnumInvoke(InstructionSubject instruction) {
-    return instruction
-        .getMethod()
-        .getName()
-        .toString()
-        .startsWith(EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_METHOD_PREFIX);
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastNullForTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastNullForTypeTest.java
index 4f60021..1f7b39a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastNullForTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastNullForTypeTest.java
@@ -57,9 +57,8 @@
               assertThat(main, isPresent());
               MethodSubject mainMethod = main.uniqueMethodWithName("main");
               assertThat(mainMethod, isPresent());
-              // TODO(b/160856783): Investigate if this can be removed.
               assertEquals(
-                  1,
+                  0,
                   mainMethod
                       .streamInstructions()
                       .filter(instruction -> instruction.isCheckCast(Main.class.getTypeName()))
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java
index 70c10da..03b1280 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java
@@ -8,6 +8,7 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -39,7 +40,8 @@
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
         .addProgramClasses(TestClass.class)
         .addKeepMainRule(TestClass.class)
-        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(
+            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineInstructionWithRepeatedOperandTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineInstructionWithRepeatedOperandTest.java
new file mode 100644
index 0000000..f7208bd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineInstructionWithRepeatedOperandTest.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.ir.optimize.outliner;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Regression test for b/192023718. */
+@RunWith(Parameterized.class)
+public class OutlineInstructionWithRepeatedOperandTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8Compat(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> {
+              options.outline.minSize = 2;
+              options.outline.threshold = 2;
+            })
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("0", "0");
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      int zero = System.currentTimeMillis() > 0 ? 0 : -1;
+      System.out.println(m1(zero));
+      System.out.println(m2(zero));
+    }
+
+    @NeverInline
+    static int m1(int x) {
+      int y = x * 42;
+      return y * y * y;
+    }
+
+    @NeverInline
+    static int m2(int x) {
+      int y = x * 42;
+      return y * y * y;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
index ca5b2c0..07767cd 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.smali.SmaliBuilder;
@@ -62,18 +63,21 @@
     MyRegisterAllocator allocator = new MyRegisterAllocator(appView, code);
     // Setup live an inactive live interval with ranges [0, 10[ and [20, 30[ with only
     // uses in the first interval and which is linked to another interval.
-    LiveIntervals inactiveIntervals = new LiveIntervals(new Value(0, TypeElement.getInt(), null));
+    LiveIntervals inactiveIntervals =
+        new LiveIntervals(ensureDefinition(new Value(0, TypeElement.getInt(), null), 0));
     inactiveIntervals.addRange(new LiveRange(0, 10));
     inactiveIntervals.addUse(new LiveIntervalsUse(0, 10));
     inactiveIntervals.addUse(new LiveIntervalsUse(4, 10));
     inactiveIntervals.addRange(new LiveRange(20, 30));
     inactiveIntervals.setRegister(0);
-    LiveIntervals linked = new LiveIntervals(new Value(1, TypeElement.getInt(), null));
+    LiveIntervals linked =
+        new LiveIntervals(ensureDefinition(new Value(1, TypeElement.getInt(), null), 1));
     linked.setRegister(1);
     inactiveIntervals.link(linked);
     allocator.addInactiveIntervals(inactiveIntervals);
     // Setup an unhandled interval that overlaps the inactive interval.
-    LiveIntervals unhandledIntervals = new LiveIntervals(new Value(2, TypeElement.getInt(), null));
+    LiveIntervals unhandledIntervals =
+        new LiveIntervals(ensureDefinition(new Value(2, TypeElement.getInt(), null), 2));
     unhandledIntervals.addRange(new LiveRange(12, 24));
     // Split the overlapping inactive intervals and check that after the split, the second
     // part of the inactive interval is unhandled and will therefore get a new register
@@ -83,4 +87,10 @@
     assert allocator.getUnhandled().peek().getStart() == 20;
     assert allocator.getUnhandled().peek().getEnd() == 30;
   }
+
+  private Value ensureDefinition(Value value, int index) {
+    Argument argument = new Argument(value, index, false);
+    value.definition = argument;
+    return value;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index c8af414..e418ced 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.kotlin;
 
 import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
-import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0_M2;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -62,7 +62,7 @@
   @Test
   public void testJStyleLambdas() throws Exception {
     // TODO(b/185497606): Unable to class inline j style lambdas.
-    assumeTrue(kotlinc.isNot(KOTLINC_1_5_0_M2));
+    assumeTrue(kotlinc.isNot(KOTLINC_1_5_0));
     String mainClassName = "class_inliner_lambda_j_style.MainKt";
     runTest(
             "class_inliner_lambda_j_style",
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
index 2b2eed4..3d872c4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0_M2;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -278,7 +278,7 @@
               assertTrue(fieldSubject.getField().accessFlags.isPublic());
 
               // kotlinc 1.5 do not generate accessors for public late-init properties.
-              if (kotlinc.isNot(KOTLINC_1_5_0_M2)) {
+              if (kotlinc.isNot(KOTLINC_1_5_0)) {
                 checkMethodIsRemoved(outerClass, getterAccessor);
                 checkMethodIsRemoved(outerClass, setterAccessor);
               }
@@ -311,7 +311,7 @@
               assertTrue(fieldSubject.getField().accessFlags.isPublic());
 
               // kotlinc 1.5 do not generate accessors for public late-init properties.
-              if (kotlinc.isNot(KOTLINC_1_5_0_M2)) {
+              if (kotlinc.isNot(KOTLINC_1_5_0)) {
                 checkMethodIsRemoved(outerClass, getterAccessor);
                 checkMethodIsRemoved(outerClass, setterAccessor);
               }
@@ -376,7 +376,7 @@
   @Test
   public void testAccessorForInnerClassIsRemovedWhenNotUsed() throws Exception {
     // TODO(b/185493636): Kotlinc 1.5 generated property accessors are not removed.
-    assumeTrue(kotlinc.isNot(KOTLINC_1_5_0_M2));
+    assumeTrue(kotlinc.isNot(KOTLINC_1_5_0));
     String mainClass =
         addMainToClasspath(
             "accessors.PropertyAccessorForInnerClassKt", "noUseOfPropertyAccessorFromInnerClass");
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
index ec3722c..aebbbb2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -80,7 +80,7 @@
             inspector -> {
               if (allowAccessModification
                   && kotlinParameters.is(
-                      KotlinCompilerVersion.KOTLINC_1_5_0_M2, KotlinTargetVersion.JAVA_8)) {
+                      KotlinCompilerVersion.KOTLINC_1_5_0, KotlinTargetVersion.JAVA_8)) {
                 checkClassIsRemoved(inspector, TEST_DATA_CLASS.getClassName());
               } else {
                 ClassSubject dataClass =
@@ -131,7 +131,7 @@
             inspector -> {
               if (allowAccessModification
                   && kotlinParameters.is(
-                      KotlinCompilerVersion.KOTLINC_1_5_0_M2, KotlinTargetVersion.JAVA_8)) {
+                      KotlinCompilerVersion.KOTLINC_1_5_0, KotlinTargetVersion.JAVA_8)) {
                 checkClassIsRemoved(inspector, TEST_DATA_CLASS.getClassName());
               } else {
                 ClassSubject dataClass =
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
index 00d2418..7b02bdb 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.kotlin.lambda;
 
-import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0_M2;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0;
 import static com.android.tools.r8.ToolHelper.getKotlinAnnotationJar;
 import static com.android.tools.r8.utils.PredicateUtils.not;
 import static junit.framework.TestCase.assertEquals;
@@ -75,7 +75,7 @@
   @Test
   public void testR8() throws Exception {
     // TODO(b/185497606): Unable to merge jstyle lambda.
-    assumeTrue(kotlinc.isNot(KOTLINC_1_5_0_M2));
+    assumeTrue(kotlinc.isNot(KOTLINC_1_5_0));
     testForR8(parameters.getBackend())
         .addProgramFiles(getProgramFiles())
         .addKeepMainRule(getMainClassName())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
index deb4e5e..6bb1804 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0_M2;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0;
 import static com.android.tools.r8.ToolHelper.getKotlinAnnotationJar;
 import static com.android.tools.r8.ToolHelper.getKotlinStdlibJar;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionProperty;
@@ -140,7 +140,7 @@
     KmPropertySubject age = kmClass.kmPropertyWithUniqueName("age");
     assertThat(age, isPresent());
     assertThat(age, not(isExtensionProperty()));
-    assertEquals(kotlinc.is(KOTLINC_1_5_0_M2) ? "b:I" : "a:I", age.fieldSignature().asString());
+    assertEquals(kotlinc.is(KOTLINC_1_5_0) ? "b:I" : "a:I", age.fieldSignature().asString());
     assertEquals("getAge()I", age.getterSignature().asString());
     assertEquals("setAge(I)V", age.setterSignature().asString());
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
index 4f5f64f..d3380e6 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
@@ -164,7 +164,7 @@
             .compileRaw();
 
     assertNotEquals(0, kotlinTestCompileResult.exitCode);
-    if (kotlinc.is(KotlinCompilerVersion.KOTLINC_1_5_0_M2)) {
+    if (kotlinc.is(KotlinCompilerVersion.KOTLINC_1_5_0)) {
       assertThat(
           kotlinTestCompileResult.stderr,
           containsString(
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
index 44092e7..263b087 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
@@ -50,8 +50,8 @@
       return 597;
     } else if (kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_4_20)) {
       return 685;
-    } else if (kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_5_0_M2)) {
-      return 694;
+    } else if (kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_5_0)) {
+      return 696;
     } else {
       throw new Unreachable("Should not compile in this configuration");
     }
@@ -62,8 +62,8 @@
       return 327;
     } else if (kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_4_20)) {
       return 413;
-    } else if (kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_5_0_M2)) {
-      return 417;
+    } else if (kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_5_0)) {
+      return 419;
     } else {
       throw new Unreachable("Should not compile in this configuration");
     }
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
index e25fd15..80d865e 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
@@ -74,6 +74,6 @@
     // TODO(b/179994975): Kotlin enum changed in 1.5.
     assertThat(
         enumClass.clinit(),
-        kotlinc.is(KotlinCompilerVersion.KOTLINC_1_5_0_M2) ? isPresent() : isAbsent());
+        kotlinc.is(KotlinCompilerVersion.KOTLINC_1_5_0) ? isPresent() : isAbsent());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java b/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
index bd10054..a3ae2e9 100644
--- a/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
+++ b/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -47,9 +46,7 @@
   }
 
   private void checkSignature(CodeInspector inspector, String signature) {
-    String fooImplFinalDescriptor =
-        DescriptorUtils.javaTypeToDescriptor(inspector.clazz(FooImpl.class).getFinalName());
-    assertEquals("()" + fooImplFinalDescriptor, signature);
+    assertEquals("()" + inspector.clazz(FooImpl.class).getFinalDescriptor(), signature);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/regress/b191296688/Regress191296688.java b/src/test/java/com/android/tools/r8/regress/b191296688/Regress191296688.java
index dc7458c..e5ed600 100644
--- a/src/test/java/com/android/tools/r8/regress/b191296688/Regress191296688.java
+++ b/src/test/java/com/android/tools/r8/regress/b191296688/Regress191296688.java
@@ -42,7 +42,7 @@
         getTestParameters().withDexRuntimes().withAllApiLevels().build(),
         getKotlinTestParameters()
             .withTargetVersion(KotlinTargetVersion.JAVA_8)
-            .withCompiler(ToolHelper.getKotlinC_1_5_0_m2())
+            .withCompiler(ToolHelper.getKotlinC_1_5_0())
             .build());
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index 926b2c0..d98725d 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -197,14 +197,12 @@
                   inspector.clazz(CLASS_WITH_ANNOTATED_METHOD).allMethods();
               assertEquals(
                   1, methods.stream().filter(m -> m.getOriginalName().equals("<init>")).count());
-              // TODO(b/132318609): This should have the annotated method, but does not due to the
-              //  annotation being removed.
               assertEquals(
-                  0,
+                  1,
                   methods.stream()
                       .filter(m -> m.getOriginalName().equals(ANNOTATED_METHOD))
                       .count());
-              assertEquals(1, methods.size());
+              assertEquals(2, methods.size());
             });
   }
 
@@ -216,8 +214,6 @@
             .addProgramFiles(R8_JAR)
             .addKeepMainRule(R8.class)
             .addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
-            // TODO(b/132318609): Remove keep annotation once fixed.
-            .addKeepClassRules(PRESENT_ANNOTATION)
             .addKeepRules("-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
             .addDontWarnGoogle()
             .addDontWarnJavaxNullableAnnotation()
@@ -231,8 +227,6 @@
             .addProgramFiles(R8_JAR)
             .addKeepMainRule(R8.class)
             .addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
-            // TODO(b/132318609): Remove keep annotation once fixed.
-            .addKeepClassRules(PRESENT_ANNOTATION)
             .addKeepRules(
                 "-if class * "
                     + "-keepclassmembers class <1> { @"
@@ -251,8 +245,6 @@
             .addProgramFiles(R8_JAR)
             .addKeepMainRule(R8.class)
             .addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
-            // TODO(b/132318609): Remove keep annotation once fixed.
-            .addKeepClassRules(PRESENT_ANNOTATION)
             .addKeepRules(
                 "-if class * "
                     + "-keepclasseswithmembers class <1> { @"
@@ -271,8 +263,6 @@
             .addProgramFiles(R8_JAR)
             .addKeepMainRule(R8.class)
             .addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
-            // TODO(b/132318609): Remove keep annotation once fixed.
-            .addKeepClassRules(PRESENT_ANNOTATION)
             .addKeepRules(
                 "-if class * { @"
                     + PRESENT_ANNOTATION
diff --git a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingCompatibilityTest.java
index f198845..e6e8b69 100644
--- a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingCompatibilityTest.java
@@ -64,8 +64,6 @@
     if (shrinker.isR8()) {
       run(
           testForR8(parameters.getBackend())
-              // Allowing all of shrinking, optimization and obfuscation will amount to a nop rule.
-              .allowUnusedProguardConfigurationRules(allowOptimization && allowObfuscation)
               .enableNoHorizontalClassMergingAnnotations());
     } else {
       run(
diff --git a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassFieldsAllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassFieldsAllowShrinkingCompatibilityTest.java
index 8b01419..314ea33 100644
--- a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassFieldsAllowShrinkingCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassFieldsAllowShrinkingCompatibilityTest.java
@@ -63,10 +63,7 @@
   @Test
   public void test() throws Exception {
     if (shrinker.isR8()) {
-      run(
-          testForR8(parameters.getBackend())
-              // Allowing all of shrinking, optimization and obfuscation will amount to a nop rule.
-              .allowUnusedProguardConfigurationRules(allowOptimization && allowObfuscation));
+      run(testForR8(parameters.getBackend()));
     } else {
       run(testForProguard(shrinker.getProguardVersion()).addDontWarn(getClass()));
     }
diff --git a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassMethodsAllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassMethodsAllowShrinkingCompatibilityTest.java
index 5a1d0f9..5305ced 100644
--- a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassMethodsAllowShrinkingCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassMethodsAllowShrinkingCompatibilityTest.java
@@ -64,8 +64,6 @@
     if (shrinker.isR8()) {
       run(
           testForR8(parameters.getBackend())
-              // Allowing all of shrinking, optimization and obfuscation will amount to a nop rule.
-              .allowUnusedProguardConfigurationRules(allowOptimization && allowObfuscation)
               .enableNoHorizontalClassMergingAnnotations());
     } else {
       run(
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/KeepAllowAnnotationRemovalTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/KeepAllowAnnotationRemovalTest.java
new file mode 100644
index 0000000..a47afde
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/KeepAllowAnnotationRemovalTest.java
@@ -0,0 +1,71 @@
+// 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.shaking.annotations;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KeepAllowAnnotationRemovalTest extends TestBase {
+
+  @Parameter(0)
+  public boolean enableCompatibilityMode;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, compat: {0}")
+  public static List<Object[]> parameters() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8Compat(parameters.getBackend(), enableCompatibilityMode)
+        .addProgramClasses(Main.class, Keep.class)
+        .addKeepRules(
+            "-keep,allowannotationremoval @" + Keep.class.getTypeName() + " class * {",
+            "  public static void main(java.lang.String[]);",
+            "}")
+        .addKeepRuntimeInvisibleAnnotations()
+        .enableProguardTestOptions()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject classSubject = inspector.clazz(Main.class);
+              assertThat(classSubject, isPresent());
+              assertThat(
+                  classSubject.annotation(Keep.class),
+                  onlyIf(enableCompatibilityMode, isPresent()));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .apply(
+            result ->
+                result.assertSuccessWithOutputLines(
+                    result.inspector().clazz(Keep.class).getFinalName()));
+  }
+
+  @Keep
+  static class Main {
+    public static void main(String[] args) {
+      System.out.println(Keep.class.getName());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/KeepDisallowAnnotationRemovalAllowOptimizationTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/KeepDisallowAnnotationRemovalAllowOptimizationTest.java
new file mode 100644
index 0000000..8fa7bc1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/KeepDisallowAnnotationRemovalAllowOptimizationTest.java
@@ -0,0 +1,97 @@
+// 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.shaking.annotations;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.containsThrow;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KeepDisallowAnnotationRemovalAllowOptimizationTest extends TestBase {
+
+  @Parameter(0)
+  public boolean enableCompatibilityMode;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, compat: {0}")
+  public static List<Object[]> parameters() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8Compat(parameters.getBackend(), enableCompatibilityMode)
+        .addProgramClasses(Main.class)
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(NeverInline.class)
+        .addKeepRules(
+            "-keepclassmembers,allowobfuscation,allowoptimization,allowshrinking class * {",
+            "  static java.lang.Object getNonNull();",
+            "}")
+        .addKeepRuntimeInvisibleAnnotations()
+        // In compatibility mode the rule above is a no-op.
+        .allowUnusedProguardConfigurationRules(enableCompatibilityMode)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject classSubject = inspector.clazz(Main.class);
+              assertThat(classSubject, isPresent());
+
+              // The annotation on getNonNull() is kept meanwhile it is subject to other
+              // optimizations.
+              MethodSubject getNonNullSubject = classSubject.uniqueMethodWithName("getNonNull");
+              assertThat(getNonNullSubject, isPresentAndRenamed());
+              assertThat(getNonNullSubject.annotation(NeverInline.class), isPresent());
+
+              // Check that the code has been optimized using the fact that getNonNull() returns a
+              // non-null value.
+              assertThat(classSubject.uniqueMethodWithName("dead"), isAbsent());
+              assertThat(classSubject.mainMethod(), not(containsThrow()));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("getNonNull()");
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      Object o = getNonNull();
+      if (o == null) {
+        dead();
+      }
+    }
+
+    @NeverInline
+    static Object getNonNull() {
+      System.out.println("getNonNull()");
+      return new Object();
+    }
+
+    @NeverInline
+    static void dead() {
+      throw new RuntimeException();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/SerializedNameAlternateTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/SerializedNameAlternateTest.java
index 6913147..de259e5 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/SerializedNameAlternateTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/SerializedNameAlternateTest.java
@@ -66,7 +66,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public SerializedNameAlternateTest(TestParameters parameters) {
@@ -83,7 +83,14 @@
             .addKeepMainRule(Main.class)
             .addKeepClassRules(Foo.class)
             .addKeepClassRules(SerializedName.class)
-            .setMinApi(parameters.getRuntime())
+            .addKeepRules(
+                // Non-compat mode only retains annotations for items matched by a -keep rule.
+                "-keepclassmembers,allowobfuscation,allowshrinking class "
+                    + Foo.class.getTypeName()
+                    + " {",
+                "  @" + SerializedName.class.getTypeName() + " <fields>;",
+                "}")
+            .setMinApi(parameters.getApiLevel())
             .noMinification()
             .run(parameters.getRuntime(), Main.class);
     checkRunResult(result);
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
index edbcd98..dfe70d5 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
@@ -54,11 +54,7 @@
     CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     verifyClassesAbsent(codeInspector,
         UnusedAnnotation.class, UnusedAnnotationDependent.class);
-    if (shrinker.isFullModeR8()) {
-      verifyClassesAbsent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
-    } else {
-      verifyClassesPresent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
-    }
+    verifyClassesPresent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
   }
 
   @Test
@@ -83,11 +79,7 @@
     CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     verifyClassesAbsent(codeInspector,
         UnusedAnnotation.class, UnusedAnnotationDependent.class);
-    if (shrinker.isFullModeR8()) {
-      verifyClassesAbsent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
-    } else {
-      verifyClassesPresent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
-    }
+    verifyClassesPresent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
   }
 
   @Test
@@ -110,11 +102,6 @@
     CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     verifyClassesAbsent(codeInspector,
         UnusedAnnotation.class, UnusedAnnotationDependent.class);
-    if (shrinker.isFullModeR8()) {
-      verifyClassesAbsent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
-    } else {
-      verifyClassesPresent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
-    }
+    verifyClassesPresent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
   }
-
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/UnsatisfiedDependentNoObfuscationTest.java b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/UnsatisfiedDependentNoObfuscationTest.java
new file mode 100644
index 0000000..a146368
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/UnsatisfiedDependentNoObfuscationTest.java
@@ -0,0 +1,72 @@
+// 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.shaking.keepclassmembers;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class UnsatisfiedDependentNoObfuscationTest 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)
+        .addKeepRules(
+            // TODO(b/192636793): This rule should not have any impact on the compilation, since
+            //  GreeterConsumer is dead.
+            "-keepclassmembers,includedescriptorclasses class "
+                + GreeterConsumer.class.getTypeName()
+                + " {",
+            "  <methods>;",
+            "}")
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              // TODO(b/192636793): This should be renamed.
+              assertThat(inspector.clazz(Greeter.class), isPresentAndNotRenamed());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello");
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      Greeter.hello();
+    }
+  }
+
+  static class Greeter {
+    @NeverInline
+    static void hello() {
+      System.out.println("Hello");
+    }
+  }
+
+  static class GreeterConsumer {
+    void accept(Greeter greeter) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java
index d86cc6c..bca691f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java
@@ -5,9 +5,16 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import com.android.tools.r8.graph.AccessFlags;
+import java.lang.annotation.Annotation;
 
 public abstract class ClassOrMemberSubject extends Subject {
 
+  public abstract AnnotationSubject annotation(String name);
+
+  public final AnnotationSubject annotation(Class<? extends Annotation> clazz) {
+    return annotation(clazz.getTypeName());
+  }
+
   public abstract AccessFlags<?> getAccessFlags();
 
   public abstract String getOriginalName();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 452669a..4f8ecd3 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -191,8 +191,6 @@
 
   public abstract DexProgramClass getDexProgramClass();
 
-  public abstract AnnotationSubject annotation(String name);
-
   @Override
   public abstract String getOriginalName();
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
index 10a68c1..c5a8cde 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
@@ -44,6 +44,27 @@
     };
   }
 
+  public static Matcher<MethodSubject> containsThrow() {
+    return new TypeSafeMatcher<MethodSubject>() {
+      @Override
+      protected boolean matchesSafely(MethodSubject subject) {
+        return subject.isPresent()
+            && subject.getMethod().hasCode()
+            && subject.streamInstructions().anyMatch(InstructionSubject::isThrow);
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("contains throw");
+      }
+
+      @Override
+      public void describeMismatchSafely(final MethodSubject subject, Description description) {
+        description.appendText("method did not");
+      }
+    };
+  }
+
   public static Matcher<MethodSubject> instantiatesClass(Class<?> clazz) {
     return instantiatesClass(clazz.getTypeName());
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
index d8fd4b9..bd9081e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
@@ -38,8 +38,6 @@
 
   public abstract String getFinalSignatureAttribute();
 
-  public abstract AnnotationSubject annotation(String name);
-
   public FieldSubject asFieldSubject() {
     return null;
   }
diff --git a/third_party/kotlin/kotlin-compiler-1.5.0.tar.gz.sha1 b/third_party/kotlin/kotlin-compiler-1.5.0.tar.gz.sha1
new file mode 100644
index 0000000..168df33
--- /dev/null
+++ b/third_party/kotlin/kotlin-compiler-1.5.0.tar.gz.sha1
@@ -0,0 +1 @@
+f7331df1f60104865f8308af89384ff2dee29427
\ No newline at end of file
diff --git a/tools/asmifier.py b/tools/asmifier.py
index 8aeb9a7..3fec231 100755
--- a/tools/asmifier.py
+++ b/tools/asmifier.py
@@ -10,7 +10,7 @@
 import sys
 import utils
 
-ASM_VERSION = '9.0'
+ASM_VERSION = '9.2'
 ASM_JAR = 'asm-' + ASM_VERSION + '.jar'
 ASM_UTIL_JAR = 'asm-util-' + ASM_VERSION + '.jar'
 
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 46b09b8..83bc2aa 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -300,6 +300,7 @@
       cmd.append('-Xmx' + args.xmx)
     if args.ea:
       cmd.append('-ea')
+      cmd.append('-Dcom.android.tools.r8.enableTestAssertions=1')
     if args.print_times:
       cmd.append('-Dcom.android.tools.r8.printtimes=1')
     if hasattr(args, 'properties'):