Merge commit 'c5a346eb80e2bfb3b279773a64c96c8de7bd403a' into dev-release
diff --git a/.gitignore b/.gitignore
index b03bb7b..24eead1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,6 +39,10 @@
 third_party/android_jar/lib-v[0-9][0-9]
 third_party/android_jar/lib-v[0-9][0-9].tar.gz
 third_party/android_jar/lib.tar.gz
+third_party/android_jar/api-versions.tar.gz
+third_party/android_jar/api-versions
+third_party/android_jar/api-database.tar.gz
+third_party/android_jar/api-database
 third_party/android_sdk
 third_party/android_sdk.tar.gz
 third_party/bazel
diff --git a/build.gradle b/build.gradle
index cc51ea6..f69b505 100644
--- a/build.gradle
+++ b/build.gradle
@@ -11,13 +11,6 @@
 import tasks.DownloadDependency
 import tasks.GetJarsFromConfiguration
 import utils.Utils
-import org.gradle.api.tasks.testing.logging.TestExceptionFormat
-import org.gradle.api.tasks.testing.logging.TestLogEvent
-import org.gradle.api.tasks.testing.TestOutputEvent
-
-import java.nio.charset.StandardCharsets
-import java.nio.file.Files
-import java.nio.file.StandardOpenOption
 
 buildscript {
     repositories {
@@ -255,6 +248,7 @@
     implementation group: 'org.ow2.asm', name: 'asm-tree', version: asmVersion
     implementation group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
     implementation group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
+    implementation files('third_party/android_jar/api-database/api-database.jar')
     jdk11TimeTestsCompile group: 'org.testng', name: 'testng', version: testngVersion
     examplesTestNGRunnerCompile group: 'org.testng', name: 'testng', version: testngVersion
     testCompile sourceSets.examples.output
@@ -315,6 +309,8 @@
                 "android_jar/lib-v29",
                 "android_jar/lib-v30",
                 "android_jar/lib-v31",
+                "android_jar/api-versions",
+                "android_jar/api-database",
                 "api-outlining/simple-app-dump",
                 "core-lambda-stubs",
                 "dart-sdk",
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 1a78e63..e02ccfb 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -408,7 +408,7 @@
           AnnotationRemover annotationRemover =
               annotationRemoverBuilder
                   .build(appViewWithLiveness, removedClasses);
-          annotationRemover.ensureValid().run();
+          annotationRemover.ensureValid().run(executorService);
           new GenericSignatureRewriter(appView, NamingLens.getIdentityLens(), genericContextBuilder)
               .run(appView.appInfo().classes(), executorService);
         }
@@ -642,7 +642,7 @@
             // Remove annotations that refer to types that no longer exist.
             AnnotationRemover.builder()
                 .build(appView.withLiveness(), removedClasses)
-                .run();
+                .run(executorService);
             new GenericSignatureRewriter(
                     appView, NamingLens.getIdentityLens(), genericContextBuilder)
                 .run(appView.appInfo().classes(), executorService);
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java
new file mode 100644
index 0000000..f33f5bd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java
@@ -0,0 +1,63 @@
+// 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.androidapi;
+
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiFunction;
+
+/** This is a base class for all generated classes from api-versions.xml. */
+public abstract class AndroidApiClass {
+
+  private final ClassReference classReference;
+
+  public AndroidApiClass(ClassReference classReference) {
+    this.classReference = classReference;
+  }
+
+  public abstract AndroidApiLevel getApiLevel();
+
+  public abstract TraversalContinuation visitFields(
+      BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor);
+
+  public abstract TraversalContinuation visitMethods(
+      BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor);
+
+  protected TraversalContinuation visitField(
+      String name,
+      String typeDescriptor,
+      int apiLevel,
+      BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor) {
+    return visitor.apply(
+        Reference.field(classReference, name, Reference.typeFromDescriptor(typeDescriptor)),
+        AndroidApiLevel.getAndroidApiLevel(apiLevel));
+  }
+
+  protected TraversalContinuation visitMethod(
+      String name,
+      String[] formalTypeDescriptors,
+      String returnType,
+      int apiLevel,
+      BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor) {
+    List<TypeReference> typeReferenceList = new ArrayList<>(formalTypeDescriptors.length);
+    for (String formalTypeDescriptor : formalTypeDescriptors) {
+      typeReferenceList.add(Reference.typeFromDescriptor(formalTypeDescriptor));
+    }
+    return visitor.apply(
+        Reference.method(
+            classReference,
+            name,
+            typeReferenceList,
+            returnType == null ? null : Reference.returnTypeFromDescriptor(returnType)),
+        AndroidApiLevel.getAndroidApiLevel(apiLevel));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index 5ed4009..cfad75e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -101,6 +101,10 @@
     // Pop the top value and push it back on with the checked type.
     state.pop();
     Slot object = state.push(type);
+    addCheckCast(builder, object);
+  }
+
+  void addCheckCast(IRBuilder builder, Slot object) {
     builder.addCheckCast(object.register, type);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSafeCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfSafeCheckCast.java
new file mode 100644
index 0000000..e1473d3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSafeCheckCast.java
@@ -0,0 +1,35 @@
+// 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.cf.code;
+
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import java.util.ListIterator;
+
+public class CfSafeCheckCast extends CfCheckCast {
+
+  public CfSafeCheckCast(DexType type) {
+    super(type);
+  }
+
+  @Override
+  void addCheckCast(IRBuilder builder, Slot object) {
+    builder.addSafeCheckCast(object.register, getType());
+  }
+
+  @Override
+  void internalRegisterUse(
+      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+    registry.registerSafeCheckCast(getType());
+  }
+
+  @Override
+  public CfInstruction withType(DexType newType) {
+    return new CfSafeCheckCast(newType);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/code/SafeCheckCast.java b/src/main/java/com/android/tools/r8/code/SafeCheckCast.java
new file mode 100644
index 0000000..e4af2a0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/code/SafeCheckCast.java
@@ -0,0 +1,31 @@
+// 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.code;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+
+public class SafeCheckCast extends CheckCast {
+
+  SafeCheckCast(int high, BytecodeStream stream, OffsetToObjectMapping mapping) {
+    super(high, stream, mapping);
+  }
+
+  public SafeCheckCast(int valueRegister, DexType type) {
+    super(valueRegister, type);
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder) {
+    builder.addSafeCheckCast(AA, getType());
+  }
+
+  @Override
+  public void registerUse(UseRegistry registry) {
+    registry.registerSafeCheckCast(getType());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
index c915fe7..4770922 100644
--- a/src/main/java/com/android/tools/r8/dex/Marker.java
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -81,10 +81,17 @@
             .allMatch(m -> m.tool == Tool.L8 || m.getDesugaredLibraryIdentifiers().length == 0);
       } else {
         String[] identifiers = marker.getDesugaredLibraryIdentifiers();
-        String identifier;
+        String identifier = null;
         switch (identifiers.length) {
           case 0:
-            identifier = NO_LIBRARY_DESUGARING;
+            // Only add the <no-library-desugaring> identifier for DEX. A marker from CF is
+            // assumed to go though desugaring for compiling to DEX, and that will introduce the
+            // DEX marker with the final library desugaring identifier.
+            if (marker.isDexBackend()) {
+              identifier = NO_LIBRARY_DESUGARING;
+            } else {
+              assert marker.isCfBackend();
+            }
             break;
           case 1:
             identifier = identifiers[0];
@@ -95,16 +102,16 @@
                 new StringDiagnostic(
                     "Merging program compiled with multiple desugared libraries."));
         }
-        if (marker.isDesugared()) {
+        if (marker.isDesugared() && identifier != null) {
           desugaredLibraryIdentifiers.add(identifier);
         } else {
-          assert identifier.equals(NO_LIBRARY_DESUGARING);
+          assert identifier == null || identifier.equals(NO_LIBRARY_DESUGARING);
         }
       }
     }
 
     if (desugaredLibraryIdentifiers.size() > 1) {
-      reporter.error(new DesugaredLibraryMismatchDiagnostic(desugaredLibraryIdentifiers));
+      reporter.error(new DesugaredLibraryMismatchDiagnostic(desugaredLibraryIdentifiers, markers));
     }
   }
 
@@ -211,11 +218,19 @@
   public String getBackend() {
     if (!hasBackend()) {
       // Before adding backend we would always compile to dex if min-api was specified.
-      return hasMinApi() ? "dex" : "cf";
+      return hasMinApi() ? Backend.DEX.name().toLowerCase() : Backend.CF.name().toLowerCase();
     }
     return jsonObject.get(BACKEND).getAsString();
   }
 
+  public boolean isCfBackend() {
+    return getBackend().equals(Backend.CF.name().toLowerCase());
+  }
+
+  public boolean isDexBackend() {
+    return getBackend().equals(Backend.DEX.name().toLowerCase());
+  }
+
   public Marker setBackend(Backend backend) {
     assert !hasBackend();
     jsonObject.addProperty(BACKEND, backend.name().toLowerCase());
diff --git a/src/main/java/com/android/tools/r8/errors/DesugaredLibraryMismatchDiagnostic.java b/src/main/java/com/android/tools/r8/errors/DesugaredLibraryMismatchDiagnostic.java
index ba9af51..5141753 100644
--- a/src/main/java/com/android/tools/r8/errors/DesugaredLibraryMismatchDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/DesugaredLibraryMismatchDiagnostic.java
@@ -5,16 +5,21 @@
 package com.android.tools.r8.errors;
 
 import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 public class DesugaredLibraryMismatchDiagnostic implements Diagnostic {
 
   private final Set<String> desugaredLibraryIdentifiers;
+  private final Set<Marker> markers;
 
-  public DesugaredLibraryMismatchDiagnostic(Set<String> desugaredLibraryIdentifiers) {
+  public DesugaredLibraryMismatchDiagnostic(
+      Set<String> desugaredLibraryIdentifiers, Set<Marker> markers) {
     this.desugaredLibraryIdentifiers = desugaredLibraryIdentifiers;
+    this.markers = markers;
   }
 
   @Override
@@ -31,6 +36,8 @@
   public String getDiagnosticMessage() {
     return "The compilation is merging inputs with different desugared library desugaring "
         + desugaredLibraryIdentifiers
-        + ", which may lead to unexpected runtime errors.";
+        + ", which may lead to unexpected runtime errors."
+        + "The markers are "
+        + markers.stream().map(Marker::toString).collect(Collectors.joining(", "));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/AnnotationGraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/AnnotationGraphNode.java
index 5242510..7baa5fa 100644
--- a/src/main/java/com/android/tools/r8/experimental/graphinfo/AnnotationGraphNode.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/AnnotationGraphNode.java
@@ -4,31 +4,44 @@
 package com.android.tools.r8.experimental.graphinfo;
 
 import com.android.tools.r8.Keep;
+import java.util.Objects;
 
 @Keep
 public final class AnnotationGraphNode extends GraphNode {
 
   private final GraphNode annotatedNode;
+  private final ClassGraphNode annotationClassNode;
 
-  public AnnotationGraphNode(GraphNode annotatedNode) {
+  public AnnotationGraphNode(GraphNode annotatedNode, ClassGraphNode annotationClassNode) {
     super(annotatedNode.isLibraryNode());
     this.annotatedNode = annotatedNode;
+    this.annotationClassNode = annotationClassNode;
   }
 
   public GraphNode getAnnotatedNode() {
     return annotatedNode;
   }
 
+  public ClassGraphNode getAnnotationClassNode() {
+    return annotationClassNode;
+  }
+
   @Override
   public boolean equals(Object o) {
-    return this == o
-        || (o instanceof AnnotationGraphNode
-            && ((AnnotationGraphNode) o).annotatedNode.equals(annotatedNode));
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof AnnotationGraphNode)) {
+      return false;
+    }
+    AnnotationGraphNode node = (AnnotationGraphNode) o;
+    return annotatedNode.equals(node.annotatedNode)
+        && annotationClassNode.equals(node.annotationClassNode);
   }
 
   @Override
   public int hashCode() {
-    return 7 * annotatedNode.hashCode();
+    return Objects.hash(annotatedNode, annotationClassNode);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 6ef4c6a..b509c1f 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -323,6 +323,13 @@
     return callSiteOptimizationInfoPropagator;
   }
 
+  public <E extends Throwable> void withCallSiteOptimizationInfoPropagator(
+      ThrowingConsumer<CallSiteOptimizationInfoPropagator, E> consumer) throws E {
+    if (callSiteOptimizationInfoPropagator != null) {
+      consumer.accept(callSiteOptimizationInfoPropagator);
+    }
+  }
+
   public LibraryMemberOptimizer libraryMethodOptimizer() {
     return libraryMemberOptimizer;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 14f0015..3d1a246 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -151,6 +151,17 @@
   private StackMapStatus stackMapStatus = StackMapStatus.NOT_VERIFIED;
 
   public CfCode(
+      DexType originalHolder, int maxStack, int maxLocals, List<CfInstruction> instructions) {
+    this(
+        originalHolder,
+        maxStack,
+        maxLocals,
+        instructions,
+        Collections.emptyList(),
+        Collections.emptyList());
+  }
+
+  public CfCode(
       DexType originalHolder,
       int maxStack,
       int maxLocals,
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index 0576326..1141602 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -25,6 +25,30 @@
 import java.util.function.Function;
 
 public class DexAnnotation extends DexItem implements StructuralItem<DexAnnotation> {
+
+  public enum AnnotatedKind {
+    FIELD,
+    METHOD,
+    TYPE,
+    PARAMETER;
+
+    public static AnnotatedKind from(DexDefinition definition) {
+      return from(definition.getReference());
+    }
+
+    public static AnnotatedKind from(ProgramDefinition definition) {
+      return from(definition.getReference());
+    }
+
+    public static AnnotatedKind from(DexReference reference) {
+      return reference.apply(type -> TYPE, field -> FIELD, method -> METHOD);
+    }
+
+    public boolean isParameter() {
+      return this == PARAMETER;
+    }
+  }
+
   public static final DexAnnotation[] EMPTY_ARRAY = {};
   public static final int VISIBILITY_BUILD = 0x00;
   public static final int VISIBILITY_RUNTIME = 0x01;
@@ -300,6 +324,11 @@
     return annotation.annotation.type == factory.annotationDefault;
   }
 
+  public static boolean isJavaLangRetentionAnnotation(
+      DexAnnotation annotation, DexItemFactory factory) {
+    return annotation.getAnnotationType() == factory.retentionType;
+  }
+
   public static boolean isSourceDebugExtension(DexAnnotation annotation,
       DexItemFactory factory) {
     return annotation.annotation.type == factory.annotationSourceDebugExtension;
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
index e1a64b1..454be57 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
@@ -7,6 +7,9 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.structural.StructuralItem;
@@ -41,6 +44,18 @@
     this.annotations = annotations;
   }
 
+  public DexAnnotationSet create(DexAnnotation[] annotations) {
+    return ArrayUtils.isEmpty(annotations) ? empty() : new DexAnnotationSet(annotations);
+  }
+
+  public DexAnnotation get(int index) {
+    return annotations[index];
+  }
+
+  public DexAnnotation getFirst() {
+    return get(0);
+  }
+
   @Override
   public DexAnnotationSet self() {
     return this;
@@ -189,14 +204,45 @@
     if (isEmpty()) {
       return this;
     }
-    DexAnnotation[] rewritten = ArrayUtils.map(DexAnnotation[].class, annotations, rewriter);
-    if (rewritten == annotations) {
-      return this;
+    DexAnnotation[] rewritten = ArrayUtils.map(annotations, rewriter, DexAnnotation.EMPTY_ARRAY);
+    return rewritten != annotations ? create(rewritten) : this;
+  }
+
+  public DexAnnotationSet methodParametersWithFakeThisArguments(DexItemFactory factory) {
+    DexAnnotation[] newAnnotations = null;
+    for (int i = 0; i < annotations.length; i++) {
+      DexAnnotation annotation = annotations[i];
+      if (annotation.annotation.type == factory.annotationMethodParameters) {
+        assert annotation.visibility == DexAnnotation.VISIBILITY_SYSTEM;
+        assert annotation.annotation.elements.length == 2;
+        assert annotation.annotation.elements[0].name.toString().equals("names");
+        assert annotation.annotation.elements[1].name.toString().equals("accessFlags");
+        DexValueArray names = annotation.annotation.elements[0].value.asDexValueArray();
+        DexValueArray accessFlags = annotation.annotation.elements[1].value.asDexValueArray();
+        assert names != null && accessFlags != null;
+        assert names.getValues().length == accessFlags.getValues().length;
+        if (newAnnotations == null) {
+          newAnnotations = new DexAnnotation[annotations.length];
+          System.arraycopy(annotations, 0, newAnnotations, 0, i);
+        }
+        DexValue[] newNames = new DexValue[names.getValues().length + 1];
+        newNames[0] =
+            new DexValueString(
+                factory.createString(DexCode.FAKE_THIS_PREFIX + DexCode.FAKE_THIS_SUFFIX));
+        System.arraycopy(names.getValues(), 0, newNames, 1, names.getValues().length);
+        DexValue[] newAccessFlags = new DexValue[accessFlags.getValues().length + 1];
+        newAccessFlags[0] = DexValueInt.create(0);
+        System.arraycopy(
+            accessFlags.getValues(), 0, newAccessFlags, 1, accessFlags.getValues().length);
+        newAnnotations[i] =
+            DexAnnotation.createMethodParametersAnnotation(newNames, newAccessFlags, factory);
+      } else {
+        if (newAnnotations != null) {
+          newAnnotations[i] = annotation;
+        }
+      }
     }
-    if (rewritten.length == 0) {
-      return DexAnnotationSet.empty();
-    }
-    return new DexAnnotationSet(rewritten);
+    return newAnnotations == null ? this : new DexAnnotationSet(newAnnotations);
   }
 
   @Override
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 182c91c..41dd2c1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -314,6 +314,15 @@
     fields(predicate).forEach(consumer);
   }
 
+  public void forEachInstanceField(Consumer<DexEncodedField> consumer) {
+    forEachInstanceFieldMatching(alwaysTrue(), consumer);
+  }
+
+  public void forEachInstanceFieldMatching(
+      Predicate<DexEncodedField> predicate, Consumer<DexEncodedField> consumer) {
+    instanceFields(predicate).forEach(consumer);
+  }
+
   public TraversalContinuation traverseFields(Function<DexEncodedField, TraversalContinuation> fn) {
     for (DexEncodedField field : fields()) {
       if (fn.apply(field).shouldBreak()) {
@@ -393,6 +402,10 @@
     return Arrays.asList(instanceFields);
   }
 
+  public Iterable<DexEncodedField> instanceFields(Predicate<? super DexEncodedField> predicate) {
+    return Iterables.filter(Arrays.asList(instanceFields), predicate::test);
+  }
+
   public void appendInstanceField(DexEncodedField field) {
     DexEncodedField[] newFields = new DexEncodedField[instanceFields.length + 1];
     System.arraycopy(instanceFields, 0, newFields, 0, instanceFields.length);
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index e4f7973..378640d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -38,8 +38,8 @@
 // DexCode corresponds to code item in dalvik/dex-format.html
 public class DexCode extends Code implements StructuralItem<DexCode> {
 
-  static final String FAKE_THIS_PREFIX = "_";
-  static final String FAKE_THIS_SUFFIX = "this";
+  public static final String FAKE_THIS_PREFIX = "_";
+  public static final String FAKE_THIS_SUFFIX = "this";
 
   public final int registerSize;
   public final int incomingRegisterSize;
@@ -259,7 +259,7 @@
     }
     for (TryHandler handler : handlers) {
       for (TypeAddrPair pair : handler.pairs) {
-        registry.registerTypeReference(pair.type);
+        registry.registerExceptionGuard(pair.type);
       }
     }
   }
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 8eeda6b..2695b1f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
@@ -3,8 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.shaking.AnnotationRemover;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
@@ -31,15 +31,14 @@
 
   public abstract AccessFlags<?> getAccessFlags();
 
-  public DexAnnotationSet liveAnnotations(AppView<AppInfoWithLiveness> appView) {
-    return annotations.keepIf(
-        annotation -> AnnotationRemover.shouldKeepAnnotation(appView, this, annotation));
-  }
-
   public void clearAnnotations() {
     setAnnotations(DexAnnotationSet.empty());
   }
 
+  public void clearAllAnnotations() {
+    clearAnnotations();
+  }
+
   public void setAnnotations(DexAnnotationSet annotations) {
     this.annotations = annotations;
   }
@@ -48,6 +47,12 @@
     setAnnotations(annotations().removeIf(predicate));
   }
 
+  public void rewriteAllAnnotations(
+      BiFunction<DexAnnotation, AnnotatedKind, DexAnnotation> rewriter) {
+    setAnnotations(
+        annotations().rewrite(annotation -> rewriter.apply(annotation, AnnotatedKind.from(this))));
+  }
+
   public boolean isDexClass() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
index 98df8c7..e338ef5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
@@ -112,7 +112,7 @@
       Function<DexAnnotationElement, DexAnnotationElement> elementRewriter) {
     DexType rewrittenType = typeRewriter.apply(type);
     DexAnnotationElement[] rewrittenElements =
-        ArrayUtils.map(DexAnnotationElement[].class, elements, elementRewriter);
+        ArrayUtils.map(elements, elementRewriter, DexAnnotationElement.EMPTY_ARRAY);
     if (rewrittenType == type && rewrittenElements == elements) {
       return this;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 1e10fa9..da8323c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
 import com.android.tools.r8.ir.optimize.info.DefaultFieldOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.DefaultFieldOptimizationWithMinApiInfo;
 import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
@@ -30,6 +31,10 @@
 
 public class DexEncodedField extends DexEncodedMember<DexEncodedField, DexField>
     implements StructuralItem<DexEncodedField> {
+
+  public static final boolean D8_R8_SYNTHESIZED = true;
+  public static final boolean NOT_DEPRECATED = false;
+  public static final DexValue NO_STATIC_VALUE = null;
   public static final DexEncodedField[] EMPTY_ARRAY = {};
 
   public final FieldAccessFlags accessFlags;
@@ -132,6 +137,10 @@
     optimizationInfo = info;
   }
 
+  public void setMinApiOptimizationInfo(DefaultFieldOptimizationWithMinApiInfo info) {
+    optimizationInfo = info;
+  }
+
   @Override
   public KotlinFieldLevelInfo getKotlinInfo() {
     return kotlinMemberInfo;
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 449747b..f8a2537 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -44,6 +44,7 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
 import com.android.tools.r8.ir.code.IRCode;
@@ -72,8 +73,6 @@
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.shaking.AnnotationRemover;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -92,6 +91,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.IntPredicate;
@@ -399,11 +399,6 @@
     return getReference().proto.returnType;
   }
 
-  public ParameterAnnotationsList liveParameterAnnotations(AppView<AppInfoWithLiveness> appView) {
-    return parameterAnnotationsList.keepIf(
-        annotation -> AnnotationRemover.shouldKeepAnnotation(appView, this, annotation));
-  }
-
   public OptionalBool isLibraryMethodOverride() {
     return isNonPrivateVirtualMethod() ? isLibraryMethodOverride : OptionalBool.FALSE;
   }
@@ -894,14 +889,38 @@
     return builder.toString();
   }
 
-  public ParameterAnnotationsList getParameterAnnotations() {
-    return parameterAnnotationsList;
+  @Override
+  public void clearAllAnnotations() {
+    clearAnnotations();
+    clearParameterAnnotations();
+  }
+
+  @Override
+  public void rewriteAllAnnotations(
+      BiFunction<DexAnnotation, AnnotatedKind, DexAnnotation> rewriter) {
+    setAnnotations(
+        annotations().rewrite(annotation -> rewriter.apply(annotation, AnnotatedKind.METHOD)));
+    setParameterAnnotations(
+        getParameterAnnotations()
+            .rewrite(annotation -> rewriter.apply(annotation, AnnotatedKind.PARAMETER)));
   }
 
   public void clearParameterAnnotations() {
     parameterAnnotationsList = ParameterAnnotationsList.empty();
   }
 
+  public DexAnnotationSet getParameterAnnotation(int index) {
+    return getParameterAnnotations().get(index);
+  }
+
+  public ParameterAnnotationsList getParameterAnnotations() {
+    return parameterAnnotationsList;
+  }
+
+  public void setParameterAnnotations(ParameterAnnotationsList parameterAnnotations) {
+    this.parameterAnnotationsList = parameterAnnotations;
+  }
+
   public String toSmaliString(ClassNameMapper naming) {
     checkIfObsolete();
     StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index a42388a..d7a48c1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.desugar.LambdaClass;
 import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -450,6 +451,8 @@
       createStaticallyKnownType("Ljava/util/function/LongConsumer;");
   public final DexType intConsumer = createStaticallyKnownType("Ljava/util/function/IntConsumer;");
 
+  public final DexType retentionType =
+      createStaticallyKnownType("Ljava/lang/annotation/Retention;");
   public final DexType runtimeExceptionType = createStaticallyKnownType(runtimeExceptionDescriptor);
   public final DexType throwableType = createStaticallyKnownType(throwableDescriptor);
   public final DexType illegalAccessErrorType =
@@ -1508,16 +1511,16 @@
   }
 
   public class JavaLangSystemMethods {
-    public final DexMethod identityHashCode;
 
-    private JavaLangSystemMethods() {
-      identityHashCode =
-          createMethod(
-              javaLangSystemDescriptor,
-              identityHashCodeName,
-              intDescriptor,
-              new DexString[] {objectDescriptor});
-    }
+    public final DexMethod arraycopy =
+        createMethod(
+            javaLangSystemType,
+            createProto(voidType, objectType, intType, objectType, intType, intType),
+            "arraycopy");
+    public final DexMethod identityHashCode =
+        createMethod(javaLangSystemType, createProto(intType, objectType), identityHashCodeName);
+
+    private JavaLangSystemMethods() {}
   }
 
   public class EnumMembers {
@@ -2242,6 +2245,10 @@
     return createMethod(holder, createProto(voidType, parameters), constructorMethodName);
   }
 
+  public DexMethod createInstanceInitializer(DexType holder, DexTypeList parameters) {
+    return createInstanceInitializer(holder, parameters.values);
+  }
+
   public DexMethod createInstanceInitializerWithFreshProto(
       DexMethod method, List<DexType> extraTypes, Predicate<DexMethod> isFresh) {
     assert method.isInstanceInitializer(this);
@@ -2400,6 +2407,13 @@
     return createField(clazz, type, createString(name));
   }
 
+  public DexField createField(FieldReference fieldReference) {
+    return createField(
+        createType(fieldReference.getHolderClass().getDescriptor()),
+        createType(fieldReference.getFieldType().getDescriptor()),
+        fieldReference.getFieldName());
+  }
+
   public DexProto createProto(DexType returnType, DexTypeList parameters, DexString shorty) {
     assert !sorted;
     DexProto proto = new DexProto(shorty, returnType, parameters);
diff --git a/src/main/java/com/android/tools/r8/graph/DexMember.java b/src/main/java/com/android/tools/r8/graph/DexMember.java
index 9b62855..ae9d9fb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -3,7 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.Iterables;
+import java.util.Map;
 import java.util.function.Function;
 
 public abstract class DexMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
@@ -60,4 +62,14 @@
   public Iterable<DexType> getReferencedBaseTypes(DexItemFactory dexItemFactory) {
     return Iterables.transform(getReferencedTypes(), type -> type.toBaseType(dexItemFactory));
   }
+
+  public AndroidApiLevel computeApiLevelForReferencedTypes(
+      AppView<?> appView, Map<DexReference, AndroidApiLevel> apiLevelMap) {
+    AndroidApiLevel minApiLevel = appView.options().minApiLevel;
+    AndroidApiLevel apiLevel = minApiLevel;
+    for (DexType type : getReferencedBaseTypes(appView.dexItemFactory())) {
+      apiLevel = apiLevel.max(apiLevelMap.getOrDefault(type, minApiLevel));
+    }
+    return apiLevel;
+  }
 }
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 6be5551..0ebd1da 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -199,6 +199,10 @@
     forEachField(field -> consumer.accept(new ProgramField(this, field)));
   }
 
+  public void forEachProgramInstanceField(Consumer<? super ProgramField> consumer) {
+    forEachInstanceField(field -> consumer.accept(new ProgramField(this, field)));
+  }
+
   public void forEachProgramMember(Consumer<? super ProgramMember<?, ?>> consumer) {
     forEachProgramField(consumer);
     forEachProgramMethod(consumer);
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
index 1697f43..850c3cb 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
@@ -58,6 +58,14 @@
     return this;
   }
 
+  public static FieldAccessFlags createPublicStaticFinalSynthetic() {
+    return fromSharedAccessFlags(
+        Constants.ACC_PUBLIC
+            | Constants.ACC_STATIC
+            | Constants.ACC_FINAL
+            | Constants.ACC_SYNTHETIC);
+  }
+
   public static FieldAccessFlags createPublicStaticSynthetic() {
     return fromSharedAccessFlags(
         Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC);
diff --git a/src/main/java/com/android/tools/r8/graph/InvalidCode.java b/src/main/java/com/android/tools/r8/graph/InvalidCode.java
new file mode 100644
index 0000000..418de86
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/InvalidCode.java
@@ -0,0 +1,69 @@
+// 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.errors.Unreachable;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.origin.Origin;
+
+public class InvalidCode extends Code {
+
+  private static final InvalidCode INSTANCE = new InvalidCode();
+
+  public static Code getInstance() {
+    return INSTANCE;
+  }
+
+  public static boolean isInvalidCode(Code code) {
+    return code == INSTANCE;
+  }
+
+  private InvalidCode() {}
+
+  @Override
+  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+    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 "<invalid-code>";
+  }
+
+  @Override
+  public String toString(DexEncodedMethod method, ClassNameMapper naming) {
+    return toString();
+  }
+
+  @Override
+  public int estimatedDexCodeSizeUpperBoundInBytes() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public boolean isEmptyVoidMethod() {
+    throw new Unreachable();
+  }
+
+  @Override
+  protected int computeHashCode() {
+    return System.identityHashCode(this);
+  }
+
+  @Override
+  protected boolean computeEquals(Object other) {
+    return this == other;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
index 17072ef..7e67c3b 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.PredicateUtils;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.base.MoreObjects;
@@ -265,15 +266,13 @@
   @Override
   void addVirtualMethod(DexEncodedMethod virtualMethod) {
     assert belongsToVirtualPool(virtualMethod);
-    virtualMethods = Arrays.copyOf(virtualMethods, virtualMethods.length + 1);
-    virtualMethods[virtualMethods.length - 1] = virtualMethod;
+    virtualMethods = ArrayUtils.appendSingleElement(virtualMethods, virtualMethod);
   }
 
   @Override
   void addDirectMethod(DexEncodedMethod directMethod) {
     assert belongsToDirectPool(directMethod);
-    directMethods = Arrays.copyOf(directMethods, directMethods.length + 1);
-    directMethods[directMethods.length - 1] = directMethod;
+    directMethods = ArrayUtils.appendSingleElement(directMethods, directMethod);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index 35e0541..635132f 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -7,6 +7,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -137,7 +138,7 @@
   public List<DexEncodedMethod> allMethodsSorted() {
     List<DexEncodedMethod> sorted = new ArrayList<>(size());
     forEachMethod(sorted::add);
-    sorted.sort((a, b) -> a.getReference().compareTo(b.getReference()));
+    sorted.sort(Comparator.comparing(DexEncodedMember::getReference));
     return sorted;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java b/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
index 7628702..ba98190 100644
--- a/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
+++ b/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
@@ -70,6 +70,13 @@
     this.missingParameterAnnotations = missingParameterAnnotations;
   }
 
+  public static ParameterAnnotationsList create(
+      DexAnnotationSet[] values, int missingParameterAnnotations) {
+    return ArrayUtils.isEmpty(values)
+        ? empty()
+        : new ParameterAnnotationsList(values, missingParameterAnnotations);
+  }
+
   @Override
   public ParameterAnnotationsList self() {
     return this;
@@ -187,6 +194,17 @@
     return new ParameterAnnotationsList(values, parameterCount - values.length);
   }
 
+  public ParameterAnnotationsList withFakeThisParameter() {
+    // If there are no parameter annotations there is no need to add one for the this parameter.
+    if (isEmpty()) {
+      return this;
+    }
+    DexAnnotationSet[] newValues = new DexAnnotationSet[size() + 1];
+    System.arraycopy(values, 0, newValues, 1, size());
+    newValues[0] = DexAnnotationSet.empty();
+    return new ParameterAnnotationsList(newValues, 0);
+  }
+
   /**
    * Return a new ParameterAnnotationsList that keeps only the annotations matched by {@code
    * filter}.
@@ -215,11 +233,15 @@
     return new ParameterAnnotationsList(filtered, missingParameterAnnotations);
   }
 
-  public ParameterAnnotationsList rewrite(Function<DexAnnotationSet, DexAnnotationSet> mapper) {
-    DexAnnotationSet[] rewritten = ArrayUtils.map(DexAnnotationSet[].class, values, mapper);
-    if (rewritten != values) {
-      return new ParameterAnnotationsList(rewritten, missingParameterAnnotations);
+  public ParameterAnnotationsList rewrite(Function<DexAnnotation, DexAnnotation> mapper) {
+    if (isEmpty()) {
+      return this;
     }
-    return this;
+    DexAnnotationSet[] rewritten =
+        ArrayUtils.map(
+            values, annotations -> annotations.rewrite(mapper), DexAnnotationSet.EMPTY_ARRAY);
+    return rewritten != values
+        ? ParameterAnnotationsList.create(rewritten, missingParameterAnnotations)
+        : this;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
index 17e779e..c1dedd5 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 
 public interface ProgramDefinition extends Definition, ProgramDerivedContext {
@@ -21,6 +23,15 @@
     return this;
   }
 
+  default void clearAllAnnotations() {
+    getDefinition().clearAllAnnotations();
+  }
+
+  default void rewriteAllAnnotations(
+      BiFunction<DexAnnotation, AnnotatedKind, DexAnnotation> rewriter) {
+    getDefinition().rewriteAllAnnotations(rewriter);
+  }
+
   DexProgramClass getContextClass();
 
   AccessFlags<?> getAccessFlags();
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramField.java b/src/main/java/com/android/tools/r8/graph/ProgramField.java
index 3e2a276..aec6be0 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramField.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramField.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
-import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
+import com.android.tools.r8.kotlin.KotlinFieldLevelInfo;
 
 public class ProgramField extends DexClassAndField
     implements ProgramMember<DexEncodedField, DexField> {
@@ -60,12 +60,7 @@
   }
 
   @Override
-  public KotlinMemberLevelInfo getKotlinInfo() {
+  public KotlinFieldLevelInfo getKotlinInfo() {
     return getDefinition().getKotlinInfo();
   }
-
-  @Override
-  public void clearKotlinInfo() {
-    getDefinition().clearKotlinInfo();
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMember.java b/src/main/java/com/android/tools/r8/graph/ProgramMember.java
index 1234378..fa77682 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMember.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMember.java
@@ -23,5 +23,11 @@
 
   KotlinMemberLevelInfo getKotlinInfo();
 
-  void clearKotlinInfo();
+  default void clearGenericSignature() {
+    getDefinition().clearGenericSignature();
+  }
+
+  default void clearKotlinInfo() {
+    getDefinition().clearKotlinInfo();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 5a58189..5faf268 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
+import com.android.tools.r8.kotlin.KotlinMethodLevelInfo;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
@@ -95,15 +95,10 @@
   }
 
   @Override
-  public KotlinMemberLevelInfo getKotlinInfo() {
+  public KotlinMethodLevelInfo getKotlinInfo() {
     return getDefinition().getKotlinInfo();
   }
 
-  @Override
-  public void clearKotlinInfo() {
-    getDefinition().clearKotlinInfo();
-  }
-
   public MethodPosition getPosition() {
     return getDefinition().getPosition();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 565511a..708a02f 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -82,6 +82,10 @@
     registerTypeReference(type);
   }
 
+  public void registerSafeCheckCast(DexType type) {
+    registerCheckCast(type);
+  }
+
   public void registerExceptionGuard(DexType guard) {
     registerTypeReference(guard);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java
new file mode 100644
index 0000000..f93437e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java
@@ -0,0 +1,82 @@
+// 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.analysis;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMember;
+import com.android.tools.r8.graph.DexMember;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.optimize.info.DefaultFieldOptimizationWithMinApiInfo;
+import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationWithMinApiInfo;
+import com.android.tools.r8.ir.optimize.info.MemberOptimizationInfo;
+import com.android.tools.r8.shaking.DefaultEnqueuerUseRegistry;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.Map;
+
+public class ApiModelAnalysis extends EnqueuerAnalysis {
+
+  private final AppView<?> appView;
+  private final AndroidApiLevel minApiLevel;
+  private final Map<DexReference, AndroidApiLevel> referenceToApiLevelMap;
+
+  public ApiModelAnalysis(
+      AppView<?> appView, Map<DexReference, AndroidApiLevel> referenceToApiLevelMap) {
+    this.appView = appView;
+    this.minApiLevel = appView.options().minApiLevel;
+    this.referenceToApiLevelMap = referenceToApiLevelMap;
+  }
+
+  @Override
+  public void processNewlyLiveField(ProgramField field, ProgramDefinition context) {
+    setApiLevelForMember(
+        field.getDefinition(), computeApiLevelForReferencedTypes(field.getReference()));
+  }
+
+  @Override
+  public void processNewlyLiveMethod(ProgramMethod method, ProgramDefinition context) {
+    setApiLevelForMember(
+        method.getDefinition(), computeApiLevelForReferencedTypes(method.getReference()));
+  }
+
+  @Override
+  public void processTracedCode(ProgramMethod method, DefaultEnqueuerUseRegistry registry) {
+    assert registry.getMaxApiReferenceLevel().isGreaterThanOrEqualTo(minApiLevel);
+    setApiLevelForMember(method.getDefinition(), registry.getMaxApiReferenceLevel());
+  }
+
+  private void setApiLevelForMember(DexEncodedMember<?, ?> member, AndroidApiLevel apiLevel) {
+    // To not have mutable update information for all members that all has min api level we
+    // swap the default optimization info for one with that marks the api level to be min api.
+    MemberOptimizationInfo<?> optimizationInfo = member.getOptimizationInfo();
+    if (!optimizationInfo.isMutableOptimizationInfo() && apiLevel == minApiLevel) {
+      member.apply(
+          field -> {
+            field.setMinApiOptimizationInfo(DefaultFieldOptimizationWithMinApiInfo.getInstance());
+          },
+          method -> {
+            method.setMinApiOptimizationInfo(DefaultMethodOptimizationWithMinApiInfo.getInstance());
+          });
+    } else {
+      AndroidApiLevel maxApiLevel =
+          optimizationInfo.hasApiReferenceLevel()
+              ? apiLevel.max(optimizationInfo.getApiReferenceLevel(minApiLevel))
+              : apiLevel;
+      member.apply(
+          field -> {
+            field.getMutableOptimizationInfo().setApiReferenceLevel(maxApiLevel);
+          },
+          method -> {
+            method.getMutableOptimizationInfo().setApiReferenceLevel(maxApiLevel);
+          });
+    }
+  }
+
+  private AndroidApiLevel computeApiLevelForReferencedTypes(DexMember<?, ?> member) {
+    return member.computeApiLevelForReferencedTypes(appView, referenceToApiLevelMap);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
index 9e09724..dc87519 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.DefaultEnqueuerUseRegistry;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
 import com.android.tools.r8.utils.Timing;
@@ -26,6 +27,9 @@
   /** Called when a method is found to be live. */
   public void processNewlyLiveMethod(ProgramMethod method, ProgramDefinition context) {}
 
+  /** Called when a method's code has been processed by the registry. */
+  public void processTracedCode(ProgramMethod method, DefaultEnqueuerUseRegistry registry) {}
+
   /**
    * Called when the Enqueuer reaches a fixpoint. This may happen multiple times, since each
    * analysis may enqueue items into the worklist upon the fixpoint using {@param worklist}.
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerCheckCastAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerCheckCastAnalysis.java
index 503e74e..ad0dcbf 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerCheckCastAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerCheckCastAnalysis.java
@@ -8,5 +8,8 @@
 import com.android.tools.r8.graph.ProgramMethod;
 
 public interface EnqueuerCheckCastAnalysis {
+
   void traceCheckCast(DexType type, ProgramMethod context);
+
+  void traceSafeCheckCast(DexType type, ProgramMethod context);
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
index e0eaf2c..21f8a5c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
@@ -8,12 +8,9 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerGraphLens.Builder;
 import com.android.tools.r8.horizontalclassmerging.policies.SameInstanceFields.InstanceFieldInfo;
 import com.android.tools.r8.utils.IterableUtils;
-import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
-import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap;
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -22,6 +19,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.BiConsumer;
 
 public class ClassInstanceFieldsMerger {
 
@@ -31,10 +29,6 @@
 
   private DexEncodedField classIdField;
 
-  // Map from target class field to all fields which should be merged into that field.
-  private final MutableBidirectionalManyToOneMap<DexEncodedField, DexEncodedField> fieldMappings =
-      BidirectionalManyToOneHashMap.newLinkedHashMap();
-
   public ClassInstanceFieldsMerger(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
@@ -42,7 +36,6 @@
     this.appView = appView;
     this.group = group;
     this.lensBuilder = lensBuilder;
-    group.forEachSource(this::addFields);
   }
 
   /**
@@ -55,13 +48,17 @@
    * Bar has fields 'A b' and 'B a'), we make a prepass that matches fields with the same reference
    * type.
    */
-  private void addFields(DexProgramClass clazz) {
+  public static void mapFields(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      DexProgramClass source,
+      DexProgramClass target,
+      BiConsumer<DexEncodedField, DexEncodedField> consumer) {
     Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByExactInfo =
-        getAvailableFieldsByExactInfo();
+        getAvailableFieldsByExactInfo(target);
     List<DexEncodedField> needsMerge = new ArrayList<>();
 
     // Pass 1: Match fields that have the exact same type.
-    for (DexEncodedField oldField : clazz.instanceFields()) {
+    for (DexEncodedField oldField : source.instanceFields()) {
       InstanceFieldInfo info = InstanceFieldInfo.createExact(oldField);
       LinkedList<DexEncodedField> availableFieldsWithExactSameInfo =
           availableFieldsByExactInfo.get(info);
@@ -69,7 +66,7 @@
         needsMerge.add(oldField);
       } else {
         DexEncodedField newField = availableFieldsWithExactSameInfo.removeFirst();
-        fieldMappings.put(oldField, newField);
+        consumer.accept(oldField, newField);
         if (availableFieldsWithExactSameInfo.isEmpty()) {
           availableFieldsByExactInfo.remove(info);
         }
@@ -78,7 +75,7 @@
 
     // Pass 2: Match fields that do not have the same reference type.
     Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByRelaxedInfo =
-        getAvailableFieldsByRelaxedInfo(availableFieldsByExactInfo);
+        getAvailableFieldsByRelaxedInfo(appView, availableFieldsByExactInfo);
     for (DexEncodedField oldField : needsMerge) {
       assert oldField.getType().isReferenceType();
       DexEncodedField newField =
@@ -87,14 +84,15 @@
               .removeFirst();
       assert newField != null;
       assert newField.getType().isReferenceType();
-      fieldMappings.put(oldField, newField);
+      consumer.accept(oldField, newField);
     }
   }
 
-  private Map<InstanceFieldInfo, LinkedList<DexEncodedField>> getAvailableFieldsByExactInfo() {
+  private static Map<InstanceFieldInfo, LinkedList<DexEncodedField>> getAvailableFieldsByExactInfo(
+      DexProgramClass target) {
     Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByInfo =
         new LinkedHashMap<>();
-    for (DexEncodedField field : group.getTarget().instanceFields()) {
+    for (DexEncodedField field : target.instanceFields()) {
       availableFieldsByInfo
           .computeIfAbsent(InstanceFieldInfo.createExact(field), ignore -> new LinkedList<>())
           .add(field);
@@ -102,8 +100,10 @@
     return availableFieldsByInfo;
   }
 
-  private Map<InstanceFieldInfo, LinkedList<DexEncodedField>> getAvailableFieldsByRelaxedInfo(
-      Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByExactInfo) {
+  private static Map<InstanceFieldInfo, LinkedList<DexEncodedField>>
+      getAvailableFieldsByRelaxedInfo(
+          AppView<? extends AppInfoWithClassHierarchy> appView,
+          Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByExactInfo) {
     Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByRelaxedInfo =
         new LinkedHashMap<>();
     availableFieldsByExactInfo.forEach(
@@ -125,26 +125,21 @@
     }
   }
 
-  public ProgramField getTargetField(ProgramField field) {
-    if (field.getHolder() == group.getTarget()) {
-      return field;
-    }
-    DexEncodedField targetField = fieldMappings.get(field.getDefinition());
-    return new ProgramField(group.getTarget(), targetField);
-  }
-
   public void setClassIdField(DexEncodedField classIdField) {
     this.classIdField = classIdField;
   }
 
   public DexEncodedField[] merge() {
+    assert group.hasInstanceFieldMap();
     List<DexEncodedField> newFields = new ArrayList<>();
     if (classIdField != null) {
       newFields.add(classIdField);
     }
-    fieldMappings.forEachManyToOneMapping(
-        (sourceFields, targetField) ->
-            newFields.add(mergeSourceFieldsToTargetField(targetField, sourceFields)));
+    group
+        .getInstanceFieldMap()
+        .forEachManyToOneMapping(
+            (sourceFields, targetField) ->
+                newFields.add(mergeSourceFieldsToTargetField(targetField, sourceFields)));
     return newFields.toArray(DexEncodedField.EMPTY_ARRAY);
   }
 
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 3927d11..d1e98c7 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -27,12 +27,10 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
 import com.android.tools.r8.horizontalclassmerging.code.ClassInitializerMerger;
-import com.android.tools.r8.horizontalclassmerging.code.SyntheticClassInitializerConverter;
+import com.android.tools.r8.horizontalclassmerging.code.SyntheticInitializerConverter;
 import com.android.tools.r8.ir.analysis.value.NumberFromIntervalValue;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
-import com.android.tools.r8.shaking.KeepClassInfo;
-import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
@@ -40,7 +38,6 @@
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -78,6 +75,7 @@
 
   private ClassMerger(
       AppView<? extends AppInfoWithClassHierarchy> appView,
+      IRCodeProvider codeProvider,
       Mode mode,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       MergeGroup group,
@@ -96,7 +94,7 @@
     this.classInitializerMerger = ClassInitializerMerger.create(group);
     this.instanceInitializerMergers =
         InstanceInitializerMergerCollection.create(
-            appView, classIdentifiers, group, classInstanceFieldsMerger, lensBuilder, mode);
+            appView, classIdentifiers, codeProvider, group, lensBuilder, mode);
     this.virtualMethodMergers = virtualMethodMergers;
 
     buildClassIdentifierMap();
@@ -109,14 +107,14 @@
 
   void mergeDirectMethods(
       SyntheticArgumentClass syntheticArgumentClass,
-      SyntheticClassInitializerConverter.Builder syntheticClassInitializerConverterBuilder) {
-    mergeInstanceInitializers(syntheticArgumentClass);
-    mergeStaticClassInitializers(syntheticClassInitializerConverterBuilder);
+      SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
+    mergeInstanceInitializers(syntheticArgumentClass, syntheticInitializerConverterBuilder);
+    mergeStaticClassInitializers(syntheticInitializerConverterBuilder);
     group.forEach(this::mergeDirectMethods);
   }
 
   void mergeStaticClassInitializers(
-      SyntheticClassInitializerConverter.Builder syntheticClassInitializerConverterBuilder) {
+      SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
     if (classInitializerMerger.isEmpty()) {
       return;
     }
@@ -145,7 +143,7 @@
     if (!definition.getCode().isCfCode()) {
       assert appView.options().isGeneratingDex();
       assert mode.isFinal();
-      syntheticClassInitializerConverterBuilder.add(group);
+      syntheticInitializerConverterBuilder.add(new ProgramMethod(group.getTarget(), definition));
     }
   }
 
@@ -187,16 +185,20 @@
         classMethodsBuilder::isFresh);
   }
 
-  void mergeInstanceInitializers(SyntheticArgumentClass syntheticArgumentClass) {
+  void mergeInstanceInitializers(
+      SyntheticArgumentClass syntheticArgumentClass,
+      SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
     instanceInitializerMergers.forEach(
-        merger -> merger.merge(classMethodsBuilder, syntheticArgumentClass));
+        merger ->
+            merger.merge(
+                classMethodsBuilder, syntheticArgumentClass, syntheticInitializerConverterBuilder));
   }
 
   void mergeMethods(
       SyntheticArgumentClass syntheticArgumentClass,
-      SyntheticClassInitializerConverter.Builder syntheticClassInitializerConverterBuilder) {
+      SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
     mergeVirtualMethods();
-    mergeDirectMethods(syntheticArgumentClass, syntheticClassInitializerConverterBuilder);
+    mergeDirectMethods(syntheticArgumentClass, syntheticInitializerConverterBuilder);
     classMethodsBuilder.setClassMethods(group.getTarget());
   }
 
@@ -316,51 +318,30 @@
 
   public void mergeGroup(
       SyntheticArgumentClass syntheticArgumentClass,
-      SyntheticClassInitializerConverter.Builder syntheticClassInitializerConverterBuilder) {
+      SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
     fixAccessFlags();
     fixNestMemberAttributes();
     mergeAnnotations();
     mergeInterfaces();
     mergeFields();
-    mergeMethods(syntheticArgumentClass, syntheticClassInitializerConverterBuilder);
+    mergeMethods(syntheticArgumentClass, syntheticInitializerConverterBuilder);
   }
 
   public static class Builder {
     private final AppView<? extends AppInfoWithClassHierarchy> appView;
+    private final IRCodeProvider codeProvider;
     private Mode mode;
     private final MergeGroup group;
 
-    public Builder(AppView<? extends AppInfoWithClassHierarchy> appView, MergeGroup group) {
+    public Builder(
+        AppView<? extends AppInfoWithClassHierarchy> appView,
+        IRCodeProvider codeProvider,
+        MergeGroup group,
+        Mode mode) {
       this.appView = appView;
+      this.codeProvider = codeProvider;
       this.group = group;
-    }
-
-    Builder setMode(Mode mode) {
       this.mode = mode;
-      return this;
-    }
-
-    private void selectTarget() {
-      Iterable<DexProgramClass> candidates = Iterables.filter(group, DexClass::isPublic);
-      if (IterableUtils.isEmpty(candidates)) {
-        candidates = group;
-      }
-      Iterator<DexProgramClass> candidateIterator = candidates.iterator();
-      DexProgramClass target = IterableUtils.first(candidates);
-      while (candidateIterator.hasNext()) {
-        DexProgramClass current = candidateIterator.next();
-        KeepClassInfo keepClassInfo = appView.getKeepInfo().getClassInfo(current);
-        if (keepClassInfo.isMinificationAllowed(appView.options())) {
-          target = current;
-          break;
-        }
-        // Select the target with the shortest name.
-        if (current.getType().getDescriptor().size() < target.getType().getDescriptor().size) {
-          target = current;
-        }
-      }
-      group.setTarget(
-          appView.testing().horizontalClassMergingTarget.apply(appView, candidates, target));
     }
 
     private List<VirtualMethodMerger> createVirtualMethodMergers() {
@@ -393,8 +374,6 @@
 
     public ClassMerger build(
         HorizontalClassMergerGraphLens.Builder lensBuilder) {
-      selectTarget();
-
       List<VirtualMethodMerger> virtualMethodMergers = createVirtualMethodMergers();
 
       boolean requiresClassIdField =
@@ -405,7 +384,7 @@
         createClassIdField();
       }
 
-      return new ClassMerger(appView, mode, lensBuilder, group, virtualMethodMergers);
+      return new ClassMerger(appView, codeProvider, mode, lensBuilder, group, virtualMethodMergers);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index 4ad6f55..666bd64 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.PrunedItems;
-import com.android.tools.r8.horizontalclassmerging.code.SyntheticClassInitializerConverter;
+import com.android.tools.r8.horizontalclassmerging.code.SyntheticInitializerConverter;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.shaking.KeepInfoCollection;
@@ -65,6 +65,10 @@
     if (options.isEnabled(mode)) {
       timing.begin("HorizontalClassMerger (" + mode.toString() + ")");
       run(runtimeTypeCheckInfo, executorService, timing);
+
+      // Clear type elements cache after IR building.
+      appView.dexItemFactory().clearTypeElementsCache();
+
       timing.end();
     } else {
       appView.setHorizontallyMergedClasses(HorizontallyMergedClasses.empty(), mode);
@@ -74,8 +78,11 @@
   private void run(
       RuntimeTypeCheckInfo runtimeTypeCheckInfo, ExecutorService executorService, Timing timing)
       throws ExecutionException {
+    IRCodeProvider codeProvider = new IRCodeProvider(appView);
+
     // Run the policies on all program classes to produce a final grouping.
-    List<Policy> policies = PolicyScheduler.getPolicies(appView, mode, runtimeTypeCheckInfo);
+    List<Policy> policies =
+        PolicyScheduler.getPolicies(appView, codeProvider, mode, runtimeTypeCheckInfo);
     Collection<MergeGroup> groups = new PolicyExecutor().run(getInitialGroups(), policies, timing);
 
     // If there are no groups, then end horizontal class merging.
@@ -88,21 +95,20 @@
         new HorizontalClassMergerGraphLens.Builder();
 
     // Merge the classes.
-    List<ClassMerger> classMergers = initializeClassMergers(lensBuilder, groups);
+    List<ClassMerger> classMergers = initializeClassMergers(codeProvider, lensBuilder, groups);
     SyntheticArgumentClass syntheticArgumentClass =
         mode.isInitial()
             ? new SyntheticArgumentClass.Builder(appView.withLiveness()).build(groups)
             : null;
-    SyntheticClassInitializerConverter.Builder syntheticClassInitializerConverterBuilder =
-        SyntheticClassInitializerConverter.builder(appView);
-    applyClassMergers(
-        classMergers, syntheticArgumentClass, syntheticClassInitializerConverterBuilder);
+    SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder =
+        SyntheticInitializerConverter.builder(appView, codeProvider);
+    applyClassMergers(classMergers, syntheticArgumentClass, syntheticInitializerConverterBuilder);
 
-    SyntheticClassInitializerConverter syntheticClassInitializerConverter =
-        syntheticClassInitializerConverterBuilder.build();
-    if (!syntheticClassInitializerConverter.isEmpty()) {
+    SyntheticInitializerConverter syntheticInitializerConverter =
+        syntheticInitializerConverterBuilder.build();
+    if (!syntheticInitializerConverter.isEmpty()) {
       assert mode.isFinal();
-      syntheticClassInitializerConverterBuilder.build().convert(executorService);
+      syntheticInitializerConverterBuilder.build().convert(executorService);
     }
 
     // Generate the graph lens.
@@ -194,12 +200,16 @@
    * be merged and how the merging should be performed.
    */
   private List<ClassMerger> initializeClassMergers(
+      IRCodeProvider codeProvider,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       Collection<MergeGroup> groups) {
     List<ClassMerger> classMergers = new ArrayList<>(groups.size());
     for (MergeGroup group : groups) {
       assert group.isNonTrivial();
-      classMergers.add(new ClassMerger.Builder(appView, group).setMode(mode).build(lensBuilder));
+      assert group.hasInstanceFieldMap();
+      assert group.hasTarget();
+      classMergers.add(
+          new ClassMerger.Builder(appView, codeProvider, group, mode).build(lensBuilder));
     }
     return classMergers;
   }
@@ -208,13 +218,10 @@
   private void applyClassMergers(
       Collection<ClassMerger> classMergers,
       SyntheticArgumentClass syntheticArgumentClass,
-      SyntheticClassInitializerConverter.Builder syntheticClassInitializerConverterBuilder) {
+      SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
     for (ClassMerger merger : classMergers) {
-      merger.mergeGroup(syntheticArgumentClass, syntheticClassInitializerConverterBuilder);
+      merger.mergeGroup(syntheticArgumentClass, syntheticInitializerConverterBuilder);
     }
-
-    // Clear type elements cache after IR building.
-    appView.dexItemFactory().clearTypeElementsCache();
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java
new file mode 100644
index 0000000..6976e5a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java
@@ -0,0 +1,35 @@
+// 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;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.IRCode;
+
+public class IRCodeProvider {
+
+  private final AppView<AppInfo> appViewForConversion;
+
+  public IRCodeProvider(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    // At this point the code rewritings described by repackaging and synthetic finalization have
+    // not been applied to the code objects. These code rewritings will be applied in the
+    // application writer. We therefore simulate that we are in D8, to allow building IR for each of
+    // the class initializers without applying the unapplied code rewritings, to avoid that we apply
+    // the lens more than once to the same piece of code.
+    AppView<AppInfo> appViewForConversion =
+        AppView.createForD8(AppInfo.createInitialAppInfo(appView.appInfo().app()));
+    appViewForConversion.setGraphLens(appView.graphLens());
+    this.appViewForConversion = appViewForConversion;
+  }
+
+  public IRCode buildIR(ProgramMethod method) {
+    return method
+        .getDefinition()
+        .getCode()
+        .buildIR(method, appViewForConversion, method.getOrigin());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
index 9a6390a..bba2edd 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
@@ -21,20 +21,14 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
-import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
-import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.utils.WorkList;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.List;
@@ -43,36 +37,12 @@
 
   public static InstanceInitializerDescription analyze(
       AppView<? extends AppInfoWithClassHierarchy> appView,
+      IRCodeProvider codeProvider,
       MergeGroup group,
-      ProgramMethod instanceInitializer,
-      ClassInstanceFieldsMerger instanceFieldsMerger,
-      Mode mode) {
+      ProgramMethod instanceInitializer) {
     InstanceInitializerDescription.Builder builder =
         InstanceInitializerDescription.builder(appView, instanceInitializer);
-
-    if (mode.isFinal()) {
-      // TODO(b/189296638): We can't build IR in the final round of class merging without simulating
-      //  that we're in D8.
-      MethodOptimizationInfo optimizationInfo =
-          instanceInitializer.getDefinition().getOptimizationInfo();
-      if (optimizationInfo.mayHaveSideEffects()) {
-        return null;
-      }
-      InstanceInitializerInfo instanceInitializerInfo =
-          optimizationInfo.getContextInsensitiveInstanceInitializerInfo();
-      if (!instanceInitializerInfo.hasParent()) {
-        // We don't know the parent constructor of the first constructor.
-        return null;
-      }
-      DexMethod parent = instanceInitializerInfo.getParent();
-      if (parent.getArity() > 0) {
-        return null;
-      }
-      builder.addInvokeConstructor(parent, ImmutableList.of());
-      return builder.build();
-    }
-
-    IRCode code = instanceInitializer.buildIR(appView);
+    IRCode code = codeProvider.buildIR(instanceInitializer);
     WorkList<BasicBlock> workList = WorkList.newIdentityWorkList(code.entryBlock());
     while (workList.hasNext()) {
       BasicBlock block = workList.next();
@@ -118,12 +88,12 @@
               }
 
               InstanceFieldInitializationInfo initializationInfo =
-                  getInitializationInfo(instancePut.value(), appView, instanceInitializer);
+                  getInitializationInfo(appView, instancePut.value());
               if (initializationInfo == null) {
                 return invalid();
               }
 
-              ProgramField targetField = instanceFieldsMerger.getTargetField(sourceField);
+              ProgramField targetField = group.getTargetInstanceField(sourceField);
               assert targetField != null;
 
               builder.addInstancePut(targetField.getReference(), initializationInfo);
@@ -157,7 +127,7 @@
                   new ArrayList<>(invoke.arguments().size() - 1);
               for (Value argument : Iterables.skip(invoke.arguments(), 1)) {
                 InstanceFieldInitializationInfo initializationInfo =
-                    getInitializationInfo(argument, appView, instanceInitializer);
+                    getInitializationInfo(appView, argument);
                 if (initializationInfo == null) {
                   return invalid();
                 }
@@ -181,23 +151,28 @@
   }
 
   private static InstanceFieldInitializationInfo getInitializationInfo(
-      Value value,
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      ProgramMethod instanceInitializer) {
-    InstanceFieldInitializationInfoFactory factory =
-        appView.instanceFieldInitializationInfoFactory();
-
+      AppView<? extends AppInfoWithClassHierarchy> appView, Value value) {
     Value root = value.getAliasedValue();
-    if (root.isDefinedByInstructionSatisfying(Instruction::isArgument)) {
-      return factory.createArgumentInitializationInfo(
-          value.getDefinition().asArgument().getIndex());
+    if (root.isPhi()) {
+      return null;
     }
 
-    AbstractValue abstractValue = value.getAbstractValue(appView, instanceInitializer);
-    if (abstractValue.isSingleConstValue()) {
-      return abstractValue.asSingleConstValue();
+    Instruction definition = root.getDefinition();
+    if (definition.isArgument()) {
+      return appView
+          .instanceFieldInitializationInfoFactory()
+          .createArgumentInitializationInfo(root.getDefinition().asArgument().getIndex());
     }
-
+    if (definition.isConstNumber()) {
+      return appView
+          .abstractValueFactory()
+          .createSingleNumberValue(definition.asConstNumber().getRawValue());
+    }
+    if (definition.isConstString()) {
+      return appView
+          .abstractValueFactory()
+          .createSingleStringValue(definition.asConstString().getValue());
+    }
     return null;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
index b67c652..eea6e6b 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
@@ -16,13 +16,9 @@
 import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.cf.code.CfPosition;
 import com.android.tools.r8.cf.code.CfReturnVoid;
-import com.android.tools.r8.code.Instruction;
-import com.android.tools.r8.code.InvokeDirectRange;
-import com.android.tools.r8.code.ReturnVoid;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -35,7 +31,6 @@
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
 import com.android.tools.r8.utils.IntBox;
-import com.android.tools.r8.utils.IterableUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -236,34 +231,6 @@
     }
   }
 
-  public DexCode createDexCode(
-      DexMethod newMethodReference,
-      DexMethod originalMethodReference,
-      DexMethod syntheticMethodReference,
-      MergeGroup group,
-      boolean hasClassId,
-      int extraNulls) {
-    assert !hasClassId;
-    assert extraNulls == 0;
-    assert parentConstructorArguments.isEmpty();
-    assert instanceFieldAssignmentsPre.isEmpty();
-    assert instanceFieldAssignmentsPost.isEmpty();
-    Instruction[] instructions = new Instruction[2];
-    instructions[0] = new InvokeDirectRange(0, 1, parentConstructor);
-    instructions[1] = new ReturnVoid();
-    int incomingRegisterSize =
-        1 + IterableUtils.sumInt(newMethodReference.getParameters(), DexType::getRequiredRegisters);
-    int outgoingRegisterSize = 1;
-    return new DexCode(
-        incomingRegisterSize,
-        incomingRegisterSize,
-        outgoingRegisterSize,
-        instructions,
-        new DexCode.Try[0],
-        new DexCode.TryHandler[0],
-        null);
-  }
-
   @Override
   public boolean equals(Object obj) {
     if (obj == null || getClass() != obj.getClass()) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
index 862a23c..e17c574 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
 import com.android.tools.r8.horizontalclassmerging.code.ConstructorEntryPointSynthesizedCode;
+import com.android.tools.r8.horizontalclassmerging.code.SyntheticInitializerConverter;
 import com.android.tools.r8.ir.conversion.ExtraConstantIntParameter;
 import com.android.tools.r8.ir.conversion.ExtraParameter;
 import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
@@ -277,16 +278,7 @@
       boolean needsClassId,
       int extraNulls) {
     if (hasInstanceInitializerDescription()) {
-      if (mode.isInitial() || appView.options().isGeneratingClassFiles()) {
-        return instanceInitializerDescription.createCfCode(
-            newMethodReference,
-            getOriginalMethodReference(),
-            syntheticMethodReference,
-            group,
-            needsClassId,
-            extraNulls);
-      }
-      return instanceInitializerDescription.createDexCode(
+      return instanceInitializerDescription.createCfCode(
           newMethodReference,
           getOriginalMethodReference(),
           syntheticMethodReference,
@@ -311,7 +303,8 @@
   /** Synthesize a new method which selects the constructor based on a parameter type. */
   void merge(
       ClassMethodsBuilder classMethodsBuilder,
-      SyntheticArgumentClass syntheticArgumentClass) {
+      SyntheticArgumentClass syntheticArgumentClass,
+      SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
     ProgramMethod representative = ListUtils.first(instanceInitializers);
 
     // Create merged instance initializer reference.
@@ -378,5 +371,15 @@
             true,
             getNewClassFileVersion());
     classMethodsBuilder.addDirectMethod(newInstanceInitializer);
+
+    if (mode.isFinal()) {
+      if (appView.options().isGeneratingDex() && !newInstanceInitializer.getCode().isDexCode()) {
+        syntheticInitializerConverterBuilder.add(
+            new ProgramMethod(group.getTarget(), newInstanceInitializer));
+      } else {
+        assert !appView.options().isGeneratingClassFiles()
+            || newInstanceInitializer.getCode().isCfCode();
+      }
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
index b5e485b..dbf0943 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
@@ -39,8 +39,8 @@
   public static InstanceInitializerMergerCollection create(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       Reference2IntMap<DexType> classIdentifiers,
+      IRCodeProvider codeProvider,
       MergeGroup group,
-      ClassInstanceFieldsMerger instanceFieldsMerger,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       Mode mode) {
     // Create an instance initializer merger for each group of instance initializers that are
@@ -54,7 +54,7 @@
                 instanceInitializer -> {
                   InstanceInitializerDescription description =
                       InstanceInitializerAnalysis.analyze(
-                          appView, group, instanceInitializer, instanceFieldsMerger, mode);
+                          appView, codeProvider, group, instanceInitializer);
                   if (description != null) {
                     buildersByDescription
                         .computeIfAbsent(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
index ba5d775..458f0ba 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
@@ -6,12 +6,20 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
+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.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.shaking.KeepClassInfo;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.IteratorUtils;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap;
+import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap;
 import com.google.common.collect.Iterables;
 import java.util.Collection;
 import java.util.Iterator;
@@ -29,6 +37,8 @@
   private DexProgramClass target = null;
   private Metadata metadata = null;
 
+  private BidirectionalManyToOneMap<DexEncodedField, DexEncodedField> instanceFieldMap;
+
   public MergeGroup() {
     this.classes = new LinkedList<>();
   }
@@ -104,6 +114,31 @@
     this.classIdField = classIdField;
   }
 
+  public boolean hasInstanceFieldMap() {
+    return instanceFieldMap != null;
+  }
+
+  public BidirectionalManyToOneMap<DexEncodedField, DexEncodedField> getInstanceFieldMap() {
+    assert hasInstanceFieldMap();
+    return instanceFieldMap;
+  }
+
+  public void selectInstanceFieldMap(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    assert hasTarget();
+    MutableBidirectionalManyToOneMap<DexEncodedField, DexEncodedField> instanceFieldMap =
+        BidirectionalManyToOneHashMap.newLinkedHashMap();
+    forEachSource(
+        source ->
+            ClassInstanceFieldsMerger.mapFields(appView, source, target, instanceFieldMap::put));
+    setInstanceFieldMap(instanceFieldMap);
+  }
+
+  public void setInstanceFieldMap(
+      BidirectionalManyToOneMap<DexEncodedField, DexEncodedField> instanceFieldMap) {
+    assert !hasInstanceFieldMap();
+    this.instanceFieldMap = instanceFieldMap;
+  }
+
   public Iterable<DexProgramClass> getSources() {
     assert hasTarget();
     return Iterables.filter(classes, clazz -> clazz != target);
@@ -122,8 +157,40 @@
     return target;
   }
 
-  public void setTarget(DexProgramClass target) {
-    assert classes.contains(target);
+  public ProgramField getTargetInstanceField(ProgramField field) {
+    assert hasTarget();
+    assert hasInstanceFieldMap();
+    if (field.getHolder() == getTarget()) {
+      return field;
+    }
+    DexEncodedField targetField = getInstanceFieldMap().get(field.getDefinition());
+    return new ProgramField(getTarget(), targetField);
+  }
+
+  public void selectTarget(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    Iterable<DexProgramClass> candidates = Iterables.filter(getClasses(), DexClass::isPublic);
+    if (IterableUtils.isEmpty(candidates)) {
+      candidates = getClasses();
+    }
+    Iterator<DexProgramClass> candidateIterator = candidates.iterator();
+    DexProgramClass target = IterableUtils.first(candidates);
+    while (candidateIterator.hasNext()) {
+      DexProgramClass current = candidateIterator.next();
+      KeepClassInfo keepClassInfo = appView.getKeepInfo().getClassInfo(current);
+      if (keepClassInfo.isMinificationAllowed(appView.options())) {
+        target = current;
+        break;
+      }
+      // Select the target with the shortest name.
+      if (current.getType().getDescriptor().size() < target.getType().getDescriptor().size) {
+        target = current;
+      }
+    }
+    setTarget(appView.testing().horizontalClassMergingTarget.apply(appView, candidates, target));
+  }
+
+  private void setTarget(DexProgramClass target) {
+    assert !hasTarget();
     this.target = target;
   }
 
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 6969f94..0d3bf0d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.horizontalclassmerging.policies.AllInstantiatedOrUninstantiated;
 import com.android.tools.r8.horizontalclassmerging.policies.CheckAbstractClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.CheckSyntheticClasses;
+import com.android.tools.r8.horizontalclassmerging.policies.FinalizeMergeGroup;
 import com.android.tools.r8.horizontalclassmerging.policies.LimitClassGroups;
 import com.android.tools.r8.horizontalclassmerging.policies.MinimizeInstanceFieldCasts;
 import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotationClasses;
@@ -56,12 +57,13 @@
 
   public static List<Policy> getPolicies(
       AppView<? extends AppInfoWithClassHierarchy> appView,
+      IRCodeProvider codeProvider,
       Mode mode,
       RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
     List<Policy> policies =
         ImmutableList.<Policy>builder()
             .addAll(getSingleClassPolicies(appView, mode, runtimeTypeCheckInfo))
-            .addAll(getMultiClassPolicies(appView, mode, runtimeTypeCheckInfo))
+            .addAll(getMultiClassPolicies(appView, codeProvider, mode, runtimeTypeCheckInfo))
             .build();
     assert verifyPolicyOrderingConstraints(policies);
     return policies;
@@ -142,6 +144,7 @@
 
   private static List<Policy> getMultiClassPolicies(
       AppView<? extends AppInfoWithClassHierarchy> appView,
+      IRCodeProvider codeProvider,
       Mode mode,
       RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
     ImmutableList.Builder<Policy> builder = ImmutableList.builder();
@@ -162,16 +165,24 @@
           new MinimizeInstanceFieldCasts());
     } else {
       assert mode.isFinal();
-      // TODO(b/185472598): Add support for merging class initializers with dex code.
       builder.add(
-          new NoInstanceInitializerMerging(mode),
           new NoVirtualMethodMerging(appView, mode),
           new NoConstructorCollisions(appView, mode));
     }
 
     addMultiClassPoliciesForInterfaceMerging(appView, mode, builder);
 
-    return builder.add(new LimitClassGroups(appView)).build();
+    builder.add(new LimitClassGroups(appView));
+
+    if (mode.isFinal()) {
+      // This needs to reason about equivalence of instance initializers, which relies on the
+      // mapping from instance fields on source classes to the instance fields on target classes.
+      // This policy therefore selects a target for each merge group and creates the mapping for
+      // instance fields. For this reason we run this policy in the very end.
+      builder.add(new NoInstanceInitializerMerging(appView, codeProvider, mode));
+    }
+
+    return builder.add(new FinalizeMergeGroup(appView, mode)).build();
   }
 
   private static void addRequiredMultiClassPolicies(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticClassInitializerConverter.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
similarity index 66%
rename from src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticClassInitializerConverter.java
rename to src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
index a31333c..d2414ed 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticClassInitializerConverter.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.IRCodeProvider;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
@@ -23,19 +23,24 @@
  * Converts synthetic class initializers that have been created as a result of merging class
  * initializers into a single class initializer to DEX.
  */
-public class SyntheticClassInitializerConverter {
+public class SyntheticInitializerConverter {
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
-  private final List<MergeGroup> groups;
+  private final IRCodeProvider codeProvider;
+  private final List<ProgramMethod> methods;
 
-  private SyntheticClassInitializerConverter(
-      AppView<? extends AppInfoWithClassHierarchy> appView, List<MergeGroup> groups) {
+  private SyntheticInitializerConverter(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      IRCodeProvider codeProvider,
+      List<ProgramMethod> methods) {
     this.appView = appView;
-    this.groups = groups;
+    this.codeProvider = codeProvider;
+    this.methods = methods;
   }
 
-  public static Builder builder(AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return new Builder(appView);
+  public static Builder builder(
+      AppView<? extends AppInfoWithClassHierarchy> appView, IRCodeProvider codeProvider) {
+    return new Builder(appView, codeProvider);
   }
 
   public void convert(ExecutorService executorService) throws ExecutionException {
@@ -51,14 +56,9 @@
     // Build IR for each of the class initializers and finalize.
     IRConverter converter = new IRConverter(appViewForConversion, Timing.empty());
     ThreadUtils.processItems(
-        groups,
-        group -> {
-          ProgramMethod classInitializer = group.getTarget().getProgramClassInitializer();
-          IRCode code =
-              classInitializer
-                  .getDefinition()
-                  .getCode()
-                  .buildIR(classInitializer, appViewForConversion, classInitializer.getOrigin());
+        methods,
+        method -> {
+          IRCode code = codeProvider.buildIR(method);
           converter.removeDeadCodeAndFinalizeIR(
               code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
         },
@@ -66,25 +66,28 @@
   }
 
   public boolean isEmpty() {
-    return groups.isEmpty();
+    return methods.isEmpty();
   }
 
   public static class Builder {
 
     private final AppView<? extends AppInfoWithClassHierarchy> appView;
-    private final List<MergeGroup> groups = new ArrayList<>();
+    private final IRCodeProvider codeProvider;
+    private final List<ProgramMethod> methods = new ArrayList<>();
 
-    private Builder(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    private Builder(
+        AppView<? extends AppInfoWithClassHierarchy> appView, IRCodeProvider codeProvider) {
       this.appView = appView;
+      this.codeProvider = codeProvider;
     }
 
-    public Builder add(MergeGroup group) {
-      this.groups.add(group);
+    public Builder add(ProgramMethod method) {
+      this.methods.add(method);
       return this;
     }
 
-    public SyntheticClassInitializerConverter build() {
-      return new SyntheticClassInitializerConverter(appView, groups);
+    public SyntheticInitializerConverter build() {
+      return new SyntheticInitializerConverter(appView, codeProvider, methods);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/FinalizeMergeGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/FinalizeMergeGroup.java
new file mode 100644
index 0000000..705c8cd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/FinalizeMergeGroup.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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.SetUtils;
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Identifies when instance initializer merging is required and bails out. This is needed to ensure
+ * that we don't need to append extra null arguments at constructor call sites, such that the result
+ * of the final round of class merging can be described as a renaming only.
+ *
+ * <p>This policy requires that all instance initializers with the same signature (relaxed, by
+ * converting references types to java.lang.Object) have the same behavior.
+ */
+public class FinalizeMergeGroup extends MultiClassPolicy {
+
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final Mode mode;
+
+  public FinalizeMergeGroup(AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
+    this.appView = appView;
+    this.mode = mode;
+  }
+
+  @Override
+  public Collection<MergeGroup> apply(MergeGroup group) {
+    if (mode.isInitial() || group.isInterfaceGroup()) {
+      group.selectTarget(appView);
+      group.selectInstanceFieldMap(appView);
+    } else {
+      // In the final round of merging each group should be finalized by the
+      // NoInstanceInitializerMerging policy.
+      assert verifyAlreadyFinalized(group);
+    }
+    return ListUtils.newLinkedList(group);
+  }
+
+  @Override
+  public String getName() {
+    return "FinalizeMergeGroup";
+  }
+
+  @Override
+  public boolean isIdentityForInterfaceGroups() {
+    return true;
+  }
+
+  private boolean verifyAlreadyFinalized(MergeGroup group) {
+    assert group.hasTarget();
+    assert group.getClasses().contains(group.getTarget());
+    assert group.hasInstanceFieldMap();
+    Set<DexType> types =
+        SetUtils.newIdentityHashSet(
+            builder -> group.forEach(clazz -> builder.accept(clazz.getType())));
+    group
+        .getInstanceFieldMap()
+        .forEach(
+            (sourceField, targetField) -> {
+              assert types.contains(sourceField.getHolderType());
+              assert types.contains(targetField.getHolderType());
+            });
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java
index 5b058c9..191adc4 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java
@@ -111,9 +111,9 @@
   private DexProto rewriteProto(DexProto proto, Map<DexType, MergeGroup> groups) {
     DexType[] parameters =
         ArrayUtils.map(
-            DexType[].class,
             proto.getParameters().values,
-            parameter -> rewriteType(parameter, groups));
+            parameter -> rewriteType(parameter, groups),
+            DexType.EMPTY_ARRAY);
     return dexItemFactory.createProto(rewriteType(proto.getReturnType(), groups), parameters);
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializerMerging.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializerMerging.java
index 0ff172c..02b6bbd 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializerMerging.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializerMerging.java
@@ -4,103 +4,261 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodSignature;
 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.ProgramMethod;
+import com.android.tools.r8.horizontalclassmerging.ClassInstanceFieldsMerger;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
+import com.android.tools.r8.horizontalclassmerging.IRCodeProvider;
+import com.android.tools.r8.horizontalclassmerging.InstanceInitializerAnalysis;
+import com.android.tools.r8.horizontalclassmerging.InstanceInitializerDescription;
 import com.android.tools.r8.horizontalclassmerging.MergeGroup;
 import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
-import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
+import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap;
+import com.android.tools.r8.utils.collections.ProgramMethodMap;
+import com.google.common.collect.Iterators;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
 
 /**
  * Identifies when instance initializer merging is required and bails out. This is needed to ensure
  * that we don't need to append extra null arguments at constructor call sites, such that the result
  * of the final round of class merging can be described as a renaming only.
+ *
+ * <p>This policy requires that all instance initializers with the same signature (relaxed, by
+ * converting references types to java.lang.Object) have the same behavior.
  */
 public class NoInstanceInitializerMerging extends MultiClassPolicy {
 
-  public NoInstanceInitializerMerging(Mode mode) {
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final IRCodeProvider codeProvider;
+
+  public NoInstanceInitializerMerging(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      IRCodeProvider codeProvider,
+      Mode mode) {
     assert mode.isFinal();
+    this.appView = appView;
+    this.codeProvider = codeProvider;
   }
 
   @Override
   public Collection<MergeGroup> apply(MergeGroup group) {
+    assert !group.hasTarget();
+    assert !group.hasInstanceFieldMap();
+
+    if (group.isInterfaceGroup()) {
+      return ListUtils.newLinkedList(group);
+    }
+
+    // When we merge equivalent instance initializers with different protos, we find the least upper
+    // bound of each parameter type. As a result of this, the final instance initializer signatures
+    // are not known until all instance initializers in the group are known. Therefore, we disallow
+    // merging of classes that have multiple methods with the same relaxed method signature (where
+    // reference parameters are converted to java.lang.Object), to ensure that merging will result
+    // in a simple renaming (specifically, we must not need to append null arguments to constructor
+    // calls due to constructor collisions).
+    group.removeIf(this::hasMultipleInstanceInitializersWithSameRelaxedSignature);
+
+    if (group.isEmpty()) {
+      return Collections.emptyList();
+    }
+
+    // We want to allow merging of equivalent instance initializers. Equivalence depends on the
+    // mapping of instance fields, so we must compute this mapping now.
+    group.selectTarget(appView);
+    group.selectInstanceFieldMap(appView);
+
     Map<MergeGroup, Map<DexMethodSignature, ProgramMethod>> newGroups = new LinkedHashMap<>();
 
-    for (DexProgramClass clazz : group) {
-      Map<DexMethodSignature, ProgramMethod> classSignatures = new HashMap<>();
-      clazz.forEachProgramInstanceInitializer(
-          method -> classSignatures.put(method.getMethodSignature(), method));
+    // Caching of instance initializer descriptions, which are used to determine equivalence.
+    // TODO(b/181846319): Make this cache available to the instance initializer merger so that we
+    //  don't reanalyze instance initializers.
+    ProgramMethodMap<Optional<InstanceInitializerDescription>> instanceInitializerDescriptions =
+        ProgramMethodMap.create();
+    Function<ProgramMethod, Optional<InstanceInitializerDescription>>
+        instanceInitializerDescriptionProvider =
+            instanceInitializer ->
+                getOrComputeInstanceInitializerDescription(
+                    group, instanceInitializer, instanceInitializerDescriptions);
 
+    // Partition group into smaller groups where there are no (non-equivalent) instance initializer
+    // collisions.
+    for (DexProgramClass clazz : group) {
       MergeGroup newGroup = null;
+      Map<DexMethodSignature, ProgramMethod> classInstanceInitializers =
+          getInstanceInitializersByRelaxedSignature(clazz);
       for (Entry<MergeGroup, Map<DexMethodSignature, ProgramMethod>> entry : newGroups.entrySet()) {
-        Map<DexMethodSignature, ProgramMethod> groupSignatures = entry.getValue();
-        if (canAddClassToGroup(classSignatures.values(), groupSignatures)) {
-          newGroup = entry.getKey();
-          groupSignatures.putAll(classSignatures);
+        MergeGroup candidateGroup = entry.getKey();
+        Map<DexMethodSignature, ProgramMethod> groupInstanceInitializers = entry.getValue();
+        if (canAddClassToGroup(
+            classInstanceInitializers,
+            groupInstanceInitializers,
+            instanceInitializerDescriptionProvider)) {
+          newGroup = candidateGroup;
+          classInstanceInitializers.forEach(groupInstanceInitializers::put);
           break;
         }
       }
-
       if (newGroup != null) {
         newGroup.add(clazz);
       } else {
-        newGroups.put(new MergeGroup(clazz), classSignatures);
+        newGroups.put(new MergeGroup(clazz), classInstanceInitializers);
       }
     }
 
-    return removeTrivialGroups(newGroups.keySet());
+    // Remove trivial groups and finalize the newly created groups.
+    Collection<MergeGroup> newNonTrivialGroups = removeTrivialGroups(newGroups.keySet());
+    setInstanceFieldMaps(newNonTrivialGroups, group);
+    return newNonTrivialGroups;
   }
 
   private boolean canAddClassToGroup(
-      Iterable<ProgramMethod> classMethods,
-      Map<DexMethodSignature, ProgramMethod> groupSignatures) {
-    for (ProgramMethod classMethod : classMethods) {
-      ProgramMethod groupMethod = groupSignatures.get(classMethod.getMethodSignature());
-      if (groupMethod != null && !equivalent(classMethod, groupMethod)) {
+      Map<DexMethodSignature, ProgramMethod> classInstanceInitializers,
+      Map<DexMethodSignature, ProgramMethod> groupInstanceInitializers,
+      Function<ProgramMethod, Optional<InstanceInitializerDescription>>
+          instanceInitializerDescriptionProvider) {
+    for (Entry<DexMethodSignature, ProgramMethod> entry : classInstanceInitializers.entrySet()) {
+      DexMethodSignature relaxedSignature = entry.getKey();
+      ProgramMethod classInstanceInitializer = entry.getValue();
+      ProgramMethod groupInstanceInitializer = groupInstanceInitializers.get(relaxedSignature);
+      if (groupInstanceInitializer == null) {
+        continue;
+      }
+
+      Optional<InstanceInitializerDescription> classInstanceInitializerDescription =
+          instanceInitializerDescriptionProvider.apply(classInstanceInitializer);
+      if (!classInstanceInitializerDescription.isPresent()) {
+        return false;
+      }
+
+      Optional<InstanceInitializerDescription> groupInstanceInitializerDescription =
+          instanceInitializerDescriptionProvider.apply(groupInstanceInitializer);
+      if (!groupInstanceInitializerDescription.isPresent()
+          || !classInstanceInitializerDescription.equals(groupInstanceInitializerDescription)) {
         return false;
       }
     }
     return true;
   }
 
-  // For now, only recognize constructors with 0 parameters that call the same parent constructor.
-  private boolean equivalent(ProgramMethod method, ProgramMethod other) {
-    if (!method.getProto().getParameters().isEmpty()) {
+  private boolean hasMultipleInstanceInitializersWithSameRelaxedSignature(DexProgramClass clazz) {
+    Iterator<ProgramMethod> instanceInitializers = clazz.programInstanceInitializers().iterator();
+    if (!instanceInitializers.hasNext()) {
+      // No instance initializers.
       return false;
     }
 
-    MethodOptimizationInfo optimizationInfo = method.getDefinition().getOptimizationInfo();
-    InstanceInitializerInfo instanceInitializerInfo =
-        optimizationInfo.getContextInsensitiveInstanceInitializerInfo();
-    if (instanceInitializerInfo.isDefaultInstanceInitializerInfo()) {
+    ProgramMethod first = instanceInitializers.next();
+    if (!instanceInitializers.hasNext()) {
+      // Only a single instance initializer.
       return false;
     }
 
-    InstanceInitializerInfo otherInstanceInitializerInfo =
-        other.getDefinition().getOptimizationInfo().getContextInsensitiveInstanceInitializerInfo();
-    assert otherInstanceInitializerInfo.isNonTrivialInstanceInitializerInfo();
-    if (!instanceInitializerInfo.hasParent()
-        || instanceInitializerInfo.getParent().getArity() > 0) {
-      return false;
-    }
+    Set<DexMethod> seen = SetUtils.newIdentityHashSet(getRelaxedSignature(first));
+    return Iterators.any(
+        instanceInitializers,
+        instanceInitializer -> !seen.add(getRelaxedSignature(instanceInitializer)));
+  }
 
-    if (instanceInitializerInfo.getParent() != otherInstanceInitializerInfo.getParent()) {
-      return false;
+  private Map<DexMethodSignature, ProgramMethod> getInstanceInitializersByRelaxedSignature(
+      DexProgramClass clazz) {
+    Map<DexMethodSignature, ProgramMethod> result = new HashMap<>();
+    for (ProgramMethod instanceInitializer : clazz.programInstanceInitializers()) {
+      DexMethodSignature relaxedSignature = getRelaxedSignature(instanceInitializer).getSignature();
+      ProgramMethod previous = result.put(relaxedSignature, instanceInitializer);
+      assert previous == null;
     }
+    return result;
+  }
 
-    return !method.getDefinition().getOptimizationInfo().mayHaveSideEffects()
-        && !other.getDefinition().getOptimizationInfo().mayHaveSideEffects();
+  private Optional<InstanceInitializerDescription> getOrComputeInstanceInitializerDescription(
+      MergeGroup group,
+      ProgramMethod instanceInitializer,
+      ProgramMethodMap<Optional<InstanceInitializerDescription>> instanceInitializerDescriptions) {
+    return instanceInitializerDescriptions.computeIfAbsent(
+        instanceInitializer,
+        key -> {
+          InstanceInitializerDescription instanceInitializerDescription =
+              InstanceInitializerAnalysis.analyze(
+                  appView, codeProvider, group, instanceInitializer);
+          return Optional.ofNullable(instanceInitializerDescription);
+        });
+  }
+
+  private DexMethod getRelaxedSignature(ProgramMethod instanceInitializer) {
+    DexType objectType = appView.dexItemFactory().objectType;
+    DexTypeList parameters = instanceInitializer.getParameters();
+    DexTypeList relaxedParameters =
+        parameters.map(parameter -> parameter.isPrimitiveType() ? parameter : objectType);
+    return parameters != relaxedParameters
+        ? appView
+            .dexItemFactory()
+            .createInstanceInitializer(instanceInitializer.getHolderType(), relaxedParameters)
+        : instanceInitializer.getReference();
+  }
+
+  private void setInstanceFieldMaps(Iterable<MergeGroup> newGroups, MergeGroup group) {
+    for (MergeGroup newGroup : newGroups) {
+      // Set target.
+      newGroup.selectTarget(appView);
+
+      // Construct mapping from instance fields on old target to instance fields on new target.
+      // Note the importance of this: If we create a fresh mapping from the instance fields of each
+      // source class to the new target class, we could invalidate the constructor equivalence.
+      Map<DexEncodedField, DexEncodedField> oldTargetToNewTargetInstanceFieldMap =
+          new IdentityHashMap<>();
+      if (newGroup.getTarget() != group.getTarget()) {
+        ClassInstanceFieldsMerger.mapFields(
+            appView,
+            group.getTarget(),
+            newGroup.getTarget(),
+            oldTargetToNewTargetInstanceFieldMap::put);
+      }
+
+      // Construct mapping from source to target fields.
+      MutableBidirectionalManyToOneMap<DexEncodedField, DexEncodedField> instanceFieldMap =
+          BidirectionalManyToOneHashMap.newLinkedHashMap();
+      newGroup.forEachSource(
+          source ->
+              source.forEachProgramInstanceField(
+                  sourceField -> {
+                    DexEncodedField oldTargetInstanceField =
+                        group.getTargetInstanceField(sourceField).getDefinition();
+                    DexEncodedField newTargetInstanceField =
+                        oldTargetToNewTargetInstanceFieldMap.getOrDefault(
+                            oldTargetInstanceField, oldTargetInstanceField);
+                    instanceFieldMap.put(sourceField.getDefinition(), newTargetInstanceField);
+                  }));
+      newGroup.setInstanceFieldMap(instanceFieldMap);
+    }
   }
 
   @Override
   public String getName() {
     return "NoInstanceInitializerMerging";
   }
+
+  @Override
+  public boolean isIdentityForInterfaceGroups() {
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index 06e1e90..3aa2dac 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.SafeCheckCast;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
@@ -373,7 +374,7 @@
         DexType rawReceiverType = receiverType.getClassType();
         if (appInfo.isStrictSubtypeOf(rawReceiverType, references.generatedMessageLiteType)) {
           Value dest = code.createValue(receiverType.asMaybeNull(), checkCast.getLocalInfo());
-          CheckCast replacement = new CheckCast(dest, checkCast.object(), rawReceiverType);
+          SafeCheckCast replacement = new SafeCheckCast(dest, checkCast.object(), rawReceiverType);
           instructionIterator.replaceCurrentInstruction(replacement, affectedValues);
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 6917a40..2448b0c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -788,8 +788,8 @@
       Value receiver = invoke.inValues().get(0);
       TypeElement castTypeLattice =
           TypeElement.fromDexType(downcast, receiver.getType().nullability(), appView);
-      CheckCast castInstruction =
-          new CheckCast(code.createValue(castTypeLattice), receiver, downcast);
+      SafeCheckCast castInstruction =
+          new SafeCheckCast(code.createValue(castTypeLattice), receiver, downcast);
       castInstruction.setPosition(invoke.getPosition());
 
       // Splice in the check cast operation.
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 3e0f741..b02559a 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
@@ -67,14 +67,13 @@
     // we have to insert a move before the check cast instruction.
     int inRegister = builder.allocatedRegister(inValues.get(0), getNumber());
     if (outValue == null) {
-      builder.add(this, new com.android.tools.r8.code.CheckCast(inRegister, type));
+      builder.add(this, createCheckCast(inRegister));
     } else {
       int outRegister = builder.allocatedRegister(outValue, getNumber());
       if (inRegister == outRegister) {
-        builder.add(this, new com.android.tools.r8.code.CheckCast(outRegister, type));
+        builder.add(this, createCheckCast(outRegister));
       } else {
-        com.android.tools.r8.code.CheckCast cast =
-            new com.android.tools.r8.code.CheckCast(outRegister, type);
+        com.android.tools.r8.code.CheckCast cast = createCheckCast(outRegister);
         if (outRegister <= Constants.U4BIT_MAX && inRegister <= Constants.U4BIT_MAX) {
           builder.add(this, new MoveObject(outRegister, inRegister), cast);
         } else {
@@ -84,6 +83,10 @@
     }
   }
 
+  com.android.tools.r8.code.CheckCast createCheckCast(int register) {
+    return new com.android.tools.r8.code.CheckCast(register, getType());
+  }
+
   @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.isCheckCast() && other.asCheckCast().type == type;
@@ -237,8 +240,8 @@
 
   public static class Builder extends BuilderBase<Builder, CheckCast> {
 
-    private DexType castType;
-    private Value object;
+    protected DexType castType;
+    protected Value object;
 
     public Builder setCastType(DexType castType) {
       this.castType = castType;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup.java b/src/main/java/com/android/tools/r8/ir/code/Dup.java
index 070b8e9..3bdc40c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup.java
@@ -37,14 +37,12 @@
   }
 
   @Override
-  public void setOutValue(Value value) {
-    assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed() ||
-        value instanceof StackValues;
-    this.outValue = value;
-    this.outValue.definition = this;
-    for (StackValue val : ((StackValues)value).getStackValues()) {
-      val.definition = this;
+  public Value setOutValue(Value newOutValue) {
+    assert newOutValue instanceof StackValues;
+    for (StackValue stackValue : ((StackValues) newOutValue).getStackValues()) {
+      stackValue.definition = this;
     }
+    return super.setOutValue(newOutValue);
   }
 
   private StackValue[] getStackValues() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup2.java b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
index 7053383..a01b789 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup2.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
@@ -51,14 +51,12 @@
   }
 
   @Override
-  public void setOutValue(Value value) {
-    assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed() ||
-        value instanceof StackValues;
-    this.outValue = value;
-    this.outValue.definition = this;
-    for (StackValue val : ((StackValues)value).getStackValues()) {
-      val.definition = this;
+  public Value setOutValue(Value newOutValue) {
+    assert newOutValue instanceof StackValues;
+    for (StackValue stackValue : ((StackValues) newOutValue).getStackValues()) {
+      stackValue.definition = this;
     }
+    return super.setOutValue(newOutValue);
   }
 
   private StackValue[] getStackValues() {
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 a1c7d45..708ffcf 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
@@ -141,12 +141,18 @@
     return outValue;
   }
 
-  public void setOutValue(Value value) {
-    assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed();
-    outValue = value;
-    if (outValue != null) {
-      outValue.definition = this;
+  public Value clearOutValue() {
+    return setOutValue(null);
+  }
+
+  // Returns the previous out-value, if any.
+  public Value setOutValue(Value newOutValue) {
+    Value previousOutValue = outValue();
+    outValue = newOutValue;
+    if (newOutValue != null) {
+      newOutValue.definition = this;
     }
+    return previousOutValue;
   }
 
   public Value swapOutValue(Value newOutValue) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/SafeCheckCast.java b/src/main/java/com/android/tools/r8/ir/code/SafeCheckCast.java
new file mode 100644
index 0000000..8b36c90
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/SafeCheckCast.java
@@ -0,0 +1,45 @@
+// 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.code;
+
+import com.android.tools.r8.cf.code.CfSafeCheckCast;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+
+public class SafeCheckCast extends CheckCast {
+
+  public SafeCheckCast(Value dest, Value value, DexType type) {
+    super(dest, value, type);
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfSafeCheckCast(getType()));
+  }
+
+  @Override
+  com.android.tools.r8.code.CheckCast createCheckCast(int register) {
+    return new com.android.tools.r8.code.SafeCheckCast(register, getType());
+  }
+
+  @Override
+  public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
+    return false;
+  }
+
+  public static class Builder extends CheckCast.Builder {
+
+    @Override
+    public CheckCast build() {
+      return amend(new SafeCheckCast(outValue, object, castType));
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Swap.java b/src/main/java/com/android/tools/r8/ir/code/Swap.java
index 5dfa623..777eec1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Swap.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Swap.java
@@ -40,14 +40,12 @@
   }
 
   @Override
-  public void setOutValue(Value value) {
-    assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed() ||
-        value instanceof StackValues;
-    this.outValue = value;
-    this.outValue.definition = this;
-    for (StackValue val : ((StackValues)value).getStackValues()) {
-      val.definition = this;
+  public Value setOutValue(Value newOutValue) {
+    assert newOutValue instanceof StackValues;
+    for (StackValue stackValue : ((StackValues) newOutValue).getStackValues()) {
+      stackValue.definition = this;
     }
+    return super.setOutValue(newOutValue);
   }
 
   private StackValue[] getStackValues() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index e01cd47..5a492f7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -134,7 +134,7 @@
     this.code = code;
   }
 
-  public CfCode build(DeadCodeRemover deadCodeRemover) {
+  public CfCode build(DeadCodeRemover deadCodeRemover, MethodConversionOptions conversionOptions) {
     computeInitializers();
     TypeVerificationHelper typeVerificationHelper = new TypeVerificationHelper(appView, code);
     typeVerificationHelper.computeVerificationTypes();
@@ -168,10 +168,13 @@
 
     loadStoreHelper.insertPhiMoves(registerAllocator);
 
-    for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) {
-      CodeRewriter.collapseTrivialGotos(code);
-      PeepholeOptimizer.removeIdenticalPredecessorBlocks(code, registerAllocator);
-      PeepholeOptimizer.shareIdenticalBlockSuffix(code, registerAllocator, SUFFIX_SHARING_OVERHEAD);
+    if (conversionOptions.isPeepholeOptimizationsEnabled()) {
+      for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) {
+        CodeRewriter.collapseTrivialGotos(code);
+        PeepholeOptimizer.removeIdenticalPredecessorBlocks(code, registerAllocator);
+        PeepholeOptimizer.shareIdenticalBlockSuffix(
+            code, registerAllocator, SUFFIX_SHARING_OVERHEAD);
+      }
     }
 
     rewriteIincPatterns();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 4ae55f6..ba1a15c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -102,6 +102,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Rem;
 import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.SafeCheckCast;
 import com.android.tools.r8.ir.code.Shl;
 import com.android.tools.r8.ir.code.Shr;
 import com.android.tools.r8.ir.code.StaticGet;
@@ -1181,11 +1182,20 @@
   }
 
   public void addCheckCast(int value, DexType type) {
+    internalAddCheckCast(value, type, false);
+  }
+
+  public void addSafeCheckCast(int value, DexType type) {
+    internalAddCheckCast(value, type, true);
+  }
+
+  private void internalAddCheckCast(int value, DexType type, boolean isSafe) {
     Value in = readRegister(value, ValueTypeConstraint.OBJECT);
     TypeElement castTypeLattice =
         TypeElement.fromDexType(type, in.getType().nullability(), appView);
     Value out = writeRegister(value, castTypeLattice, ThrowingInfo.CAN_THROW);
-    CheckCast instruction = new CheckCast(out, in, type);
+    CheckCast instruction =
+        isSafe ? new SafeCheckCast(out, in, type) : new CheckCast(out, in, type);
     assert instruction.instructionTypeCanThrow();
     add(instruction);
   }
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 8ae52ef..f68c904 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
@@ -42,6 +42,8 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.DefaultMethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfClassDesugaringEventConsumer.D8CfClassDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
@@ -57,6 +59,7 @@
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.AssumeInserter;
+import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
@@ -670,6 +673,15 @@
     // 2) Revisit DexEncodedMethods for the collected candidates.
 
     printPhase("Primary optimization pass");
+
+    appView.withCallSiteOptimizationInfoPropagator(
+        optimization -> {
+          optimization.abandonCallSitePropagationForLambdaImplementationMethods(
+              executorService, timing);
+          optimization.abandonCallSitePropagationForPinnedMethodsAndOverrides(
+              executorService, timing);
+        });
+
     if (fieldAccessAnalysis != null) {
       fieldAccessAnalysis.fieldAssignmentTracker().initialize();
     }
@@ -678,8 +690,7 @@
     GraphLens initialGraphLensForIR = appView.graphLens();
     GraphLens graphLensForIR = initialGraphLensForIR;
     OptimizationFeedbackDelayed feedback = delayedOptimizationFeedback;
-    PostMethodProcessor.Builder postMethodProcessorBuilder =
-        new PostMethodProcessor.Builder(getOptimizationsForPostIRProcessing());
+    PostMethodProcessor.Builder postMethodProcessorBuilder = new PostMethodProcessor.Builder();
     {
       timing.begin("Build primary method processor");
       PrimaryMethodProcessor primaryMethodProcessor =
@@ -721,9 +732,9 @@
     //   1) Second pass for methods whose collected call site information become more precise.
     //   2) Second inlining pass for dealing with double inline callers.
     printPhase("Post optimization pass");
-    if (appView.callSiteOptimizationInfoPropagator() != null) {
-      postMethodProcessorBuilder.put(appView.callSiteOptimizationInfoPropagator());
-    }
+    appView.withCallSiteOptimizationInfoPropagator(
+        optimization ->
+            postMethodProcessorBuilder.put(appView.callSiteOptimizationInfoPropagator()));
     if (inliner != null) {
       postMethodProcessorBuilder.put(inliner);
     }
@@ -742,7 +753,12 @@
         postMethodProcessorBuilder.build(appView, executorService, timing);
     if (postMethodProcessor != null) {
       assert !options.debug;
-      postMethodProcessor.forEachWaveWithExtension(feedback, executorService);
+      postMethodProcessor.forEachMethod(
+          (method, methodProcessingContext) ->
+              processDesugaredMethod(
+                  method, feedback, postMethodProcessor, methodProcessingContext),
+          feedback,
+          executorService);
       feedback.updateVisibleOptimizationInfo();
       assert graphLensForIR == appView.graphLens();
     }
@@ -832,9 +848,8 @@
     }
 
     if (Log.ENABLED) {
-      if (appView.callSiteOptimizationInfoPropagator() != null) {
-        appView.callSiteOptimizationInfoPropagator().logResults();
-      }
+      appView.withCallSiteOptimizationInfoPropagator(
+          CallSiteOptimizationInfoPropagator::logResults);
       constantCanonicalizer.logResults();
       if (idempotentFunctionCallCanonicalizer != null) {
         idempotentFunctionCallCanonicalizer.logResults();
@@ -981,7 +996,9 @@
     Timing timing = Timing.empty();
     deadCodeRemover.run(code, timing);
     code.traceBlocks();
-    RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing);
+    RegisterAllocator registerAllocator =
+        performRegisterAllocation(
+            code, method, DefaultMethodConversionOptions.getInstance(), timing);
     method.setCode(code, registerAllocator, appView);
     if (Log.ENABLED) {
       Log.debug(getClass(), "Resulting dex code for %s:\n%s",
@@ -1168,6 +1185,8 @@
     ProgramMethod context = code.context();
     DexEncodedMethod method = context.getDefinition();
     DexProgramClass holder = context.getHolder();
+    MutableMethodConversionOptions conversionOptions =
+        new MutableMethodConversionOptions(methodProcessor);
     assert holder != null;
 
     Timing timing = Timing.create(method.qualifiedName(), options);
@@ -1237,7 +1256,13 @@
       assert appView.enableWholeProgramOptimizations();
       timing.begin("Collect optimization info");
       collectOptimizationInfo(
-          context, code, ClassInitializerDefaultsResult.empty(), feedback, methodProcessor, timing);
+          context,
+          code,
+          ClassInitializerDefaultsResult.empty(),
+          feedback,
+          methodProcessor,
+          conversionOptions,
+          timing);
       timing.end();
       return timing;
     }
@@ -1566,7 +1591,13 @@
     if (appView.enableWholeProgramOptimizations()) {
       timing.begin("Collect optimization info");
       collectOptimizationInfo(
-          context, code, classInitializerDefaultsResult, feedback, methodProcessor, timing);
+          context,
+          code,
+          classInitializerDefaultsResult,
+          feedback,
+          methodProcessor,
+          conversionOptions,
+          timing);
       timing.end();
     }
 
@@ -1593,7 +1624,7 @@
 
     printMethod(code, "Optimized IR (SSA)", previous);
     timing.begin("Finalize IR");
-    finalizeIR(code, feedback, timing);
+    finalizeIR(code, feedback, conversionOptions, timing);
     timing.end();
     return timing;
   }
@@ -1606,9 +1637,11 @@
       ClassInitializerDefaultsResult classInitializerDefaultsResult,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
+      MutableMethodConversionOptions conversionOptions,
       Timing timing) {
+
     if (enumUnboxer != null && methodProcessor.isPrimaryMethodProcessor()) {
-      enumUnboxer.analyzeEnums(code);
+      enumUnboxer.analyzeEnums(code, conversionOptions);
     }
 
     if (libraryMethodOverrideAnalysis != null) {
@@ -1671,16 +1704,20 @@
       stringSwitchRemover.run(code);
     }
     deadCodeRemover.run(code, timing);
-    finalizeIR(code, feedback, timing);
+    finalizeIR(code, feedback, DefaultMethodConversionOptions.getInstance(), timing);
   }
 
-  public void finalizeIR(IRCode code, OptimizationFeedback feedback, Timing timing) {
+  public void finalizeIR(
+      IRCode code,
+      OptimizationFeedback feedback,
+      MethodConversionOptions conversionOptions,
+      Timing timing) {
     code.traceBlocks();
     if (options.isGeneratingClassFiles()) {
-      finalizeToCf(code, feedback);
+      finalizeToCf(code, feedback, conversionOptions);
     } else {
       assert options.isGeneratingDex();
-      finalizeToDex(code, feedback, timing);
+      finalizeToDex(code, feedback, conversionOptions, timing);
     }
   }
 
@@ -1691,23 +1728,29 @@
     feedback.markProcessed(method, ConstraintWithTarget.ALWAYS);
   }
 
-  private void finalizeToCf(IRCode code, OptimizationFeedback feedback) {
+  private void finalizeToCf(
+      IRCode code, OptimizationFeedback feedback, MethodConversionOptions conversionOptions) {
     DexEncodedMethod method = code.method();
     assert !method.getCode().isDexCode();
     CfBuilder builder = new CfBuilder(appView, method, code);
-    CfCode result = builder.build(deadCodeRemover);
+    CfCode result = builder.build(deadCodeRemover, conversionOptions);
     method.setCode(result, appView);
     markProcessed(code, feedback);
   }
 
-  private void finalizeToDex(IRCode code, OptimizationFeedback feedback, Timing timing) {
+  private void finalizeToDex(
+      IRCode code,
+      OptimizationFeedback feedback,
+      MethodConversionOptions conversionOptions,
+      Timing timing) {
     DexEncodedMethod method = code.method();
     // Workaround massive dex2oat memory use for self-recursive methods.
     CodeRewriter.disableDex2OatInliningForSelfRecursiveMethods(appView, code);
     // Workaround MAX_INT switch issue.
     codeRewriter.rewriteSwitchForMaxInt(code);
     // Perform register allocation.
-    RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing);
+    RegisterAllocator registerAllocator =
+        performRegisterAllocation(code, method, conversionOptions, timing);
     timing.begin("Build DEX code");
     method.setCode(code, registerAllocator, appView);
     timing.end();
@@ -1759,7 +1802,10 @@
   }
 
   private RegisterAllocator performRegisterAllocation(
-      IRCode code, DexEncodedMethod method, Timing timing) {
+      IRCode code,
+      DexEncodedMethod method,
+      MethodConversionOptions conversionOptions,
+      Timing timing) {
     // Always perform dead code elimination before register allocation. The register allocator
     // does not allow dead code (to make sure that we do not waste registers for unneeded values).
     assert deadCodeRemover.verifyNoDeadCode(code);
@@ -1773,12 +1819,14 @@
       codeRewriter.workaroundExceptionTargetingLoopHeaderBug(code);
     }
     printMethod(code, "After register allocation (non-SSA)", null);
-    timing.begin("Peephole optimize");
-    for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) {
-      CodeRewriter.collapseTrivialGotos(code);
-      PeepholeOptimizer.optimize(code, registerAllocator);
+    if (conversionOptions.isPeepholeOptimizationsEnabled()) {
+      timing.begin("Peephole optimize");
+      for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) {
+        CodeRewriter.collapseTrivialGotos(code);
+        PeepholeOptimizer.optimize(code, registerAllocator);
+      }
+      timing.end();
     }
-    timing.end();
     timing.begin("Clean up");
     CodeRewriter.removeUnneededMovesOnExitingPaths(code, registerAllocator);
     CodeRewriter.collapseTrivialGotos(code);
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 b02d5e7..f44ec67 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
@@ -86,6 +86,7 @@
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.SafeCheckCast;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
@@ -415,7 +416,7 @@
                   Value castOutValue = code.createValue(castType);
                   newOutValue.replaceUsers(castOutValue);
                   CheckCast checkCast =
-                      CheckCast.builder()
+                      SafeCheckCast.builder()
                           .setCastType(lookup.getCastType())
                           .setObject(newOutValue)
                           .setOutValue(castOutValue)
@@ -479,7 +480,7 @@
                   Value castOutValue = code.createValue(castType);
                   newOutValue.replaceUsers(castOutValue);
                   CheckCast checkCast =
-                      CheckCast.builder()
+                      SafeCheckCast.builder()
                           .setCastType(lookup.getCastType())
                           .setObject(newOutValue)
                           .setOutValue(castOutValue)
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java
new file mode 100644
index 0000000..1dd9510
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java
@@ -0,0 +1,48 @@
+// 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.conversion;
+
+public abstract class MethodConversionOptions {
+
+  public abstract boolean isPeepholeOptimizationsEnabled();
+
+  public static class MutableMethodConversionOptions extends MethodConversionOptions {
+
+    private final MethodProcessor methodProcessor;
+    private boolean enablePeepholeOptimizations = true;
+
+    public MutableMethodConversionOptions(MethodProcessor methodProcessor) {
+      this.methodProcessor = methodProcessor;
+    }
+
+    public void disablePeepholeOptimizations() {
+      assert methodProcessor.isPrimaryMethodProcessor();
+      enablePeepholeOptimizations = false;
+    }
+
+    @Override
+    public boolean isPeepholeOptimizationsEnabled() {
+      assert enablePeepholeOptimizations || methodProcessor.isPrimaryMethodProcessor();
+      return enablePeepholeOptimizations;
+    }
+  }
+
+  public static class DefaultMethodConversionOptions extends MethodConversionOptions {
+
+    private static final DefaultMethodConversionOptions INSTANCE =
+        new DefaultMethodConversionOptions();
+
+    private DefaultMethodConversionOptions() {}
+
+    public static DefaultMethodConversionOptions getInstance() {
+      return INSTANCE;
+    }
+
+    @Override
+    public boolean isPeepholeOptimizationsEnabled() {
+      return true;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index 21608cf..9d9b9f1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -13,9 +14,6 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -25,33 +23,22 @@
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import java.util.ArrayDeque;
-import java.util.Collection;
 import java.util.Deque;
-import java.util.IdentityHashMap;
-import java.util.LinkedHashSet;
-import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.BiConsumer;
 
 public class PostMethodProcessor extends MethodProcessorWithWave {
 
   private final ProcessorContext processorContext;
-  private final AppView<AppInfoWithLiveness> appView;
-  private final Collection<CodeOptimization> defaultCodeOptimizations;
-  private final Map<DexMethod, Collection<CodeOptimization>> methodsMap;
   private final Deque<SortedProgramMethodSet> waves;
   private final ProgramMethodSet processed = ProgramMethodSet.create();
 
   private PostMethodProcessor(
       AppView<AppInfoWithLiveness> appView,
-      Collection<CodeOptimization> defaultCodeOptimizations,
-      Map<DexMethod, Collection<CodeOptimization>> methodsMap,
       CallGraph callGraph) {
     this.processorContext = appView.createProcessorContext();
-    this.appView = appView;
-    this.defaultCodeOptimizations = defaultCodeOptimizations;
-    this.methodsMap = methodsMap;
     this.waves = createWaves(callGraph);
   }
 
@@ -63,58 +50,28 @@
 
   public static class Builder {
 
-    private final Collection<CodeOptimization> defaultCodeOptimizations;
-    private final LongLivedProgramMethodSetBuilder<?> methodsToReprocess =
+    private final LongLivedProgramMethodSetBuilder<?> methodsToReprocessBuilder =
         LongLivedProgramMethodSetBuilder.createForIdentitySet();
-    private final Map<DexMethod, Collection<CodeOptimization>> optimizationsMap =
-        new IdentityHashMap<>();
 
-    Builder(Collection<CodeOptimization> defaultCodeOptimizations) {
-      this.defaultCodeOptimizations = defaultCodeOptimizations;
-    }
+    Builder() {}
 
-    private void put(
-        ProgramMethodSet methodsToRevisit, Collection<CodeOptimization> codeOptimizations) {
-      if (codeOptimizations.isEmpty()) {
-        // Nothing to conduct.
-        return;
-      }
-      for (ProgramMethod method : methodsToRevisit) {
-        methodsToReprocess.add(method);
-        optimizationsMap
-            .computeIfAbsent(
-                method.getReference(),
-                // Optimization order might matter, hence a collection that preserves orderings.
-                k -> new LinkedHashSet<>())
-            .addAll(codeOptimizations);
-      }
+    public void add(ProgramMethod method) {
+      methodsToReprocessBuilder.add(method);
     }
 
     public void put(ProgramMethodSet methodsToRevisit) {
-      put(methodsToRevisit, defaultCodeOptimizations);
+      methodsToRevisit.forEach(this::add);
     }
 
     public void put(PostOptimization postOptimization) {
-      Collection<CodeOptimization> codeOptimizations =
-          postOptimization.codeOptimizationsForPostProcessing();
-      if (codeOptimizations == null) {
-        codeOptimizations = defaultCodeOptimizations;
-      }
-      put(postOptimization.methodsToRevisit(), codeOptimizations);
+      put(postOptimization.methodsToRevisit());
     }
 
     // Some optimizations may change methods, creating new instances of the encoded methods with a
     // new signature. The compiler needs to update the set of methods that must be reprocessed
     // according to the graph lens.
     public void rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens applied) {
-      methodsToReprocess.rewrittenWithLens(appView, applied);
-      Map<DexMethod, Collection<CodeOptimization>> newOptimizationsMap = new IdentityHashMap<>();
-      optimizationsMap.forEach(
-          (method, optimizations) ->
-              newOptimizationsMap.put(
-                  appView.graphLens().getRenamedMethodSignature(method, applied), optimizations));
-      optimizationsMap.clear();
-      optimizationsMap.putAll(newOptimizationsMap);
+      methodsToReprocessBuilder.rewrittenWithLens(appView, applied);
     }
 
     PostMethodProcessor build(
@@ -133,16 +90,15 @@
             });
         put(set);
       }
-      if (methodsToReprocess.isEmpty()) {
+      if (methodsToReprocessBuilder.isEmpty()) {
         // Nothing to revisit.
         return null;
       }
+      ProgramMethodSet methodsToReprocess =
+          methodsToReprocessBuilder.build(appView, appView.graphLens());
       CallGraph callGraph =
-          new PartialCallGraphBuilder(
-                  appView, methodsToReprocess.build(appView, appView.graphLens()))
-              .build(executorService, timing);
-      return new PostMethodProcessor(
-          appView, defaultCodeOptimizations, optimizationsMap, callGraph);
+          new PartialCallGraphBuilder(appView, methodsToReprocess).build(executorService, timing);
+      return new PostMethodProcessor(appView, callGraph);
     }
   }
 
@@ -159,18 +115,10 @@
     return waves;
   }
 
-  @Override
-  protected void prepareForWaveExtensionProcessing() {
-    waveExtension.forEach(
-        method -> {
-          assert !methodsMap.containsKey(method.getReference());
-          methodsMap.put(method.getReference(), defaultCodeOptimizations);
-        });
-    super.prepareForWaveExtensionProcessing();
-  }
-
-  void forEachWaveWithExtension(
-      OptimizationFeedbackDelayed feedback, ExecutorService executorService)
+  void forEachMethod(
+      BiConsumer<ProgramMethod, MethodProcessingContext> consumer,
+      OptimizationFeedbackDelayed feedback,
+      ExecutorService executorService)
       throws ExecutionException {
     while (!waves.isEmpty()) {
       wave = waves.removeFirst();
@@ -180,12 +128,8 @@
         assert feedback.noUpdatesLeft();
         ThreadUtils.processItems(
             wave,
-            method -> {
-              Collection<CodeOptimization> codeOptimizations =
-                  methodsMap.get(method.getReference());
-              assert codeOptimizations != null && !codeOptimizations.isEmpty();
-              forEachMethod(method, codeOptimizations, feedback);
-            },
+            method ->
+                consumer.accept(method, processorContext.createMethodProcessingContext(method)),
             executorService);
         feedback.updateVisibleOptimizationInfo();
         processed.addAll(wave);
@@ -193,26 +137,4 @@
       } while (!wave.isEmpty());
     }
   }
-
-  private void forEachMethod(
-      ProgramMethod method,
-      Collection<CodeOptimization> codeOptimizations,
-      OptimizationFeedback feedback) {
-    // TODO(b/140766440): Make IRConverter#process receive a list of CodeOptimization to conduct.
-    //   Then, we can share IRCode creation there.
-    if (appView.options().skipIR) {
-      feedback.markProcessed(method.getDefinition(), ConstraintWithTarget.NEVER);
-      return;
-    }
-    IRCode code = method.buildIR(appView);
-    if (code == null) {
-      feedback.markProcessed(method.getDefinition(), ConstraintWithTarget.NEVER);
-      return;
-    }
-    // TODO(b/140768815): Reprocessing may trigger more methods to revisit. Update waves on-the-fly.
-    for (CodeOptimization codeOptimization : codeOptimizations) {
-      codeOptimization.optimize(
-          code, feedback, this, processorContext.createMethodProcessingContext(method));
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java b/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java
index 7f06207..30c873d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import java.util.Collection;
 
 /**
  * An abstraction of optimizations that require post processing of methods.
@@ -13,16 +12,4 @@
 
   /** @return a set of methods that need post processing. */
   ProgramMethodSet methodsToRevisit();
-
-  // TODO(b/127694949): different CodeOptimization for primary processor v.s. post processor?
-  //  In that way, instead of internal state changes, such as COLLECT v.s. APPLY or REVISIT,
-  //  optimizers that need post processing can return what to do at each processor.
-  // Collection<CodeOptimization> codeOptimizationsForPrimaryProcessing();
-
-  /**
-   * @return specific collection of {@link CodeOptimization}s to conduct during post processing.
-   *   Otherwise, i.e., if the default one---IRConverter's full processing---is okay,
-   *   returns {@code null}.
-   */
-  Collection<CodeOptimization> codeOptimizationsForPostProcessing();
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 3eada1f..021ffa2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -977,6 +977,17 @@
       DexProto proto;
       DexMethod method;
 
+      // List
+      type = factory.listType;
+
+      // List List.copyOf(Collection)
+      name = factory.createString("copyOf");
+      proto = factory.createProto(factory.listType, factory.collectionType);
+      method = factory.createMethod(type, proto, name);
+      addProvider(
+          new MethodGenerator(
+              method, BackportedMethods::CollectionsMethods_copyOfList, "copyOfList"));
+
       // Set
       type = factory.setType;
 
@@ -1133,16 +1144,7 @@
     }
 
     private void initializeJava10MethodProviders(DexItemFactory factory) {
-      // List
-      DexType type = factory.listType;
-
-      // List List.copyOf(Collection)
-      DexString name = factory.createString("copyOf");
-      DexProto proto = factory.createProto(factory.listType, factory.collectionType);
-      DexMethod method = factory.createMethod(type, proto, name);
-      addProvider(
-          new MethodGenerator(
-              method, BackportedMethods::CollectionsMethods_copyOfList, "copyOfList"));
+      // Nothing right now.
     }
 
     private void initializeJava11MethodProviders(DexItemFactory factory) {
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 cf86667..30fc05c 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
@@ -245,6 +245,8 @@
                           companionMethod.name)));
         }
       }
+    } else {
+      assert extraDispatchCases.size() <= 1;
     }
     return extraDispatchCases;
   }
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 79afbf8..ae334dc 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
@@ -40,6 +40,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.InvalidCode;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
@@ -858,9 +859,9 @@
     assert resolutionResult != null;
     assert resolutionResult.getResolvedMethod().isStatic();
     assert invokeNeedsRewriting(invokedMethod, STATIC);
-
-    return rewriteInvoke.apply(
-        staticAsMethodOfCompanionClass(resolutionResult.getResolutionPair()));
+    DexClassAndMethod companionMethod =
+        ensureStaticAsMethodOfCompanionClassStub(resolutionResult.getResolutionPair());
+    return rewriteInvoke.apply(companionMethod.getReference());
   }
 
   private Collection<CfInstruction> rewriteInvokeSuper(
@@ -1187,12 +1188,21 @@
     return factory.createType(interfaceTypeDescriptor);
   }
 
+  DexClassAndMethod ensureStaticAsMethodOfCompanionClassStub(DexClassAndMethod method) {
+    if (method.isProgramMethod()) {
+      return ensureStaticAsMethodOfProgramCompanionClassStub(method.asProgramMethod());
+    } else {
+      ClasspathOrLibraryClass context = method.getHolder().asClasspathOrLibraryClass();
+      DexMethod companionMethodReference = staticAsMethodOfCompanionClass(method);
+      return ensureMethodOfClasspathCompanionClassStub(companionMethodReference, context, appView);
+    }
+  }
+
   // Represent a static interface method as a method of companion class.
   final DexMethod staticAsMethodOfCompanionClass(DexClassAndMethod method) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     DexType companionClassType = getCompanionClassType(method.getHolderType(), dexItemFactory);
     DexMethod rewritten = method.getReference().withHolder(companionClassType, dexItemFactory);
-    recordCompanionClassReference(appView, method, rewritten);
     return rewritten;
   }
 
@@ -1244,19 +1254,24 @@
     return privateAsMethodOfCompanionClass(method.getReference(), factory);
   }
 
-  private static void recordCompanionClassReference(
+  private static DexClassAndMethod recordCompanionClassReference(
       AppView<?> appView, DexClassAndMethod method, DexMethod rewritten) {
     ClasspathOrLibraryClass context = method.getHolder().asClasspathOrLibraryClass();
     // If the interface class is a program class, we shouldn't need to synthesize the companion
     // class on the classpath.
     if (context == null) {
-      return;
+      return null;
     }
-    appView
+    return ensureMethodOfClasspathCompanionClassStub(rewritten, context, appView);
+  }
+
+  private static DexClassAndMethod ensureMethodOfClasspathCompanionClassStub(
+      DexMethod companionMethodReference, ClasspathOrLibraryClass context, AppView<?> appView) {
+    return appView
         .getSyntheticItems()
         .ensureFixedClasspathClassMethod(
-            rewritten.getName(),
-            rewritten.getProto(),
+            companionMethodReference.getName(),
+            companionMethodReference.getProto(),
             SyntheticKind.COMPANION_CLASS,
             context,
             appView,
@@ -1267,6 +1282,32 @@
                     .setCode(DexEncodedMethod::buildEmptyThrowingCfCode));
   }
 
+  ProgramMethod ensureStaticAsMethodOfProgramCompanionClassStub(ProgramMethod method) {
+    DexMethod companionMethodReference = staticAsMethodOfCompanionClass(method);
+    DexEncodedMethod definition = method.getDefinition();
+    return InterfaceProcessor.ensureCompanionMethod(
+        method.getHolder(),
+        companionMethodReference.getName(),
+        companionMethodReference.getProto(),
+        appView,
+        methodBuilder -> {
+          MethodAccessFlags newFlags = definition.getAccessFlags().copy();
+          newFlags.promoteToPublic();
+          methodBuilder
+              .setAccessFlags(newFlags)
+              .setGenericSignature(definition.getGenericSignature())
+              .setAnnotations(definition.annotations())
+              .setParameterAnnotationsList(definition.getParameterAnnotations())
+              // TODO(b/183998768): Once R8 desugars in the enqueuer this should set an invalid
+              //  code to ensure it is never used before desugared and installed.
+              .setCode(
+                  m ->
+                      appView.enableWholeProgramOptimizations()
+                          ? definition.getCode()
+                          : InvalidCode.getInstance());
+        });
+  }
+
   /**
    * Move static and default interface methods to companion classes, add missing methods to forward
    * to moved default methods implementation.
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 2174002..ab61be0 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
@@ -36,6 +36,7 @@
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.InvalidCode;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodCollection;
 import com.android.tools.r8.graph.NestedGraphLens;
@@ -74,12 +75,10 @@
   private final InterfaceMethodRewriter rewriter;
   private final Map<DexProgramClass, PostProcessingInterfaceInfo> postProcessingInterfaceInfos =
       new ConcurrentHashMap<>();
-  private final ClassTypeSignature objectTypeSignature;
 
   InterfaceProcessor(AppView<?> appView, InterfaceMethodRewriter rewriter) {
     this.appView = appView;
     this.rewriter = rewriter;
-    this.objectTypeSignature = new ClassTypeSignature(appView.dexItemFactory().objectType);
   }
 
   @Override
@@ -111,30 +110,29 @@
     processDirectInterfaceMethods(iface);
   }
 
-  private ProgramMethod ensureCompanionMethod(
+  static ProgramMethod ensureCompanionMethod(
       DexProgramClass iface,
       DexString methodName,
       DexProto methodProto,
+      AppView<?> appView,
       Consumer<SyntheticMethodBuilder> fn) {
-    ProgramMethod method =
-        appView
-            .getSyntheticItems()
-            .ensureFixedClassMethod(
-                methodName,
-                methodProto,
-                SyntheticKind.COMPANION_CLASS,
-                iface,
-                appView,
-                builder ->
-                    builder
-                        .setSourceFile(iface.sourceFile)
-                        .setGenericSignature(
-                            iface
-                                .getClassSignature()
-                                .toObjectBoundWithSameFormals(objectTypeSignature)),
-                fn);
-    assert method.getHolderType() == rewriter.getCompanionClassType(iface.type);
-    return method;
+    return appView
+        .getSyntheticItems()
+        .ensureFixedClassMethod(
+            methodName,
+            methodProto,
+            SyntheticKind.COMPANION_CLASS,
+            iface,
+            appView,
+            builder ->
+                builder
+                    .setSourceFile(iface.sourceFile)
+                    .setGenericSignature(
+                        iface
+                            .getClassSignature()
+                            .toObjectBoundWithSameFormals(
+                                new ClassTypeSignature(appView.dexItemFactory().objectType))),
+            fn);
   }
 
   private void ensureCompanionClassInitializesInterface(
@@ -148,6 +146,7 @@
             iface,
             appView.dexItemFactory().classConstructorMethodName,
             appView.dexItemFactory().createProto(appView.dexItemFactory().voidType),
+            appView,
             methodBuilder -> createCompanionClassInitializer(iface, clinitField, methodBuilder));
     synthesizedMethods.add(clinit);
   }
@@ -252,12 +251,17 @@
             iface,
             companionMethod.getName(),
             companionMethod.getProto(),
+            appView,
             methodBuilder ->
                 methodBuilder
                     .setAccessFlags(newFlags)
                     .setGenericSignature(MethodTypeSignature.noSignature())
-                    .setAnnotations(virtual.annotations())
-                    .setParameterAnnotationsList(virtual.getParameterAnnotations())
+                    .setAnnotations(
+                        virtual
+                            .annotations()
+                            .methodParametersWithFakeThisArguments(appView.dexItemFactory()))
+                    .setParameterAnnotationsList(
+                        virtual.getParameterAnnotations().withFakeThisParameter())
                     .setCode(ignored -> virtual.getCode())
                     .setOnBuildConsumer(
                         implMethod -> {
@@ -298,25 +302,17 @@
                 + " is expected to "
                 + "either be public or private in "
                 + iface.origin;
-        DexMethod companionMethod = rewriter.staticAsMethodOfCompanionClass(method);
-
-        ensureCompanionMethod(
-            iface,
-            companionMethod.getName(),
-            companionMethod.getProto(),
-            methodBuilder ->
-                methodBuilder
-                    .setAccessFlags(newFlags)
-                    .setGenericSignature(definition.getGenericSignature())
-                    .setAnnotations(definition.annotations())
-                    .setParameterAnnotationsList(definition.getParameterAnnotations())
-                    .setCode(ignored -> definition.getCode())
-                    .setOnBuildConsumer(
-                        implMethod -> {
-                          implMethod.copyMetadata(definition);
-                        }));
-
-        getPostProcessingInterfaceInfo(iface).moveMethod(oldMethod, companionMethod);
+        ProgramMethod companion = rewriter.ensureStaticAsMethodOfProgramCompanionClassStub(method);
+        // TODO(b/183998768): R8 should also install an "invalid code" object until the actual code
+        //  moves.
+        assert appView.enableWholeProgramOptimizations()
+            || InvalidCode.isInvalidCode(companion.getDefinition().getCode());
+        if (definition.hasClassFileVersion()) {
+          companion.getDefinition().downgradeClassFileVersion(definition.getClassFileVersion());
+        }
+        companion.getDefinition().setCode(definition.getCode(), appView);
+        getPostProcessingInterfaceInfo(iface).moveMethod(oldMethod, companion.getReference());
+        definition.setCode(InvalidCode.getInstance(), appView);
         continue;
       }
 
@@ -343,6 +339,7 @@
           iface,
           companionMethod.getName(),
           companionMethod.getProto(),
+          appView,
           methodBuilder ->
               methodBuilder
                   .setAccessFlags(newFlags)
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index 2fdd282..5a30d1d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -27,7 +27,6 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.CodeOptimization;
 import com.android.tools.r8.ir.conversion.PostOptimization;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
@@ -38,13 +37,16 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
 import com.android.tools.r8.utils.LazyBox;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
-import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
 
 public class CallSiteOptimizationInfoPropagator implements PostOptimization {
 
@@ -95,10 +97,6 @@
       return;
     }
 
-    if (appView.appInfo().isMethodTargetedByInvokeDynamic(code.context().getReference())) {
-      abandonCallSitePropagationForMethodAndOverrides(code.context());
-    }
-
     ProgramMethod context = code.context();
     for (Instruction instruction : code.instructions()) {
       if (instruction.isInvokeMethod()) {
@@ -291,6 +289,51 @@
     }
   }
 
+  public void abandonCallSitePropagationForLambdaImplementationMethods(
+      ExecutorService executorService, Timing timing) throws ExecutionException {
+    if (appView.options().isGeneratingClassFiles()) {
+      timing.begin("Call site optimization: abandon lambda implementation methods");
+      ForEachable<ProgramMethod> lambdaImplementationMethods =
+          consumer ->
+              appView
+                  .appInfo()
+                  .forEachMethod(
+                      method -> {
+                        if (appView
+                            .appInfo()
+                            .isMethodTargetedByInvokeDynamic(method.getReference())) {
+                          consumer.accept(method);
+                        }
+                      });
+      ThreadUtils.processItems(
+          lambdaImplementationMethods,
+          this::abandonCallSitePropagationForMethodAndOverrides,
+          executorService);
+      timing.end();
+    }
+  }
+
+  public void abandonCallSitePropagationForPinnedMethodsAndOverrides(
+      ExecutorService executorService, Timing timing) throws ExecutionException {
+    timing.begin("Call site optimization: abandon pinned methods");
+    ThreadUtils.processItems(
+        this::forEachPinnedNonPrivateVirtualMethod,
+        this::abandonCallSitePropagationForMethodAndOverrides,
+        executorService);
+    timing.end();
+  }
+
+  private void forEachPinnedNonPrivateVirtualMethod(Consumer<ProgramMethod> consumer) {
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      for (ProgramMethod virtualProgramMethod : clazz.virtualProgramMethods()) {
+        if (virtualProgramMethod.getDefinition().isNonPrivateVirtualMethod()
+            && appView.getKeepInfo().isPinned(virtualProgramMethod.getReference(), appView)) {
+          consumer.accept(virtualProgramMethod);
+        }
+      }
+    }
+  }
+
   private void abandonCallSitePropagationForMethodAndOverrides(ProgramMethod method) {
     Set<ProgramMethod> abandonSet = Sets.newIdentityHashSet();
     if (method.getDefinition().isNonPrivateVirtualMethod()) {
@@ -467,12 +510,6 @@
     return targetsToRevisit;
   }
 
-  @Override
-  public Collection<CodeOptimization> codeOptimizationsForPostProcessing() {
-    // Run IRConverter#optimize.
-    return null;
-  }
-
   private synchronized boolean verifyAllProgramDispatchTargetsHaveBeenAbandoned(
       InvokeMethod invoke, ProgramMethod context) {
     ProgramMethodSet targets = invoke.lookupProgramDispatchTargets(appView, context);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 7dae8cd..55780b6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.ir.code.InvokeInterface;
 import com.android.tools.r8.ir.code.InvokeSuper;
 import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.SafeCheckCast;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
@@ -63,7 +64,7 @@
     Map<InvokeInterface, InvokeVirtual> devirtualizedCall = new IdentityHashMap<>();
     DominatorTree dominatorTree = new DominatorTree(code);
     Map<Value, Map<DexType, Value>> castedReceiverCache = new IdentityHashMap<>();
-    Set<CheckCast> newCheckCastInstructions = Sets.newIdentityHashSet();
+    Set<SafeCheckCast> newCheckCastInstructions = Sets.newIdentityHashSet();
 
     ListIterator<BasicBlock> blocks = code.listIterator();
     while (blocks.hasNext()) {
@@ -261,7 +262,8 @@
                 castedReceiverCache.putIfAbsent(receiver, new IdentityHashMap<>());
                 castedReceiverCache.get(receiver).put(holderClass.getType(), newReceiver);
               }
-              CheckCast checkCast = new CheckCast(newReceiver, receiver, holderClass.getType());
+              SafeCheckCast checkCast =
+                  new SafeCheckCast(newReceiver, receiver, holderClass.getType());
               checkCast.setPosition(invoke.getPosition());
               newCheckCastInstructions.add(checkCast);
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index a6f246f..9e82361 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -46,7 +46,6 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.CodeOptimization;
 import com.android.tools.r8.ir.conversion.LensCodeRewriter;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.PostOptimization;
@@ -71,7 +70,6 @@
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Deque;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -244,12 +242,6 @@
     return doubleInlineCallers;
   }
 
-  @Override
-  public Collection<CodeOptimization> codeOptimizationsForPostProcessing() {
-    // Run IRConverter#optimize.
-    return null;  // Technically same as return converter.getOptimizationForPostIRProcessing();
-  }
-
   /**
    * Encodes the constraints for inlining a method's instructions into a different context.
    * <p>
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
index 07471e7..b47ec05 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.enums;
 
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
 import com.google.common.collect.ImmutableMap;
@@ -30,6 +31,12 @@
     return map.isEmpty();
   }
 
+  public EnumData get(DexProgramClass enumClass) {
+    EnumData enumData = map.get(enumClass.getType());
+    assert enumData != null;
+    return enumData;
+  }
+
   public Set<DexType> getUnboxedEnums() {
     return map.keySet();
   }
@@ -55,6 +62,16 @@
     return map.get(enumType).getValuesSize();
   }
 
+  public int getMaxValuesSize() {
+    int maxValuesSize = 0;
+    for (EnumData data : map.values()) {
+      if (data.hasValues()) {
+        maxValuesSize = Math.max(maxValuesSize, data.getValuesSize());
+      }
+    }
+    return maxValuesSize;
+  }
+
   public boolean matchesValuesField(DexField staticField) {
     assert map.containsKey(staticField.holder);
     return map.get(staticField.holder).matchesValuesField(staticField);
@@ -101,8 +118,12 @@
       return valuesFields.contains(field);
     }
 
+    public boolean hasValues() {
+      return valuesSize != INVALID_VALUES_SIZE;
+    }
+
     public int getValuesSize() {
-      assert valuesSize != INVALID_VALUES_SIZE;
+      assert hasValues();
       return valuesSize;
     }
   }
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 0b47a53..edd774e 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
@@ -38,7 +38,6 @@
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ProgramPackageCollection;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
@@ -69,6 +68,7 @@
 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.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
@@ -127,10 +127,10 @@
   // Map the enum candidates with their dependencies, i.e., the methods to reprocess for the given
   // enum if the optimization eventually decides to unbox it.
   private final EnumUnboxingCandidateInfoCollection enumUnboxingCandidatesInfo;
-  private final ProgramPackageCollection enumsToUnboxWithPackageRequirement =
-      ProgramPackageCollection.createEmpty();
   private final Map<DexType, EnumStaticFieldValues> staticFieldValuesMap =
       new ConcurrentHashMap<>();
+  private final ProgramMethodSet methodsDependingOnLibraryModelisation =
+      ProgramMethodSet.createConcurrent();
 
   private final DexEncodedField ordinalField;
 
@@ -181,6 +181,10 @@
     return false;
   }
 
+  private void markMethodDependsOnLibraryModelisation(ProgramMethod method) {
+    methodsDependingOnLibraryModelisation.add(method);
+  }
+
   private DexProgramClass getEnumUnboxingCandidateOrNull(TypeElement lattice) {
     if (lattice.isClassType()) {
       DexType classType = lattice.asClassType().getClassType();
@@ -199,7 +203,7 @@
     return enumUnboxingCandidatesInfo.getCandidateClassOrNull(type);
   }
 
-  public void analyzeEnums(IRCode code) {
+  public void analyzeEnums(IRCode code, MutableMethodConversionOptions conversionOptions) {
     Set<DexType> eligibleEnums = Sets.newIdentityHashSet();
     for (BasicBlock block : code.blocks) {
       for (Instruction instruction : block.getInstructions()) {
@@ -262,6 +266,9 @@
         enumUnboxingCandidatesInfo.addMethodDependency(eligibleEnum, code.context());
       }
     }
+    if (methodsDependingOnLibraryModelisation.contains(code.context())) {
+      conversionOptions.disablePeepholeOptimizations();
+    }
   }
 
   private void analyzeFieldInstruction(FieldInstruction fieldInstruction, IRCode code) {
@@ -365,10 +372,12 @@
             // The name data is required for the correct mapping from the enum name to the ordinal
             // in the valueOf utility method.
             addRequiredNameData(enumClass);
+            markMethodDependsOnLibraryModelisation(context);
             continue;
           }
           if (singleTarget.getReference()
               == factory.javaLangReflectArrayMembers.newInstanceMethodWithDimensions) {
+            markMethodDependsOnLibraryModelisation(context);
             continue;
           }
         }
@@ -469,18 +478,22 @@
     DirectMappedDexApplication.Builder appBuilder = appView.appInfo().app().asDirect().builder();
     FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder =
         FieldAccessInfoCollectionModifier.builder();
-    UnboxedEnumMemberRelocator relocator =
-        UnboxedEnumMemberRelocator.builder(appView)
+
+    EnumUnboxingUtilityClasses utilityClasses =
+        EnumUnboxingUtilityClasses.builder(appView)
             .synthesizeEnumUnboxingUtilityClasses(
                 enumClassesToUnbox,
-                enumsToUnboxWithPackageRequirement,
+                enumDataMap,
                 appBuilder,
                 fieldAccessInfoCollectionModifierBuilder)
             .build();
+    utilityClasses.forEach(
+        utilityClass -> utilityClass.getDefinition().forEachProgramMethod(postBuilder::add));
+
     fieldAccessInfoCollectionModifierBuilder.build().modify(appView);
-    enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumDataMap, relocator);
+    enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumDataMap, utilityClasses);
     EnumUnboxingLens enumUnboxingLens =
-        new EnumUnboxingTreeFixer(appView, enumsToUnbox, relocator, enumUnboxerRewriter)
+        new EnumUnboxingTreeFixer(appView, enumsToUnbox, utilityClasses, enumUnboxerRewriter)
             .fixupTypeReferences();
     enumUnboxerRewriter.setEnumUnboxingLens(enumUnboxingLens);
     appView.setUnboxedEnums(enumDataMap);
@@ -488,6 +501,10 @@
     appView.rewriteWithLensAndApplication(enumUnboxingLens, appBuilder.build());
     updateOptimizationInfos(executorService, feedback);
     postBuilder.put(dependencies);
+    // Methods depending on library modelisation need to be reprocessed so they are peephole
+    // optimized.
+    postBuilder.put(methodsDependingOnLibraryModelisation);
+    methodsDependingOnLibraryModelisation.clear();
     postBuilder.rewrittenWithLens(appView, previousLens);
   }
 
@@ -523,7 +540,6 @@
 
   public EnumDataMap finishAnalysis() {
     analyzeInitializers();
-    analyzeAccessibility();
     EnumDataMap enumDataMap = analyzeEnumInstances();
     if (debugLogEnabled) {
       // Remove all enums that have been reported as being unboxable.
@@ -725,39 +741,6 @@
     return OptionalInt.empty();
   }
 
-  private void analyzeAccessibility() {
-    // Unboxing an enum will require to move its methods to a different class, which may impact
-    // accessibility. For a quick analysis we simply reuse the inliner analysis.
-    enumUnboxingCandidatesInfo.forEachCandidate(
-        enumClass -> {
-          Constraint classConstraint = analyzeAccessibilityInClass(enumClass);
-          if (classConstraint == Constraint.NEVER) {
-            markEnumAsUnboxable(Reason.ACCESSIBILITY, enumClass);
-          } else if (classConstraint == Constraint.PACKAGE) {
-            enumsToUnboxWithPackageRequirement.addProgramClass(enumClass);
-          }
-        });
-  }
-
-  private Constraint analyzeAccessibilityInClass(DexProgramClass enumClass) {
-    Constraint classConstraint = Constraint.ALWAYS;
-    EnumAccessibilityUseRegistry useRegistry = null;
-    for (DexEncodedMethod method : enumClass.methods()) {
-      // Enum initializer are analyzed in analyzeInitializers instead.
-      if (!method.isInitializer()) {
-        if (useRegistry == null) {
-          useRegistry = new EnumAccessibilityUseRegistry(factory);
-        }
-        Constraint methodConstraint = constraintForEnumUnboxing(method, useRegistry);
-        classConstraint = classConstraint.meet(methodConstraint);
-        if (classConstraint == Constraint.NEVER) {
-          return classConstraint;
-        }
-      }
-    }
-    return classConstraint;
-  }
-
   public Constraint constraintForEnumUnboxing(
       DexEncodedMethod method, EnumAccessibilityUseRegistry useRegistry) {
     return useRegistry.computeConstraint(method.asProgramMethod(appView));
@@ -1210,6 +1193,25 @@
 
     assert targetHolder.isLibraryClass();
 
+    Reason reason =
+        analyzeLibraryInvoke(
+            invoke, code, context, enumClass, enumValue, singleTargetReference, targetHolder);
+
+    if (reason == Reason.ELIGIBLE) {
+      markMethodDependsOnLibraryModelisation(context);
+    }
+
+    return reason;
+  }
+
+  private Reason analyzeLibraryInvoke(
+      InvokeMethod invoke,
+      IRCode code,
+      ProgramMethod context,
+      DexProgramClass enumClass,
+      Value enumValue,
+      DexMethod singleTargetReference,
+      DexClass targetHolder) {
     if (targetHolder.getType() != factory.enumType) {
       // System.identityHashCode(Object) is supported for proto enums.
       // Object#getClass without outValue and Objects.requireNonNull are supported since R8
@@ -1221,6 +1223,11 @@
         addRequiredNameData(enumClass);
         return Reason.ELIGIBLE;
       }
+      if (singleTargetReference == factory.stringBuilderMethods.appendObject
+          || singleTargetReference == factory.stringBufferMethods.appendObject) {
+        addRequiredNameData(enumClass);
+        return Reason.ELIGIBLE;
+      }
       if (singleTargetReference == factory.objectMembers.getClass
           && (!invoke.hasOutValue() || !invoke.outValue().hasAnyUsers())) {
         // This is a hidden null check.
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 7ddfbaa..3bbd636 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,10 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.enums;
 
-import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
-
 import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -38,6 +35,7 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.StaticGet;
@@ -46,12 +44,14 @@
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
 import com.android.tools.r8.ir.synthetic.EnumUnboxingCfCodeProvider;
 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;
@@ -69,64 +69,67 @@
 
   private final AppView<AppInfoWithLiveness> appView;
   private final DexItemFactory factory;
+  private final InternalOptions options;
   private final EnumDataMap unboxedEnumsData;
-  private final UnboxedEnumMemberRelocator relocator;
   private 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 valuesUtilityMethod;
   private final DexMethod zeroCheckMethod;
   private final DexMethod zeroCheckMessageMethod;
 
   EnumUnboxingRewriter(
       AppView<AppInfoWithLiveness> appView,
       EnumDataMap unboxedEnumsInstanceFieldData,
-      UnboxedEnumMemberRelocator relocator) {
+      EnumUnboxingUtilityClasses utilityClasses) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
+    this.options = appView.options();
     this.unboxedEnumsData = unboxedEnumsInstanceFieldData;
-    this.relocator = relocator;
+    this.utilityClasses = utilityClasses;
 
     // Custom methods for java.lang.Enum methods ordinal, equals and compareTo.
-    DexType defaultEnumUnboxingUtility = relocator.getDefaultEnumUnboxingUtility();
+    DexType sharedEnumUnboxingUtilityType = utilityClasses.getSharedUtilityClass().getType();
     this.ordinalUtilityMethod =
         factory.createMethod(
-            defaultEnumUnboxingUtility,
+            sharedEnumUnboxingUtilityType,
             factory.createProto(factory.intType, factory.intType),
             ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "ordinal");
     this.equalsUtilityMethod =
         factory.createMethod(
-            defaultEnumUnboxingUtility,
+            sharedEnumUnboxingUtilityType,
             factory.createProto(factory.booleanType, factory.intType, factory.intType),
             ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "equals");
     this.compareToUtilityMethod =
         factory.createMethod(
-            defaultEnumUnboxingUtility,
+            sharedEnumUnboxingUtilityType,
             factory.createProto(factory.intType, factory.intType, factory.intType),
             ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "compareTo");
-    // Custom methods for generated field $VALUES initialization.
-    this.valuesUtilityMethod =
-        factory.createMethod(
-            defaultEnumUnboxingUtility,
-            factory.createProto(factory.intArrayType, factory.intType),
-            ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "values");
     // Custom methods for Object#getClass without outValue and Objects.requireNonNull.
     this.zeroCheckMethod =
         factory.createMethod(
-            defaultEnumUnboxingUtility,
+            sharedEnumUnboxingUtilityType,
             factory.createProto(factory.voidType, factory.intType),
             ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheck");
     this.zeroCheckMessageMethod =
         factory.createMethod(
-            defaultEnumUnboxingUtility,
+            sharedEnumUnboxingUtilityType,
             factory.createProto(factory.voidType, factory.intType, factory.stringType),
             ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheckMessage");
   }
 
+  private LocalEnumUnboxingUtilityClass getLocalUtilityClass(DexType enumType) {
+    return utilityClasses.getLocalUtilityClass(enumType);
+  }
+
+  private SharedEnumUnboxingUtilityClass getSharedUtilityClass() {
+    return utilityClasses.getSharedUtilityClass();
+  }
+
   public void setEnumUnboxingLens(EnumUnboxingLens enumUnboxingLens) {
     this.enumUnboxingLens = enumUnboxingLens;
   }
@@ -142,13 +145,21 @@
     Map<Instruction, DexType> convertedEnums = new IdentityHashMap<>();
     Set<Phi> affectedPhis = Sets.newIdentityHashSet();
     ListIterator<BasicBlock> blocks = code.listIterator();
+    Set<BasicBlock> seenBlocks = Sets.newIdentityHashSet();
+    Set<Instruction> instructionsToRemove = Sets.newIdentityHashSet();
     Value zeroConstValue = null;
     while (blocks.hasNext()) {
       BasicBlock block = blocks.next();
+      seenBlocks.add(block);
       zeroConstValue = fixNullsInBlockPhis(code, block, zeroConstValue);
       InstructionListIterator iterator = block.listIterator(code);
       while (iterator.hasNext()) {
         Instruction instruction = iterator.next();
+        if (instructionsToRemove.contains(instruction)) {
+          iterator.removeOrReplaceByDebugLocalRead();
+          continue;
+        }
+
         // Rewrites specific enum methods, such as ordinal, into their corresponding enum unboxed
         // counterpart. The rewriting (== or match) is based on the following:
         // - name, ordinal and compareTo are final and implemented only on java.lang.Enum,
@@ -159,8 +170,8 @@
         if (instruction.isInvokeMethodWithReceiver()) {
           InvokeMethodWithReceiver invokeMethod = instruction.asInvokeMethodWithReceiver();
           DexType enumType = getEnumTypeOrNull(invokeMethod.getReceiver(), convertedEnums);
+          DexMethod invokedMethod = invokeMethod.getInvokedMethod();
           if (enumType != null) {
-            DexMethod invokedMethod = invokeMethod.getInvokedMethod();
             if (invokedMethod == factory.enumMembers.ordinalMethod
                 || invokedMethod.match(factory.enumMembers.hashCode)) {
               replaceEnumInvoke(
@@ -190,6 +201,42 @@
               assert !invokeMethod.hasOutValue() || !invokeMethod.outValue().hasAnyUsers();
               replaceEnumInvoke(
                   iterator, invokeMethod, zeroCheckMethod, m -> synthesizeZeroCheckMethod());
+              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);
+            DexType enumArgType = getEnumTypeOrNull(enumArg, convertedEnums);
+            if (enumArgType != null) {
+              DexMethod stringValueOfMethod = computeStringValueOfUtilityMethod(enumArgType);
+              InvokeStatic toStringInvoke =
+                  InvokeStatic.builder()
+                      .setMethod(stringValueOfMethod)
+                      .setSingleArgument(enumArg)
+                      .setFreshOutValue(appView, code)
+                      .setPosition(invokeMethod)
+                      .build();
+              DexMethod newAppendMethod =
+                  invokedMethod == factory.stringBuilderMethods.appendObject
+                      ? factory.stringBuilderMethods.appendString
+                      : factory.stringBufferMethods.appendString;
+              List<Value> arguments =
+                  ImmutableList.of(invokeMethod.getReceiver(), toStringInvoke.outValue());
+              InvokeVirtual invokeAppendString =
+                  new InvokeVirtual(newAppendMethod, invokeMethod.clearOutValue(), arguments);
+              invokeAppendString.setPosition(invokeMethod.getPosition());
+              iterator.replaceCurrentInstruction(toStringInvoke);
+              if (block.hasCatchHandlers()) {
+                iterator
+                    .splitCopyCatchHandlers(code, blocks, appView.options())
+                    .listIterator(code)
+                    .add(invokeAppendString);
+              } else {
+                iterator.add(invokeAppendString);
+              }
+              continue;
             }
           }
         } else if (instruction.isInvokeStatic()) {
@@ -264,38 +311,46 @@
           StaticGet staticGet = instruction.asStaticGet();
           DexField field = staticGet.getField();
           DexType holder = field.holder;
-          if (unboxedEnumsData.isUnboxedEnum(holder)) {
-            if (staticGet.outValue() == null) {
-              iterator.removeOrReplaceByDebugLocalRead();
-              continue;
-            }
-            affectedPhis.addAll(staticGet.outValue().uniquePhiUsers());
-            if (unboxedEnumsData.matchesValuesField(field)) {
-              utilityMethods.computeIfAbsent(
-                  valuesUtilityMethod, m -> synthesizeValuesUtilityMethod());
-              DexField fieldValues = createValuesField(holder);
-              DexMethod methodValues = createValuesMethod(holder);
-              utilityMethods.computeIfAbsent(
-                  methodValues,
-                  m ->
-                      computeValuesEncodedMethod(
-                          m, fieldValues, unboxedEnumsData.getValuesSize(holder)));
-              Value rewrittenOutValue =
-                  code.createValue(
-                      ArrayTypeElement.create(TypeElement.getInt(), definitelyNotNull()));
-              InvokeStatic invoke =
-                  new InvokeStatic(methodValues, rewrittenOutValue, ImmutableList.of());
-              iterator.replaceCurrentInstruction(invoke);
-              convertedEnums.put(invoke, holder);
-            } else {
-              // Replace by ordinal + 1 for null check (null is 0).
-              assert unboxedEnumsData.hasUnboxedValueFor(field)
-                  : "Invalid read to " + field.name + ", error during enum analysis";
-              ConstNumber intConstant =
-                  code.createIntConstant(unboxedEnumsData.getUnboxedValue(field));
-              iterator.replaceCurrentInstruction(intConstant);
-              convertedEnums.put(intConstant, holder);
-            }
+          if (!unboxedEnumsData.isUnboxedEnum(holder)) {
+            continue;
+          }
+          if (staticGet.hasUnusedOutValue()) {
+            iterator.removeOrReplaceByDebugLocalRead();
+            continue;
+          }
+          affectedPhis.addAll(staticGet.outValue().uniquePhiUsers());
+          if (unboxedEnumsData.matchesValuesField(field)) {
+            // Load the size of this enum's $VALUES array before the current instruction.
+            iterator.previous();
+            Value sizeValue =
+                iterator.insertConstIntInstruction(
+                    code, options, unboxedEnumsData.getValuesSize(holder));
+            iterator.next();
+
+            // Replace Enum.$VALUES by a call to: int[] SharedUtilityClass.values(int size).
+            InvokeStatic invoke =
+                InvokeStatic.builder()
+                    .setMethod(getSharedUtilityClass().getValuesMethod())
+                    .setFreshOutValue(appView, code)
+                    .setSingleArgument(sizeValue)
+                    .build();
+            iterator.replaceCurrentInstruction(invoke);
+
+            convertedEnums.put(invoke, holder);
+
+            // Check if the call to SharedUtilityClass.values(size) is followed by a call to
+            // clone(). If so, remove it, since SharedUtilityClass.values(size) returns a fresh
+            // array. This is needed because the javac generated implementation of MyEnum.values()
+            // is implemented as `return $VALUES.clone()`.
+            removeRedundantValuesArrayCloning(invoke, instructionsToRemove, seenBlocks);
+          } else {
+            // Replace by ordinal + 1 for null check (null is 0).
+            assert unboxedEnumsData.hasUnboxedValueFor(field)
+                : "Invalid read to " + field.name + ", error during enum analysis";
+            ConstNumber intConstant =
+                code.createIntConstant(unboxedEnumsData.getUnboxedValue(field));
+            iterator.replaceCurrentInstruction(intConstant);
+            convertedEnums.put(intConstant, holder);
           }
         }
 
@@ -338,6 +393,26 @@
     return affectedPhis;
   }
 
+  private void removeRedundantValuesArrayCloning(
+      InvokeStatic invoke, Set<Instruction> instructionsToRemove, Set<BasicBlock> seenBlocks) {
+    for (Instruction user : invoke.outValue().aliasedUsers()) {
+      if (user.isInvokeVirtual()) {
+        InvokeVirtual cloneCandidate = user.asInvokeVirtual();
+        if (cloneCandidate.getInvokedMethod().match(appView.dexItemFactory().objectMembers.clone)) {
+          if (cloneCandidate.hasOutValue()) {
+            cloneCandidate.outValue().replaceUsers(invoke.outValue());
+          }
+          BasicBlock cloneBlock = cloneCandidate.getBlock();
+          if (cloneBlock == invoke.getBlock() || !seenBlocks.contains(cloneBlock)) {
+            instructionsToRemove.add(cloneCandidate);
+          } else {
+            cloneBlock.removeInstruction(cloneCandidate);
+          }
+        }
+      }
+    }
+  }
+
   private void rewriteNameMethod(
       InstructionListIterator iterator, InvokeMethodWithReceiver invokeMethod, DexType enumType) {
     DexMethod toStringMethod =
@@ -368,7 +443,7 @@
     while (iterator.hasNext() && iterator.peekNext().isArgument()) {
       iterator.next();
     }
-    return iterator.insertConstNumberInstruction(code, appView.options(), 0, TypeElement.getInt());
+    return iterator.insertConstIntInstruction(code, options, 0);
   }
 
   private DexMethod computeInstanceFieldMethod(DexField field) {
@@ -427,34 +502,6 @@
     return type.toSourceString().replace('.', '$');
   }
 
-  private DexField createValuesField(DexType enumType) {
-    return createValuesField(enumType, relocator.getNewMemberLocationFor(enumType), factory);
-  }
-
-  static DexField createValuesField(
-      DexType enumType, DexType enumUtilityClass, DexItemFactory dexItemFactory) {
-    return dexItemFactory.createField(
-        enumUtilityClass,
-        dexItemFactory.intArrayType,
-        "$$values$$field$" + compatibleName(enumType));
-  }
-
-  private DexMethod createValuesMethod(DexType enumType) {
-    return factory.createMethod(
-        relocator.getNewMemberLocationFor(enumType),
-        factory.createProto(factory.intArrayType),
-        "$$values$$method$" + compatibleName(enumType));
-  }
-
-  private DexEncodedMethod computeValuesEncodedMethod(
-      DexMethod method, DexField fieldValues, int numEnumInstances) {
-    CfCode cfCode =
-        new EnumUnboxingCfCodeProvider.EnumUnboxingValuesCfCodeProvider(
-                appView, method.holder, fieldValues, numEnumInstances, valuesUtilityMethod)
-            .generateCfCode();
-    return synthesizeUtilityMethod(cfCode, method, true);
-  }
-
   private DexMethod computeInstanceFieldUtilityMethod(DexType enumType, DexField field) {
     assert unboxedEnumsData.isUnboxedEnum(enumType);
     assert field.holder == enumType || field.holder == factory.enumType;
@@ -466,7 +513,7 @@
             + compatibleName(enumType);
     DexMethod fieldMethod =
         factory.createMethod(
-            relocator.getNewMemberLocationFor(enumType),
+            utilityClasses.getLocalUtilityClass(enumType).getType(),
             factory.createProto(field.type, factory.intType),
             methodName);
     utilityMethods.computeIfAbsent(
@@ -480,7 +527,7 @@
     String methodName = "string$valueOf$" + compatibleName(enumType);
     DexMethod fieldMethod =
         factory.createMethod(
-            relocator.getNewMemberLocationFor(enumType),
+            utilityClasses.getLocalUtilityClass(enumType).getType(),
             factory.createProto(factory.stringType, factory.intType),
             methodName);
     AbstractValue nullString =
@@ -495,7 +542,7 @@
     assert unboxedEnumsData.isUnboxedEnum(enumType);
     DexMethod valueOf =
         factory.createMethod(
-            relocator.getNewMemberLocationFor(enumType),
+            utilityClasses.getLocalUtilityClass(enumType).getType(),
             factory.createProto(factory.intType, factory.stringType),
             "valueOf" + compatibleName(enumType));
     utilityMethods.computeIfAbsent(valueOf, m -> synthesizeValueOfUtilityMethod(m, enumType));
@@ -555,7 +602,7 @@
     }
     // We make the order deterministic.
     for (List<T> value : encodedMembersMap.values()) {
-      value.sort((m1, m2) -> m1.getReference().compareTo(m2.getReference()));
+      value.sort(Comparator.comparing(DexEncodedMember::getReference));
     }
     return encodedMembersMap;
   }
@@ -572,7 +619,7 @@
                 unboxedEnumsData.getInstanceFieldData(enumType, field).asEnumFieldMappingData(),
                 nullValue)
             .generateCfCode();
-    return synthesizeUtilityMethod(cfCode, method, false);
+    return synthesizeUtilityMethod(cfCode, method);
   }
 
   private DexEncodedMethod synthesizeValueOfUtilityMethod(DexMethod method, DexType enumType) {
@@ -589,51 +636,45 @@
                     .getInstanceFieldData(enumType, factory.enumMembers.nameField)
                     .asEnumFieldMappingData())
             .generateCfCode();
-    return synthesizeUtilityMethod(cfCode, method, false);
+    return synthesizeUtilityMethod(cfCode, method);
   }
 
   private DexEncodedMethod synthesizeZeroCheckMethod() {
     CfCode cfCode =
         EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheck(appView.options(), zeroCheckMethod);
-    return synthesizeUtilityMethod(cfCode, zeroCheckMethod, false);
+    return synthesizeUtilityMethod(cfCode, zeroCheckMethod);
   }
 
   private DexEncodedMethod synthesizeZeroCheckMessageMethod() {
     CfCode cfCode =
         EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheckMessage(
             appView.options(), zeroCheckMessageMethod);
-    return synthesizeUtilityMethod(cfCode, zeroCheckMessageMethod, false);
+    return synthesizeUtilityMethod(cfCode, zeroCheckMessageMethod);
   }
 
   private DexEncodedMethod synthesizeOrdinalMethod() {
     CfCode cfCode =
         EnumUnboxingCfMethods.EnumUnboxingMethods_ordinal(appView.options(), ordinalUtilityMethod);
-    return synthesizeUtilityMethod(cfCode, ordinalUtilityMethod, false);
+    return synthesizeUtilityMethod(cfCode, ordinalUtilityMethod);
   }
 
   private DexEncodedMethod synthesizeEqualsMethod() {
     CfCode cfCode =
         EnumUnboxingCfMethods.EnumUnboxingMethods_equals(appView.options(), equalsUtilityMethod);
-    return synthesizeUtilityMethod(cfCode, equalsUtilityMethod, false);
+    return synthesizeUtilityMethod(cfCode, equalsUtilityMethod);
   }
 
   private DexEncodedMethod synthesizeCompareToMethod() {
     CfCode cfCode =
         EnumUnboxingCfMethods.EnumUnboxingMethods_compareTo(
             appView.options(), compareToUtilityMethod);
-    return synthesizeUtilityMethod(cfCode, compareToUtilityMethod, false);
+    return synthesizeUtilityMethod(cfCode, compareToUtilityMethod);
   }
 
-  private DexEncodedMethod synthesizeValuesUtilityMethod() {
-    CfCode cfCode =
-        EnumUnboxingCfMethods.EnumUnboxingMethods_values(appView.options(), valuesUtilityMethod);
-    return synthesizeUtilityMethod(cfCode, valuesUtilityMethod, false);
-  }
-
-  private DexEncodedMethod synthesizeUtilityMethod(CfCode cfCode, DexMethod method, boolean sync) {
+  private DexEncodedMethod synthesizeUtilityMethod(CfCode cfCode, DexMethod method) {
     return new DexEncodedMethod(
         method,
-        synthesizedMethodAccessFlags(sync),
+        MethodAccessFlags.createPublicStaticSynthetic(),
         MethodTypeSignature.noSignature(),
         DexAnnotationSet.empty(),
         ParameterAnnotationsList.empty(),
@@ -641,12 +682,4 @@
         true,
         REQUIRED_CLASS_FILE_VERSION);
   }
-
-  private MethodAccessFlags synthesizedMethodAccessFlags(boolean sync) {
-    int access = Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC | Constants.ACC_STATIC;
-    if (sync) {
-      access = access | Constants.ACC_SYNCHRONIZED;
-    }
-    return MethodAccessFlags.fromSharedAccessFlags(access, false);
-  }
 }
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 a29e290..8bab979 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
@@ -38,18 +38,18 @@
   private final AppView<AppInfoWithLiveness> appView;
   private final DexItemFactory factory;
   private final Set<DexType> enumsToUnbox;
-  private final UnboxedEnumMemberRelocator relocator;
+  private final EnumUnboxingUtilityClasses utilityClasses;
   private final EnumUnboxingRewriter enumUnboxerRewriter;
 
   EnumUnboxingTreeFixer(
       AppView<AppInfoWithLiveness> appView,
       Set<DexType> enumsToUnbox,
-      UnboxedEnumMemberRelocator relocator,
+      EnumUnboxingUtilityClasses utilityClasses,
       EnumUnboxingRewriter enumUnboxerRewriter) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
     this.enumsToUnbox = enumsToUnbox;
-    this.relocator = relocator;
+    this.utilityClasses = utilityClasses;
     this.enumUnboxerRewriter = enumUnboxerRewriter;
   }
 
@@ -57,7 +57,7 @@
     assert enumUnboxerRewriter != null;
     // Fix all methods and fields using enums to unbox.
     for (DexProgramClass clazz : appView.appInfo().classes()) {
-      if (enumsToUnbox.contains(clazz.type)) {
+      if (enumsToUnbox.contains(clazz.getType())) {
         // Clear the initializers and move the static methods to the new location.
         Set<DexEncodedMethod> methodsToRemove = Sets.newIdentityHashSet();
         clazz
@@ -67,7 +67,7 @@
                   if (m.isInitializer()) {
                     clearEnumToUnboxMethod(m);
                   } else {
-                    DexType newHolder = relocator.getNewMemberLocationFor(clazz.type);
+                    DexType newHolder = utilityClasses.getLocalUtilityClass(clazz).getType();
                     List<DexEncodedMethod> movedMethods =
                         unboxedEnumsMethods.computeIfAbsent(newHolder, k -> new ArrayList<>());
                     movedMethods.add(fixupEncodedMethodToUtility(m, newHolder));
@@ -178,16 +178,15 @@
       newMethod =
           factory.createInstanceInitializerWithFreshProto(
               newMethod,
-              relocator.getDefaultEnumUnboxingUtility(),
+              utilityClasses.getSharedUtilityClass().getType(),
               tryMethod -> holder.lookupMethod(tryMethod) == null);
     } else {
       int index = 0;
       while (holder.lookupMethod(newMethod) != null) {
         newMethod =
-            factory.createMethod(
-                newMethod.holder,
-                newMethod.proto,
-                encodedMethod.getName().toString() + "$enumunboxing$" + index++);
+            newMethod.withName(
+                encodedMethod.getName().toString() + "$enumunboxing$" + index++,
+                appView.dexItemFactory());
       }
     }
     return newMethod;
@@ -202,7 +201,7 @@
       DexField field = encodedField.getReference();
       DexType newType = fixupType(field.type);
       if (newType != field.type) {
-        DexField newField = factory.createField(field.holder, newType, field.name);
+        DexField newField = field.withType(newType, factory);
         lensBuilder.move(field, newField);
         DexEncodedField newEncodedField =
             encodedField.toTypeSubstitutedField(
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
new file mode 100644
index 0000000..6026c71
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClass.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.enums;
+
+import com.android.tools.r8.graph.DexProgramClass;
+
+public abstract class EnumUnboxingUtilityClass {
+
+  public abstract DexProgramClass getDefinition();
+}
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
new file mode 100644
index 0000000..1753375
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java
@@ -0,0 +1,108 @@
+// 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.optimize.enums;
+
+import com.android.tools.r8.graph.AppView;
+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.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.function.Consumer;
+
+public class EnumUnboxingUtilityClasses {
+
+  // Synthetic classes for utilities specific to the unboxing of a single enum.
+  private final ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses;
+
+  // Default enum unboxing utility synthetic class used to hold all the shared unboxed enum
+  // methods (ordinal(I), equals(II), etc.).
+  private final SharedEnumUnboxingUtilityClass sharedUtilityClass;
+
+  private EnumUnboxingUtilityClasses(
+      SharedEnumUnboxingUtilityClass sharedUtilityClass,
+      ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses) {
+    this.sharedUtilityClass = sharedUtilityClass;
+    this.localUtilityClasses = localUtilityClasses;
+  }
+
+  public void forEach(Consumer<? super EnumUnboxingUtilityClass> consumer) {
+    localUtilityClasses.values().forEach(consumer);
+    consumer.accept(getSharedUtilityClass());
+  }
+
+  public LocalEnumUnboxingUtilityClass getLocalUtilityClass(DexProgramClass enumClass) {
+    return getLocalUtilityClass(enumClass.getType());
+  }
+
+  public LocalEnumUnboxingUtilityClass getLocalUtilityClass(DexType enumType) {
+    LocalEnumUnboxingUtilityClass localEnumUnboxingUtilityClass = localUtilityClasses.get(enumType);
+    assert localEnumUnboxingUtilityClass != null;
+    return localEnumUnboxingUtilityClass;
+  }
+
+  public SharedEnumUnboxingUtilityClass getSharedUtilityClass() {
+    return sharedUtilityClass;
+  }
+
+  public static Builder builder(AppView<AppInfoWithLiveness> appView) {
+    return new Builder(appView);
+  }
+
+  public static class Builder {
+
+    private final AppView<AppInfoWithLiveness> appView;
+    private ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses;
+    private SharedEnumUnboxingUtilityClass sharedUtilityClass;
+
+    public Builder(AppView<AppInfoWithLiveness> appView) {
+      this.appView = appView;
+    }
+
+    public Builder synthesizeEnumUnboxingUtilityClasses(
+        Set<DexProgramClass> enumsToUnbox,
+        EnumDataMap enumDataMap,
+        DirectMappedDexApplication.Builder appBuilder,
+        FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
+      SharedEnumUnboxingUtilityClass sharedUtilityClass =
+          SharedEnumUnboxingUtilityClass.builder(
+                  appView, enumDataMap, enumsToUnbox, fieldAccessInfoCollectionModifierBuilder)
+              .build(appBuilder);
+      ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses =
+          createLocalUtilityClasses(enumsToUnbox, appBuilder);
+      this.localUtilityClasses = localUtilityClasses;
+      this.sharedUtilityClass = sharedUtilityClass;
+      return this;
+    }
+
+    public EnumUnboxingUtilityClasses build() {
+      return new EnumUnboxingUtilityClasses(sharedUtilityClass, localUtilityClasses);
+    }
+
+    private ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> createLocalUtilityClasses(
+        Set<DexProgramClass> enumsToUnbox, DirectMappedDexApplication.Builder appBuilder) {
+      ImmutableMap.Builder<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses =
+          ImmutableMap.builder();
+      for (DexProgramClass enumToUnbox : enumsToUnbox) {
+        localUtilityClasses.put(
+            enumToUnbox.getType(),
+            LocalEnumUnboxingUtilityClass.builder(appView, enumToUnbox).build(appBuilder));
+      }
+      return localUtilityClasses.build();
+    }
+
+    static DexType getUtilityClassType(
+        DexProgramClass context, String suffix, DexItemFactory dexItemFactory) {
+      return dexItemFactory.createType(
+          DescriptorUtils.getDescriptorFromClassBinaryName(
+              DescriptorUtils.getBinaryNameFromDescriptor(context.getType().toDescriptorString())
+                  + suffix));
+    }
+  }
+}
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
new file mode 100644
index 0000000..f4d4636
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
@@ -0,0 +1,94 @@
+// 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.optimize.enums;
+
+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.DexItemFactory;
+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.DirectMappedDexApplication;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Collections;
+
+public class LocalEnumUnboxingUtilityClass extends EnumUnboxingUtilityClass {
+
+  private static final String ENUM_UNBOXING_LOCAL_UTILITY_CLASS_SUFFIX =
+      "$r8$EnumUnboxingLocalUtility";
+
+  private final DexProgramClass localUtilityClass;
+
+  public LocalEnumUnboxingUtilityClass(DexProgramClass localUtilityClass) {
+    this.localUtilityClass = localUtilityClass;
+  }
+
+  public static Builder builder(AppView<AppInfoWithLiveness> appView, DexProgramClass enumToUnbox) {
+    return new Builder(appView, enumToUnbox);
+  }
+
+  @Override
+  public DexProgramClass getDefinition() {
+    return localUtilityClass;
+  }
+
+  public DexType getType() {
+    return localUtilityClass.getType();
+  }
+
+  public static class Builder {
+
+    private final AppView<AppInfoWithLiveness> appView;
+    private final DexItemFactory dexItemFactory;
+    private final DexProgramClass enumToUnbox;
+    private final DexType localUtilityClassType;
+
+    private Builder(AppView<AppInfoWithLiveness> appView, DexProgramClass enumToUnbox) {
+      this.appView = appView;
+      this.dexItemFactory = appView.dexItemFactory();
+      this.enumToUnbox = enumToUnbox;
+      this.localUtilityClassType =
+          EnumUnboxingUtilityClasses.Builder.getUtilityClassType(
+              enumToUnbox, ENUM_UNBOXING_LOCAL_UTILITY_CLASS_SUFFIX, dexItemFactory);
+
+      assert appView.appInfo().definitionForWithoutExistenceAssert(localUtilityClassType) == null;
+    }
+
+    LocalEnumUnboxingUtilityClass build(DirectMappedDexApplication.Builder appBuilder) {
+      DexProgramClass clazz = createClass();
+      appBuilder.addSynthesizedClass(clazz);
+      appView.appInfo().addSynthesizedClass(clazz, enumToUnbox);
+      return new LocalEnumUnboxingUtilityClass(clazz);
+    }
+
+    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);
+    }
+  }
+}
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
new file mode 100644
index 0000000..19adc9f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
@@ -0,0 +1,279 @@
+// 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.optimize.enums;
+
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.cf.code.CfArrayStore;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+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.DexItemFactory;
+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.DirectMappedDexApplication;
+import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramField;
+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.origin.SynthesizedOrigin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import org.objectweb.asm.Opcodes;
+
+public class SharedEnumUnboxingUtilityClass extends EnumUnboxingUtilityClass {
+
+  public static final String ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX =
+      "$r8$EnumUnboxingSharedUtility";
+
+  private final DexProgramClass sharedUtilityClass;
+  private final ProgramField valuesField;
+  private final ProgramMethod valuesMethod;
+
+  public SharedEnumUnboxingUtilityClass(
+      DexProgramClass sharedUtilityClass, ProgramField valuesField, ProgramMethod valuesMethod) {
+    this.sharedUtilityClass = sharedUtilityClass;
+    this.valuesField = valuesField;
+    this.valuesMethod = valuesMethod;
+  }
+
+  public static Builder builder(
+      AppView<AppInfoWithLiveness> appView,
+      EnumDataMap enumDataMap,
+      Set<DexProgramClass> enumsToUnbox,
+      FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
+    return new Builder(
+        appView, enumDataMap, enumsToUnbox, fieldAccessInfoCollectionModifierBuilder);
+  }
+
+  @Override
+  public DexProgramClass getDefinition() {
+    return sharedUtilityClass;
+  }
+
+  public ProgramField getValuesField() {
+    return valuesField;
+  }
+
+  public ProgramMethod getValuesMethod() {
+    return valuesMethod;
+  }
+
+  public DexType getType() {
+    return sharedUtilityClass.getType();
+  }
+
+  public static class Builder {
+
+    private final AppView<AppInfoWithLiveness> appView;
+    private final DexItemFactory dexItemFactory;
+    private final EnumDataMap enumDataMap;
+    private final Set<DexProgramClass> enumsToUnbox;
+    private final FieldAccessInfoCollectionModifier.Builder
+        fieldAccessInfoCollectionModifierBuilder;
+    private final DexType sharedUtilityClassType;
+
+    private DexEncodedField valuesField;
+    private DexEncodedMethod valuesMethod;
+
+    private Builder(
+        AppView<AppInfoWithLiveness> appView,
+        EnumDataMap enumDataMap,
+        Set<DexProgramClass> enumsToUnbox,
+        FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
+      this.appView = appView;
+      this.dexItemFactory = appView.dexItemFactory();
+      this.enumDataMap = enumDataMap;
+      this.enumsToUnbox = enumsToUnbox;
+      this.fieldAccessInfoCollectionModifierBuilder = fieldAccessInfoCollectionModifierBuilder;
+      this.sharedUtilityClassType =
+          EnumUnboxingUtilityClasses.Builder.getUtilityClassType(
+              findDeterministicContextType(enumsToUnbox),
+              ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX,
+              dexItemFactory);
+
+      assert appView.appInfo().definitionForWithoutExistenceAssert(sharedUtilityClassType) == null;
+    }
+
+    SharedEnumUnboxingUtilityClass build(DirectMappedDexApplication.Builder appBuilder) {
+      DexProgramClass clazz = createClass();
+      appBuilder.addSynthesizedClass(clazz);
+      appView.appInfo().addSynthesizedClassToBase(clazz, enumsToUnbox);
+      return new SharedEnumUnboxingUtilityClass(
+          clazz, new ProgramField(clazz, valuesField), new ProgramMethod(clazz, valuesMethod));
+    }
+
+    private DexProgramClass createClass() {
+      DexEncodedField valuesField = createValuesField(sharedUtilityClassType);
+      return new DexProgramClass(
+          sharedUtilityClassType,
+          null,
+          new SynthesizedOrigin("enum unboxing", EnumUnboxer.class),
+          ClassAccessFlags.createPublicFinalSynthetic(),
+          dexItemFactory.objectType,
+          DexTypeList.empty(),
+          null,
+          null,
+          Collections.emptyList(),
+          null,
+          Collections.emptyList(),
+          ClassSignature.noSignature(),
+          DexAnnotationSet.empty(),
+          new DexEncodedField[] {valuesField},
+          DexEncodedField.EMPTY_ARRAY,
+          new DexEncodedMethod[] {
+            createClassInitializer(valuesField), createValuesMethod(valuesField)
+          },
+          DexEncodedMethod.EMPTY_ARRAY,
+          dexItemFactory.getSkipNameValidationForTesting(),
+          DexProgramClass::checksumFromType);
+    }
+
+    // Fields.
+
+    private DexEncodedField createValuesField(DexType sharedUtilityClassType) {
+      DexEncodedField valuesField =
+          new DexEncodedField(
+              dexItemFactory.createField(
+                  sharedUtilityClassType, dexItemFactory.intArrayType, "$VALUES"),
+              FieldAccessFlags.createPublicStaticFinalSynthetic(),
+              FieldTypeSignature.noSignature(),
+              DexAnnotationSet.empty(),
+              DexEncodedField.NO_STATIC_VALUE,
+              DexEncodedField.NOT_DEPRECATED,
+              DexEncodedField.D8_R8_SYNTHESIZED);
+      fieldAccessInfoCollectionModifierBuilder
+          .recordFieldReadInUnknownContext(valuesField.getReference())
+          .recordFieldWriteInUnknownContext(valuesField.getReference());
+      this.valuesField = valuesField;
+      return valuesField;
+    }
+
+    // Methods.
+
+    private DexEncodedMethod createClassInitializer(DexEncodedField valuesField) {
+      return new DexEncodedMethod(
+          dexItemFactory.createClassInitializer(sharedUtilityClassType),
+          MethodAccessFlags.createForClassInitializer(),
+          MethodTypeSignature.noSignature(),
+          DexAnnotationSet.empty(),
+          ParameterAnnotationsList.empty(),
+          createClassInitializerCode(valuesField),
+          DexEncodedMethod.D8_R8_SYNTHESIZED,
+          CfVersion.V1_6);
+    }
+
+    private CfCode createClassInitializerCode(DexEncodedField valuesField) {
+      int maxValuesArraySize = enumDataMap.getMaxValuesSize();
+      int numberOfInstructions = 4 + maxValuesArraySize * 4;
+      List<CfInstruction> instructions = new ArrayList<>(numberOfInstructions);
+      instructions.add(new CfConstNumber(maxValuesArraySize, ValueType.INT));
+      instructions.add(new CfNewArray(dexItemFactory.intArrayType));
+      for (int i = 0; i < maxValuesArraySize; i++) {
+        instructions.add(new CfStackInstruction(Opcode.Dup));
+        instructions.add(new CfConstNumber(i, ValueType.INT));
+        // i + 1 because 0 represents the null value.
+        instructions.add(new CfConstNumber(i + 1, ValueType.INT));
+        instructions.add(new CfArrayStore(MemberType.INT));
+      }
+      instructions.add(new CfFieldInstruction(Opcodes.PUTSTATIC, valuesField.getReference()));
+      instructions.add(new CfReturnVoid());
+
+      int maxStack = 4;
+      int maxLocals = 0;
+      return new CfCode(
+          sharedUtilityClassType,
+          maxStack,
+          maxLocals,
+          instructions,
+          Collections.emptyList(),
+          Collections.emptyList());
+    }
+
+    private DexEncodedMethod createValuesMethod(DexEncodedField valuesField) {
+      DexEncodedMethod valuesMethod =
+          new DexEncodedMethod(
+              dexItemFactory.createMethod(
+                  sharedUtilityClassType,
+                  dexItemFactory.createProto(dexItemFactory.intArrayType, dexItemFactory.intType),
+                  "values"),
+              MethodAccessFlags.createPublicStaticSynthetic(),
+              MethodTypeSignature.noSignature(),
+              DexAnnotationSet.empty(),
+              ParameterAnnotationsList.empty(),
+              createValuesMethodCode(valuesField),
+              DexEncodedMethod.D8_R8_SYNTHESIZED,
+              CfVersion.V1_6);
+      this.valuesMethod = valuesMethod;
+      return valuesMethod;
+    }
+
+    private CfCode createValuesMethodCode(DexEncodedField valuesField) {
+      int maxStack = 5;
+      int maxLocals = 2;
+      int argumentLocalSlot = 0;
+      int resultLocalSlot = 1;
+      return new CfCode(
+          sharedUtilityClassType,
+          maxStack,
+          maxLocals,
+          ImmutableList.of(
+              // int[] result = new int[size];
+              new CfLoad(ValueType.INT, argumentLocalSlot),
+              new CfNewArray(dexItemFactory.intArrayType),
+              new CfStore(ValueType.OBJECT, resultLocalSlot),
+              // System.arraycopy(SharedUtilityClass.$VALUES, 0, result, 0, size);
+              new CfFieldInstruction(Opcodes.GETSTATIC, valuesField.getReference()),
+              new CfConstNumber(0, ValueType.INT),
+              new CfLoad(ValueType.OBJECT, resultLocalSlot),
+              new CfConstNumber(0, ValueType.INT),
+              new CfLoad(ValueType.INT, argumentLocalSlot),
+              new CfInvoke(
+                  Opcodes.INVOKESTATIC, dexItemFactory.javaLangSystemMethods.arraycopy, false),
+              // return result
+              new CfLoad(ValueType.OBJECT, resultLocalSlot),
+              new CfReturn(ValueType.OBJECT)),
+          Collections.emptyList(),
+          Collections.emptyList());
+    }
+
+    private static DexProgramClass findDeterministicContextType(Set<DexProgramClass> contexts) {
+      DexProgramClass deterministicContext = null;
+      for (DexProgramClass context : contexts) {
+        if (deterministicContext == null) {
+          deterministicContext = context;
+        } else if (context.type.compareTo(deterministicContext.type) < 0) {
+          deterministicContext = context;
+        }
+      }
+      return deterministicContext;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
deleted file mode 100644
index 54c416a..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
+++ /dev/null
@@ -1,198 +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.optimize.enums;
-
-import static com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter.createValuesField;
-
-import com.android.tools.r8.dex.Constants;
-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.DexProgramClass;
-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.FieldAccessFlags;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.graph.ProgramPackage;
-import com.android.tools.r8.graph.ProgramPackageCollection;
-import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
-import com.android.tools.r8.utils.SetUtils;
-import com.google.common.collect.ImmutableMap;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-public class UnboxedEnumMemberRelocator {
-
-  public static final String ENUM_UNBOXING_UTILITY_CLASS_SUFFIX = "$r8$EnumUnboxingUtility";
-
-  // Default enum unboxing utility synthetic class used to hold all the shared unboxed enum
-  // methods (ordinal(I), equals(II), etc.) and the unboxed enums members which were free to be
-  // placed anywhere.
-  private final DexType defaultEnumUnboxingUtility;
-  // Some unboxed enum members have to be placed in a specific package, in this case, we keep a
-  // map from unboxed enum types to synthetic classes, so that all members of unboxed enums in the
-  // keys are moved to the corresponding value.
-  private final ImmutableMap<DexType, DexType> relocationMap;
-
-  public DexType getDefaultEnumUnboxingUtility() {
-    return defaultEnumUnboxingUtility;
-  }
-
-  public DexType getNewMemberLocationFor(DexType enumType) {
-    return relocationMap.getOrDefault(enumType, defaultEnumUnboxingUtility);
-  }
-
-  private UnboxedEnumMemberRelocator(
-      DexType defaultEnumUnboxingUtility, ImmutableMap<DexType, DexType> relocationMap) {
-    this.defaultEnumUnboxingUtility = defaultEnumUnboxingUtility;
-    this.relocationMap = relocationMap;
-  }
-
-  public static Builder builder(AppView<?> appView) {
-    return new Builder(appView);
-  }
-
-  public static class Builder {
-    private DexProgramClass defaultEnumUnboxingUtility;
-    private Map<DexType, DexType> relocationMap = new IdentityHashMap<>();
-    private final AppView<?> appView;
-
-    public Builder(AppView<?> appView) {
-      this.appView = appView;
-    }
-
-    public Builder synthesizeEnumUnboxingUtilityClasses(
-        Set<DexProgramClass> enumsToUnbox,
-        ProgramPackageCollection enumsToUnboxWithPackageRequirement,
-        DirectMappedDexApplication.Builder appBuilder,
-        FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
-      Set<DexProgramClass> enumsToUnboxWithoutPackageRequirement =
-          SetUtils.newIdentityHashSet(enumsToUnbox);
-      enumsToUnboxWithoutPackageRequirement.removeIf(enumsToUnboxWithPackageRequirement::contains);
-      defaultEnumUnboxingUtility =
-          synthesizeUtilityClass(
-              enumsToUnbox,
-              enumsToUnboxWithoutPackageRequirement,
-              appBuilder,
-              fieldAccessInfoCollectionModifierBuilder);
-      if (!enumsToUnboxWithPackageRequirement.isEmpty()) {
-        synthesizeRelocationMap(
-            enumsToUnbox,
-            enumsToUnboxWithPackageRequirement,
-            appBuilder,
-            fieldAccessInfoCollectionModifierBuilder);
-      }
-      return this;
-    }
-
-    public UnboxedEnumMemberRelocator build() {
-      return new UnboxedEnumMemberRelocator(
-          defaultEnumUnboxingUtility.getType(), ImmutableMap.copyOf(relocationMap));
-    }
-
-    private void synthesizeRelocationMap(
-        Set<DexProgramClass> contexts,
-        ProgramPackageCollection enumsToUnboxWithPackageRequirement,
-        DirectMappedDexApplication.Builder appBuilder,
-        FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
-      for (ProgramPackage programPackage : enumsToUnboxWithPackageRequirement) {
-        Set<DexProgramClass> enumsToUnboxInPackage = programPackage.classesInPackage();
-        DexProgramClass enumUtilityClass =
-            synthesizeUtilityClass(
-                contexts,
-                enumsToUnboxInPackage,
-                appBuilder,
-                fieldAccessInfoCollectionModifierBuilder);
-        if (enumUtilityClass != defaultEnumUnboxingUtility) {
-          for (DexProgramClass enumToUnbox : enumsToUnboxInPackage) {
-            assert !relocationMap.containsKey(enumToUnbox.type);
-            relocationMap.put(enumToUnbox.type, enumUtilityClass.getType());
-          }
-        }
-      }
-    }
-
-    private DexProgramClass synthesizeUtilityClass(
-        Set<DexProgramClass> contexts,
-        Set<DexProgramClass> relocatedEnums,
-        DirectMappedDexApplication.Builder appBuilder,
-        FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
-      DexProgramClass deterministicContext = findDeterministicContextType(contexts);
-      String descriptorString = deterministicContext.getType().toDescriptorString();
-      String descriptorPrefix = descriptorString.substring(0, descriptorString.length() - 1);
-      String syntheticClassDescriptor = descriptorPrefix + ENUM_UNBOXING_UTILITY_CLASS_SUFFIX + ";";
-      DexType type = appView.dexItemFactory().createType(syntheticClassDescriptor);
-
-      // Required fields.
-      List<DexEncodedField> staticFields = new ArrayList<>(relocatedEnums.size());
-      for (DexProgramClass relocatedEnum : relocatedEnums) {
-        DexField reference =
-            createValuesField(relocatedEnum.getType(), type, appView.dexItemFactory());
-        staticFields.add(
-            new DexEncodedField(reference, FieldAccessFlags.createPublicStaticSynthetic()));
-        fieldAccessInfoCollectionModifierBuilder
-            .recordFieldReadInUnknownContext(reference)
-            .recordFieldWriteInUnknownContext(reference);
-      }
-      staticFields.sort(Comparator.comparing(DexEncodedField::getReference));
-
-      // The defaultEnumUnboxingUtility depends on all unboxable enums, and other synthetic types
-      // depend on a subset of the unboxable enums, the deterministicContextType can therefore
-      // be found twice, and in that case the same utility class can be used for both.
-      if (defaultEnumUnboxingUtility != null && type == defaultEnumUnboxingUtility.getType()) {
-        defaultEnumUnboxingUtility.appendStaticFields(staticFields);
-        return defaultEnumUnboxingUtility;
-      }
-      assert appView.appInfo().definitionForWithoutExistenceAssert(type) == null;
-      DexProgramClass syntheticClass =
-          new DexProgramClass(
-              type,
-              null,
-              new SynthesizedOrigin("enum unboxing", EnumUnboxer.class),
-              ClassAccessFlags.fromSharedAccessFlags(
-                  Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
-              appView.dexItemFactory().objectType,
-              DexTypeList.empty(),
-              null,
-              null,
-              Collections.emptyList(),
-              null,
-              Collections.emptyList(),
-              ClassSignature.noSignature(),
-              DexAnnotationSet.empty(),
-              staticFields.toArray(DexEncodedField.EMPTY_ARRAY),
-              DexEncodedField.EMPTY_ARRAY,
-              DexEncodedMethod.EMPTY_ARRAY,
-              DexEncodedMethod.EMPTY_ARRAY,
-              appView.dexItemFactory().getSkipNameValidationForTesting(),
-              DexProgramClass::checksumFromType);
-      appBuilder.addSynthesizedClass(syntheticClass);
-      appView.appInfo().addSynthesizedClassToBase(syntheticClass, contexts);
-      return syntheticClass;
-    }
-
-    private DexProgramClass findDeterministicContextType(Set<DexProgramClass> contexts) {
-      DexProgramClass deterministicContext = null;
-      for (DexProgramClass context : contexts) {
-        if (deterministicContext == null) {
-          deterministicContext = context;
-        } else if (context.type.compareTo(deterministicContext.type) < 0) {
-          deterministicContext = context;
-        }
-      }
-      return deterministicContext;
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
index 0408418..533174b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
@@ -8,12 +8,13 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
+import com.android.tools.r8.utils.AndroidApiLevel;
 
 public class DefaultFieldOptimizationInfo extends FieldOptimizationInfo {
 
   private static final DefaultFieldOptimizationInfo INSTANCE = new DefaultFieldOptimizationInfo();
 
-  private DefaultFieldOptimizationInfo() {}
+  protected DefaultFieldOptimizationInfo() {}
 
   public static DefaultFieldOptimizationInfo getInstance() {
     return INSTANCE;
@@ -55,6 +56,11 @@
   }
 
   @Override
+  public AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApi) {
+    throw new RuntimeException("Should never be called");
+  }
+
+  @Override
   public MutableFieldOptimizationInfo toMutableOptimizationInfo() {
     return new MutableFieldOptimizationInfo();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationWithMinApiInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationWithMinApiInfo.java
new file mode 100644
index 0000000..27c6f43
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationWithMinApiInfo.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.info;
+
+import com.android.tools.r8.utils.AndroidApiLevel;
+
+public class DefaultFieldOptimizationWithMinApiInfo extends DefaultFieldOptimizationInfo {
+
+  private static final DefaultFieldOptimizationWithMinApiInfo INSTANCE =
+      new DefaultFieldOptimizationWithMinApiInfo();
+
+  public static DefaultFieldOptimizationWithMinApiInfo getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public boolean hasApiReferenceLevel() {
+    return true;
+  }
+
+  @Override
+  public AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApi) {
+    return minApi;
+  }
+
+  @Override
+  public MutableFieldOptimizationInfo toMutableOptimizationInfo() {
+    MutableFieldOptimizationInfo updatableFieldOptimizationInfo = super.toMutableOptimizationInfo();
+    // Use null to specify that the min api is set to minApi.
+    updatableFieldOptimizationInfo.setMinApiReferenceLevel();
+    return updatableFieldOptimizationInfo;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 1df189a..0aed133 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
@@ -180,7 +180,7 @@
 
   @Override
   public AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApi) {
-    return UNKNOWN_API_REFERENCE_LEVEL;
+    throw new RuntimeException("Should never be called");
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationWithMinApiInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationWithMinApiInfo.java
index a5e21d9..0638369 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationWithMinApiInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationWithMinApiInfo.java
@@ -30,7 +30,7 @@
     MutableMethodOptimizationInfo updatableMethodOptimizationInfo =
         super.toMutableOptimizationInfo();
     // Use null to specify that the min api is set to minApi.
-    updatableMethodOptimizationInfo.setApiReferenceLevel(null);
+    updatableMethodOptimizationInfo.setMinApiReferenceLevel();
     return updatableMethodOptimizationInfo;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MemberOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MemberOptimizationInfo.java
index e9ce890..910669d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MemberOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MemberOptimizationInfo.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.info;
 
+import com.android.tools.r8.utils.AndroidApiLevel;
+
 public interface MemberOptimizationInfo<
     T extends MemberOptimizationInfo<T> & MutableOptimizationInfo> {
 
@@ -19,5 +21,11 @@
     return null;
   }
 
+  default boolean hasApiReferenceLevel() {
+    return false;
+  }
+
+  AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApi);
+
   T toMutableOptimizationInfo();
 }
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 41c145f..748d82d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OptionalBool;
 import java.util.BitSet;
@@ -95,10 +94,6 @@
 
   public abstract boolean returnValueHasBeenPropagated();
 
-  public abstract AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApi);
-
-  public abstract boolean hasApiReferenceLevel();
-
   public static OptionalBool isApiSafeForInlining(
       MethodOptimizationInfo caller, MethodOptimizationInfo inlinee, InternalOptions options) {
     if (!options.apiModelingOptions().enableApiCallerIdentification) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
index 9150ea0..d00377e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
@@ -15,6 +15,8 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.Optional;
 import java.util.Set;
 
 /**
@@ -36,6 +38,7 @@
   private int readBits = 0;
   private ClassTypeElement dynamicLowerBoundType = null;
   private TypeElement dynamicUpperBoundType = null;
+  private Optional<AndroidApiLevel> apiReferenceLevel = null;
 
   public MutableFieldOptimizationInfo fixupClassTypeReferences(
       AppView<? extends AppInfoWithClassHierarchy> appView, GraphLens lens) {
@@ -150,4 +153,29 @@
   public MutableFieldOptimizationInfo asMutableFieldOptimizationInfo() {
     return this;
   }
+
+  @SuppressWarnings("OptionalAssignedToNull")
+  @Override
+  public boolean hasApiReferenceLevel() {
+    return apiReferenceLevel != null;
+  }
+
+  @Override
+  public AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApi) {
+    assert hasApiReferenceLevel();
+    return apiReferenceLevel.orElse(minApi);
+  }
+
+  @Override
+  @SuppressWarnings("OptionalAssignedToNull")
+  public void setMinApiReferenceLevel() {
+    assert apiReferenceLevel == null;
+    this.apiReferenceLevel = Optional.empty();
+  }
+
+  @Override
+  public void setApiReferenceLevel(AndroidApiLevel apiReferenceLevel) {
+    assert apiReferenceLevel != null;
+    this.apiReferenceLevel = Optional.of(apiReferenceLevel);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index 95d817f..1b47801 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
@@ -498,6 +498,19 @@
   }
 
   @Override
+  @SuppressWarnings("OptionalAssignedToNull")
+  public void setMinApiReferenceLevel() {
+    assert apiReferenceLevel == null;
+    this.apiReferenceLevel = Optional.empty();
+  }
+
+  @Override
+  public void setApiReferenceLevel(AndroidApiLevel apiReferenceLevel) {
+    assert apiReferenceLevel != null;
+    this.apiReferenceLevel = Optional.of(apiReferenceLevel);
+  }
+
+  @Override
   public boolean isMutableOptimizationInfo() {
     return true;
   }
@@ -512,11 +525,6 @@
     return this;
   }
 
-  public MutableMethodOptimizationInfo setApiReferenceLevel(AndroidApiLevel apiReferenceLevel) {
-    this.apiReferenceLevel = Optional.ofNullable(apiReferenceLevel);
-    return this;
-  }
-
   public MutableMethodOptimizationInfo mutableCopy() {
     return new MutableMethodOptimizationInfo(this);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableOptimizationInfo.java
index bf34966..db7bd1d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableOptimizationInfo.java
@@ -4,4 +4,11 @@
 
 package com.android.tools.r8.ir.optimize.info;
 
-public interface MutableOptimizationInfo {}
+import com.android.tools.r8.utils.AndroidApiLevel;
+
+public interface MutableOptimizationInfo {
+
+  void setMinApiReferenceLevel();
+
+  void setApiReferenceLevel(AndroidApiLevel apiReferenceLevel);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index b809e23..91b334c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -34,6 +34,7 @@
 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.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
 import com.android.tools.r8.ir.optimize.AssumeInserter;
@@ -410,6 +411,7 @@
             ClassInitializerDefaultsResult.empty(),
             feedback,
             methodProcessor,
+            new MutableMethodConversionOptions(methodProcessor),
             Timing.empty());
   }
 
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 0206e81..74d7686 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.graph.DexEncodedAnnotation;
 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.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
@@ -29,7 +30,6 @@
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
-import com.android.tools.r8.graph.DexValue.DexValueEnum;
 import com.android.tools.r8.graph.DexValue.DexValueInt;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.GraphLens;
@@ -522,9 +522,11 @@
         break;
 
       case ENUM:
-        DexValueEnum en = value.asDexValueEnum();
+        DexField enumField = value.asDexValueEnum().getValue();
         visitor.visitEnum(
-            name, namingLens.lookupDescriptor(en.value.type).toString(), en.value.name.toString());
+            name,
+            namingLens.lookupDescriptor(enumField.getType()).toString(),
+            namingLens.lookupName(enumField).toString());
         break;
 
       case FIELD:
diff --git a/src/main/java/com/android/tools/r8/references/Reference.java b/src/main/java/com/android/tools/r8/references/Reference.java
index 795c3bb..e6a829b 100644
--- a/src/main/java/com/android/tools/r8/references/Reference.java
+++ b/src/main/java/com/android/tools/r8/references/Reference.java
@@ -161,6 +161,22 @@
         returnTypeDescriptor.equals("V") ? null : typeFromDescriptor(returnTypeDescriptor));
   }
 
+  /** Get a method reference from class reference, method name and signature. */
+  public static MethodReference methodFromDescriptor(
+      ClassReference classReference, String methodName, String methodDescriptor) {
+    ImmutableList.Builder<TypeReference> builder = ImmutableList.builder();
+    for (String parameterTypeDescriptor :
+        DescriptorUtils.getArgumentTypeDescriptors(methodDescriptor)) {
+      builder.add(typeFromDescriptor(parameterTypeDescriptor));
+    }
+    String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(methodDescriptor);
+    return method(
+        classReference,
+        methodName,
+        builder.build(),
+        returnTypeDescriptor.equals("V") ? null : typeFromDescriptor(returnTypeDescriptor));
+  }
+
   public static MethodReference classConstructor(ClassReference type) {
     return method(type, "<clinit>", Collections.emptyList(), null);
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java b/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java
index 4fdad6a..fedb75d 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java
@@ -40,10 +40,8 @@
   }
 
   private void processMethod(DexEncodedMethod method) {
-    method.setAnnotations(method.annotations().rewrite(this::rewriteAnnotation));
-    method.parameterAnnotationsList =
-        method.parameterAnnotationsList.rewrite(
-            dexAnnotationSet -> dexAnnotationSet.rewrite(this::rewriteAnnotation));
+    method.rewriteAllAnnotations(
+        (annotation, isParameterAnnotation) -> rewriteAnnotation(annotation));
   }
 
   private void processField(DexEncodedField field) {
@@ -73,7 +71,7 @@
     if (value.isDexValueArray()) {
       DexValue[] originalValues = value.asDexValueArray().getValues();
       DexValue[] rewrittenValues =
-          ArrayUtils.map(DexValue[].class, originalValues, this::rewriteComplexValue);
+          ArrayUtils.map(originalValues, this::rewriteComplexValue, DexValue.EMPTY_ARRAY);
       if (rewrittenValues != originalValues) {
         return new DexValueArray(rewrittenValues);
       }
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 567b7cb..ac1ad75 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -6,24 +6,28 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
-import com.android.tools.r8.graph.DexEncodedMember;
-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.DexType;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.graph.ProgramDefinition;
+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.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 
 public class AnnotationRemover {
 
@@ -49,49 +53,50 @@
   }
 
   /** Used to filter annotations on classes, methods and fields. */
-  private boolean filterAnnotations(DexDefinition holder, DexAnnotation annotation) {
+  private boolean filterAnnotations(
+      ProgramDefinition holder, DexAnnotation annotation, AnnotatedKind kind) {
     return annotationsToRetain.contains(annotation)
-        || shouldKeepAnnotation(appView, holder, annotation, isAnnotationTypeLive(annotation));
-  }
-
-  public static boolean shouldKeepAnnotation(
-      AppView<AppInfoWithLiveness> appView, DexDefinition holder, DexAnnotation annotation) {
-    return shouldKeepAnnotation(
-        appView, holder, annotation, isAnnotationTypeLive(annotation, appView));
+        || shouldKeepAnnotation(
+            appView, holder, annotation, isAnnotationTypeLive(annotation), kind);
   }
 
   public static boolean shouldKeepAnnotation(
       AppView<?> appView,
-      DexDefinition holder,
+      ProgramDefinition holder,
       DexAnnotation annotation,
-      boolean isAnnotationTypeLive) {
+      boolean isAnnotationTypeLive,
+      AnnotatedKind kind) {
     // If we cannot run the AnnotationRemover we are keeping the annotation.
     if (!appView.options().isShrinking()) {
       return true;
     }
+
+    InternalOptions options = appView.options();
     ProguardKeepAttributes config =
-        appView.options().getProguardConfiguration() != null
-            ? appView.options().getProguardConfiguration().getKeepAttributes()
+        options.getProguardConfiguration() != null
+            ? options.getProguardConfiguration().getKeepAttributes()
             : ProguardKeepAttributes.fromPatterns(ImmutableList.of());
 
     DexItemFactory dexItemFactory = appView.dexItemFactory();
-
     switch (annotation.visibility) {
       case DexAnnotation.VISIBILITY_SYSTEM:
+        if (kind.isParameter()) {
+          return false;
+        }
         // InnerClass and EnclosingMember are represented in class attributes, not annotations.
         assert !DexAnnotation.isInnerClassAnnotation(annotation, dexItemFactory);
         assert !DexAnnotation.isMemberClassesAnnotation(annotation, dexItemFactory);
         assert !DexAnnotation.isEnclosingMethodAnnotation(annotation, dexItemFactory);
         assert !DexAnnotation.isEnclosingClassAnnotation(annotation, dexItemFactory);
-        assert appView.options().passthroughDexCode
+        assert options.passthroughDexCode
             || !DexAnnotation.isSignatureAnnotation(annotation, dexItemFactory);
         if (config.exceptions && DexAnnotation.isThrowingAnnotation(annotation, dexItemFactory)) {
           return true;
         }
         if (DexAnnotation.isSourceDebugExtension(annotation, dexItemFactory)) {
-          assert holder.isDexClass();
+          assert holder.isProgramClass();
           appView.setSourceDebugExtensionForType(
-              holder.asDexClass(), annotation.annotation.elements[0].value.asDexValueString());
+              holder.asProgramClass(), annotation.annotation.elements[0].value.asDexValueString());
           return config.sourceDebugExtension;
         }
         if (config.methodParameters
@@ -106,14 +111,35 @@
         return false;
 
       case DexAnnotation.VISIBILITY_RUNTIME:
-        if (!config.runtimeVisibleAnnotations) {
-          return false;
+        // We always keep the @java.lang.Retention annotation on annotation classes, since the
+        // removal of this annotation may change the annotation from being runtime visible to
+        // runtime invisible.
+        if (holder.isProgramClass()
+            && holder.asProgramClass().isAnnotation()
+            && DexAnnotation.isJavaLangRetentionAnnotation(annotation, dexItemFactory)) {
+          return true;
+        }
+
+        if (kind.isParameter()) {
+          if (!options.isKeepRuntimeVisibleParameterAnnotationsEnabled()) {
+            return false;
+          }
+        } else {
+          if (!options.isKeepRuntimeVisibleAnnotationsEnabled()) {
+            return false;
+          }
         }
         return isAnnotationTypeLive;
 
       case DexAnnotation.VISIBILITY_BUILD:
-        if (!config.runtimeInvisibleAnnotations) {
-          return false;
+        if (kind.isParameter()) {
+          if (!options.isKeepRuntimeInvisibleParameterAnnotationsEnabled()) {
+            return false;
+          }
+        } else {
+          if (!options.isKeepRuntimeInvisibleAnnotationsEnabled()) {
+            return false;
+          }
         }
         return isAnnotationTypeLive;
 
@@ -132,60 +158,35 @@
     return appView.appInfo().isNonProgramTypeOrLiveProgramType(annotationType);
   }
 
-  /**
-   * Used to filter annotations on parameters.
-   */
-  private boolean filterParameterAnnotations(DexAnnotation annotation) {
-    if (annotationsToRetain.contains(annotation)) {
-      return true;
-    }
-    switch (annotation.visibility) {
-      case DexAnnotation.VISIBILITY_SYSTEM:
-        return false;
-      case DexAnnotation.VISIBILITY_RUNTIME:
-        if (!keep.runtimeVisibleParameterAnnotations) {
-          return false;
-        }
-        break;
-      case DexAnnotation.VISIBILITY_BUILD:
-        if (!keep.runtimeInvisibleParameterAnnotations) {
-          return false;
-        }
-        break;
-      default:
-        throw new Unreachable("Unexpected annotation visibility.");
-    }
-    return isAnnotationTypeLive(annotation);
-  }
-
   public AnnotationRemover ensureValid() {
     keep.ensureValid(appView.options().forceProguardCompatibility);
     return this;
   }
 
-  public void run() {
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      stripAttributes(clazz);
-      clazz.setAnnotations(
-          clazz.annotations().rewrite(annotation -> rewriteAnnotation(clazz, annotation)));
-      // Kotlin metadata for classes are removed in the KotlinMetadataEnqueuerExtension. Kotlin
-      // properties are split over fields and methods. Check if any is pinned before pruning the
-      // information.
-      Set<KotlinPropertyInfo> pinnedKotlinProperties = Sets.newIdentityHashSet();
-      clazz.forEachProgramMember(
-          member -> processMember(member.getDefinition(), clazz, pinnedKotlinProperties));
-      clazz.forEachProgramMember(
-          member -> {
-            KotlinMemberLevelInfo kotlinInfo = member.getKotlinInfo();
-            if (kotlinInfo.isProperty()
-                && !pinnedKotlinProperties.contains(kotlinInfo.asProperty())) {
-              member.clearKotlinInfo();
-            }
-          });
-    }
+  public void run(ExecutorService executorService) throws ExecutionException {
+    ThreadUtils.processItems(appView.appInfo().classes(), this::run, executorService);
     assert verifyNoKeptKotlinMembersForClassesWithNoKotlinInfo();
   }
 
+  private void run(DexProgramClass clazz) {
+    KeepClassInfo keepInfo = appView.getKeepInfo().getClassInfo(clazz);
+    removeAnnotations(clazz, keepInfo);
+    stripAttributes(clazz, keepInfo);
+    // Kotlin metadata for classes are removed in the KotlinMetadataEnqueuerExtension. Kotlin
+    // properties are split over fields and methods. Check if any is pinned before pruning the
+    // information.
+    Set<KotlinPropertyInfo> pinnedKotlinProperties = Sets.newIdentityHashSet();
+    clazz.forEachProgramMember(member -> processMember(member, clazz, pinnedKotlinProperties));
+    clazz.forEachProgramMember(
+        member -> {
+          KotlinMemberLevelInfo kotlinInfo = member.getKotlinInfo();
+          if (kotlinInfo.isProperty()
+              && !pinnedKotlinProperties.contains(kotlinInfo.asProperty())) {
+            member.clearKotlinInfo();
+          }
+        });
+  }
+
   private boolean verifyNoKeptKotlinMembersForClassesWithNoKotlinInfo() {
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       if (clazz.getKotlinInfo().isNoKotlinInformation()) {
@@ -200,17 +201,11 @@
   }
 
   private void processMember(
-      DexEncodedMember<?, ?> member,
+      ProgramMember<?, ?> member,
       DexProgramClass clazz,
       Set<KotlinPropertyInfo> pinnedKotlinProperties) {
-    member.setAnnotations(
-        member.annotations().rewrite(annotation -> rewriteAnnotation(member, annotation)));
-    if (member.isDexEncodedMethod()) {
-      DexEncodedMethod method = member.asDexEncodedMethod();
-      method.parameterAnnotationsList =
-          method.parameterAnnotationsList.keepIf(this::filterParameterAnnotations);
-    }
-    KeepMemberInfo<?, ?> memberInfo = appView.getKeepInfo().getMemberInfo(member, clazz);
+    KeepMemberInfo<?, ?> memberInfo = appView.getKeepInfo().getMemberInfo(member);
+    removeAnnotations(member, memberInfo);
     if (memberInfo.isSignatureAttributeRemovalAllowed(options)) {
       member.clearGenericSignature();
     }
@@ -223,9 +218,10 @@
     }
   }
 
-  private DexAnnotation rewriteAnnotation(DexDefinition holder, DexAnnotation original) {
+  private DexAnnotation rewriteAnnotation(
+      ProgramDefinition holder, DexAnnotation original, AnnotatedKind kind) {
     // Check if we should keep this annotation first.
-    if (filterAnnotations(holder, original)) {
+    if (filterAnnotations(holder, original, kind)) {
       // Then, filter out values that refer to dead definitions.
       return original.rewrite(this::rewriteEncodedAnnotation);
     }
@@ -265,12 +261,28 @@
     return liveGetter ? original : null;
   }
 
-  private void stripAttributes(DexProgramClass clazz) {
+  private void removeAnnotations(ProgramDefinition definition, KeepInfo<?, ?> keepInfo) {
+    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();
+    } else {
+      definition.rewriteAllAnnotations(
+          (annotation, isParameterAnnotation) ->
+              DexAnnotation.isJavaLangRetentionAnnotation(annotation, appView.dexItemFactory())
+                  ? annotation
+                  : null);
+    }
+  }
+
+  private void stripAttributes(DexProgramClass clazz, KeepClassInfo keepInfo) {
     // If [clazz] is mentioned by a keep rule, it could be used for reflection, and we therefore
     // need to keep the enclosing method and inner classes attributes, if requested. In Proguard
     // compatibility mode we keep these attributes independent of whether the given class is kept.
     // In full mode we remove the attribute if not both sides are kept.
-    KeepClassInfo keepInfo = appView.getKeepInfo().getClassInfo(clazz);
     clazz.removeEnclosingMethodAttribute(
         enclosingMethodAttribute ->
             keepInfo.isEnclosingMethodAttributeRemovalAllowed(
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index cb49580..da86fe5 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -91,46 +91,55 @@
 
   @Override
   public void registerInstanceFieldRead(DexField field) {
+    setMaxApiReferenceLevel(field);
     enqueuer.traceInstanceFieldRead(field, context);
   }
 
   @Override
   public void registerInstanceFieldReadFromMethodHandle(DexField field) {
+    setMaxApiReferenceLevel(field);
     enqueuer.traceInstanceFieldReadFromMethodHandle(field, context);
   }
 
   @Override
   public void registerInstanceFieldWrite(DexField field) {
+    setMaxApiReferenceLevel(field);
     enqueuer.traceInstanceFieldWrite(field, context);
   }
 
   @Override
   public void registerInstanceFieldWriteFromMethodHandle(DexField field) {
+    setMaxApiReferenceLevel(field);
     enqueuer.traceInstanceFieldWriteFromMethodHandle(field, context);
   }
 
   @Override
   public void registerNewInstance(DexType type) {
+    setMaxApiReferenceLevel(type);
     enqueuer.traceNewInstance(type, context);
   }
 
   @Override
   public void registerStaticFieldRead(DexField field) {
+    setMaxApiReferenceLevel(field);
     enqueuer.traceStaticFieldRead(field, context);
   }
 
   @Override
   public void registerStaticFieldReadFromMethodHandle(DexField field) {
+    setMaxApiReferenceLevel(field);
     enqueuer.traceStaticFieldReadFromMethodHandle(field, context);
   }
 
   @Override
   public void registerStaticFieldWrite(DexField field) {
+    setMaxApiReferenceLevel(field);
     enqueuer.traceStaticFieldWrite(field, context);
   }
 
   @Override
   public void registerStaticFieldWriteFromMethodHandle(DexField field) {
+    setMaxApiReferenceLevel(field);
     enqueuer.traceStaticFieldWriteFromMethodHandle(field, context);
   }
 
@@ -146,6 +155,11 @@
   }
 
   @Override
+  public void registerSafeCheckCast(DexType type) {
+    enqueuer.traceSafeCheckCast(type, context);
+  }
+
+  @Override
   public void registerTypeReference(DexType type) {
     enqueuer.traceTypeReference(type, context);
   }
@@ -172,10 +186,16 @@
     enqueuer.traceCallSite(callSite, context);
   }
 
-  private void setMaxApiReferenceLevel(DexMethod invokedMethod) {
+  private void setMaxApiReferenceLevel(DexReference reference) {
+    if (reference.isDexMember()) {
+      this.maxApiReferenceLevel =
+          maxApiReferenceLevel.max(
+              reference
+                  .asDexMember()
+                  .computeApiLevelForReferencedTypes(appView, apiReferenceMapping));
+    }
     this.maxApiReferenceLevel =
-        maxApiReferenceLevel.max(
-            apiReferenceMapping.getOrDefault(invokedMethod, maxApiReferenceLevel));
+        maxApiReferenceLevel.max(apiReferenceMapping.getOrDefault(reference, maxApiReferenceLevel));
   }
 
   public AndroidApiLevel getMaxApiReferenceLevel() {
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 743fa3c..75bb5f5 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -8,7 +8,6 @@
 import static com.android.tools.r8.ir.desugar.LambdaDescriptor.isLambdaMetafactoryMethod;
 import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.emulateInterfaceLibraryMethod;
 import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.getEmulateLibraryInterfaceClassType;
-import static com.android.tools.r8.ir.optimize.enums.UnboxedEnumMemberRelocator.ENUM_UNBOXING_UTILITY_CLASS_SUFFIX;
 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;
@@ -31,6 +30,7 @@
 import com.android.tools.r8.graph.ClasspathOrLibraryClass;
 import com.android.tools.r8.graph.ClasspathOrLibraryDefinition;
 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.DexApplication;
 import com.android.tools.r8.graph.DexCallSite;
@@ -42,7 +42,6 @@
 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.DexItem;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexItemFactory.ClassMethods;
 import com.android.tools.r8.graph.DexLibraryClass;
@@ -80,6 +79,7 @@
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
+import com.android.tools.r8.graph.analysis.ApiModelAnalysis;
 import com.android.tools.r8.graph.analysis.DesugaredLibraryConversionWrapperAnalysis;
 import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
 import com.android.tools.r8.graph.analysis.EnqueuerCheckCastAnalysis;
@@ -102,8 +102,6 @@
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.LambdaClass;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
-import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationWithMinApiInfo;
 import com.android.tools.r8.kotlin.KotlinMetadataEnqueuerExtension;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
@@ -171,11 +169,11 @@
 
 /**
  * Approximates the runtime dependencies for the given set of roots.
- * <p>
+ *
  * <p>The implementation filters the static call-graph with liveness information on classes to
  * remove virtual methods that are reachable by their static type but are unreachable at runtime as
  * they are not visible from any instance.
- * <p>
+ *
  * <p>As result of the analysis, an instance of {@link AppInfoWithLiveness} is returned. See the
  * field descriptions for details.
  */
@@ -395,6 +393,13 @@
   private final Map<DexType, Map<DexAnnotation, List<ProgramDefinition>>> deferredAnnotations =
       new IdentityHashMap<>();
 
+  /**
+   * A map from annotation classes to parameter annotations that need to be processed should the
+   * classes ever become live.
+   */
+  private final Map<DexType, Map<DexAnnotation, List<ProgramDefinition>>>
+      deferredParameterAnnotations = new IdentityHashMap<>();
+
   /** Map of active if rules to speed up aapt2 generated keep rules. */
   private Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> activeIfRules;
 
@@ -470,14 +475,7 @@
     }
     referenceToApiLevelMap = new IdentityHashMap<>();
     if (options.apiModelingOptions().enableApiCallerIdentification) {
-      options
-          .apiModelingOptions()
-          .methodApiMapping
-          .forEach(
-              (methodReference, apiLevel) -> {
-                referenceToApiLevelMap.put(
-                    options.dexItemFactory().createMethod(methodReference), apiLevel);
-              });
+      options.apiModelingOptions().appendToApiLevelMap(referenceToApiLevelMap, dexItemFactory);
     }
   }
 
@@ -1040,6 +1038,11 @@
     traceConstClassOrCheckCast(type, currentMethod);
   }
 
+  void traceSafeCheckCast(DexType type, ProgramMethod currentMethod) {
+    checkCastAnalyses.forEach(analysis -> analysis.traceSafeCheckCast(type, currentMethod));
+    traceCompilerSynthesizedConstClassOrCheckCast(type, currentMethod);
+  }
+
   void traceConstClass(
       DexType type,
       ProgramMethod currentMethod,
@@ -1100,8 +1103,21 @@
   }
 
   private void traceConstClassOrCheckCast(DexType type, ProgramMethod currentMethod) {
+    internalTraceConstClassOrCheckCast(type, currentMethod, false);
+  }
+
+  // TODO(b/190487539): Currently only used by traceSafeCheckCast(), but should also be used to
+  //  ensure we don't trigger compat behavior for const-class instructions synthesized for
+  //  synchronized methods.
+  private void traceCompilerSynthesizedConstClassOrCheckCast(
+      DexType type, ProgramMethod currentMethod) {
+    internalTraceConstClassOrCheckCast(type, currentMethod, true);
+  }
+
+  private void internalTraceConstClassOrCheckCast(
+      DexType type, ProgramMethod currentMethod, boolean isCompilerSynthesized) {
     traceTypeReference(type, currentMethod);
-    if (!forceProguardCompatibility) {
+    if (!forceProguardCompatibility || isCompilerSynthesized) {
       return;
     }
     DexType baseType = type.toBaseType(appView.dexItemFactory());
@@ -1688,7 +1704,7 @@
       return;
     }
     if (!type.isClassType()) {
-      // Ignore primitive types.
+      // Ignore primitive types and void.
       return;
     }
     DexProgramClass clazz = getProgramClassOrNull(type, context);
@@ -1727,7 +1743,6 @@
     assert !mode.isFinalMainDexTracing()
             || !options.testing.checkForNotExpandingMainDexTracingResult
             || appView.appInfo().getMainDexInfo().isTracedRoot(clazz, appView.getSyntheticItems())
-            || clazz.toSourceString().contains(ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)
         : "Class " + clazz.toSourceString() + " was not a main dex root in the first round";
 
     // Mark types in inner-class attributes referenced.
@@ -1819,16 +1834,9 @@
 
     // If this type has deferred annotations, we have to process those now, too.
     if (clazz.isAnnotation()) {
-      Map<DexAnnotation, List<ProgramDefinition>> annotations =
-          deferredAnnotations.remove(clazz.getType());
-      if (annotations != null) {
-        assert annotations.keySet().stream()
-            .allMatch(a -> a.getAnnotationType() == clazz.getType());
-        annotations.forEach(
-            (annotation, annotatedItems) ->
-                annotatedItems.forEach(
-                    annotatedItem -> processAnnotation(annotatedItem, annotation)));
-      }
+      processDeferredAnnotations(clazz, deferredAnnotations, AnnotatedKind::from);
+      processDeferredAnnotations(
+          clazz, deferredParameterAnnotations, annotatedItem -> AnnotatedKind.PARAMETER);
     }
 
     rootSet.forEachDependentInstanceConstructor(
@@ -1840,6 +1848,24 @@
     analyses.forEach(analysis -> analysis.processNewlyLiveClass(clazz, workList));
   }
 
+  private void processDeferredAnnotations(
+      DexProgramClass clazz,
+      Map<DexType, Map<DexAnnotation, List<ProgramDefinition>>> deferredAnnotations,
+      Function<ProgramDefinition, AnnotatedKind> kindProvider) {
+    Map<DexAnnotation, List<ProgramDefinition>> annotations =
+        deferredAnnotations.remove(clazz.getType());
+    if (annotations != null) {
+      assert annotations.keySet().stream()
+          .allMatch(annotation -> annotation.getAnnotationType() == clazz.getType());
+      annotations.forEach(
+          (annotation, annotatedItems) ->
+              annotatedItems.forEach(
+                  annotatedItem ->
+                      processAnnotation(
+                          annotatedItem, annotation, kindProvider.apply(annotatedItem))));
+    }
+  }
+
   private void ensureMethodsContinueToWidenAccess(ClassDefinition clazz) {
     assert !clazz.isProgramClass();
     ScopedDexMethodSet seen =
@@ -1922,27 +1948,35 @@
   }
 
   private void processAnnotations(ProgramDefinition annotatedItem) {
-    processAnnotations(annotatedItem, annotatedItem.getDefinition().annotations());
+    processAnnotations(
+        annotatedItem,
+        annotatedItem.getDefinition().annotations(),
+        AnnotatedKind.from(annotatedItem));
   }
 
-  private void processAnnotations(ProgramDefinition annotatedItem, DexAnnotationSet annotations) {
-    processAnnotations(annotatedItem, annotations.annotations);
+  private void processAnnotations(
+      ProgramDefinition annotatedItem, DexAnnotationSet annotations, AnnotatedKind kind) {
+    processAnnotations(annotatedItem, annotations.annotations, kind);
   }
 
-  private void processAnnotations(ProgramDefinition annotatedItem, DexAnnotation[] annotations) {
+  private void processAnnotations(
+      ProgramDefinition annotatedItem, DexAnnotation[] annotations, AnnotatedKind kind) {
     for (DexAnnotation annotation : annotations) {
-      processAnnotation(annotatedItem, annotation);
+      processAnnotation(annotatedItem, annotation, kind);
     }
   }
 
-  private void processAnnotation(ProgramDefinition annotatedItem, DexAnnotation annotation) {
+  private 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.getDefinition(), annotation, isLive)) {
+    if (!shouldKeepAnnotation(appView, annotatedItem, annotation, isLive, kind)) {
       // Remember this annotation for later.
       if (!annotationTypeIsLibraryClass) {
+        Map<DexType, Map<DexAnnotation, List<ProgramDefinition>>> deferredAnnotations =
+            kind.isParameter() ? deferredParameterAnnotations : this.deferredAnnotations;
         Map<DexAnnotation, List<ProgramDefinition>> deferredAnnotationsForAnnotationType =
             deferredAnnotations.computeIfAbsent(type, ignore -> new IdentityHashMap<>());
         deferredAnnotationsForAnnotationType
@@ -1951,11 +1985,14 @@
       }
       return;
     }
-    KeepReason reason = KeepReason.annotatedOn(annotatedItem.getDefinition());
-    graphReporter.registerAnnotation(annotation, reason);
+
+    // Report that the annotation is retained due to the annotated item.
+    graphReporter.registerAnnotation(annotation, annotatedItem);
+
+    // Report that the items referenced from inside the annotation are retained due to the
+    // annotation.
     AnnotationReferenceMarker referenceMarker =
-        new AnnotationReferenceMarker(
-            annotation.getAnnotationType(), annotatedItem, appView.dexItemFactory(), reason);
+        new AnnotationReferenceMarker(annotation, annotatedItem);
     annotation.annotation.collectIndexedItems(referenceMarker);
   }
 
@@ -2990,6 +3027,9 @@
         && appView.options().getProguardConfiguration().getKeepAttributes().signature) {
       registerAnalysis(new GenericSignatureEnqueuerAnalysis(enqueuerDefinitionSupplier));
     }
+    if (appView.options().apiModelingOptions().enableApiCallerIdentification) {
+      registerAnalysis(new ApiModelAnalysis(appView, referenceToApiLevelMap));
+    }
     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
@@ -3870,8 +3910,6 @@
     // Add all dependent members to the workqueue.
     enqueueRootItems(rootSet.getDependentItems(definition));
 
-    checkDefinitionForSoftPinning(method);
-
     // Notify analyses.
     analyses.forEach(analysis -> analysis.processNewlyLiveMethod(method, context));
   }
@@ -3896,12 +3934,14 @@
   }
 
   void traceMethodDefinitionExcludingCode(ProgramMethod method) {
+    checkDefinitionForSoftPinning(method);
     markReferencedTypesAsLive(method);
     processAnnotations(method);
     method
         .getDefinition()
         .getParameterAnnotations()
-        .forEachAnnotation(annotation -> processAnnotation(method, annotation));
+        .forEachAnnotation(
+            annotation -> processAnnotation(method, annotation, AnnotatedKind.PARAMETER));
   }
 
   private void traceNonDesugaredCode(ProgramMethod method) {
@@ -3917,19 +3957,8 @@
     DefaultEnqueuerUseRegistry registry =
         useRegistryFactory.create(appView, method, this, referenceToApiLevelMap);
     method.registerCodeReferences(registry);
-    DexEncodedMethod methodDefinition = method.getDefinition();
-    AndroidApiLevel maxApiReferenceLevel = registry.getMaxApiReferenceLevel();
-    assert maxApiReferenceLevel.isGreaterThanOrEqualTo(options.minApiLevel);
-    // To not have mutable update information for all methods that all has min api level we
-    // swap the default optimization info for one with that marks the api level to be min api.
-    if (methodDefinition.getOptimizationInfo() == DefaultMethodOptimizationInfo.getInstance()
-        && maxApiReferenceLevel == options.minApiLevel) {
-      methodDefinition.setMinApiOptimizationInfo(
-          DefaultMethodOptimizationWithMinApiInfo.getInstance());
-      return;
-    }
-    methodDefinition.setOptimizationInfo(
-        methodDefinition.getMutableOptimizationInfo().setApiReferenceLevel(maxApiReferenceLevel));
+    // Notify analyses.
+    analyses.forEach(analysis -> analysis.processTracedCode(method, registry));
   }
 
   private void checkDefinitionForSoftPinning(ProgramDefinition definition) {
@@ -4490,20 +4519,12 @@
 
   private class AnnotationReferenceMarker implements IndexedItemCollection {
 
-    private final DexItem annotationHolder;
     private final ProgramDefinition context;
-    private final DexItemFactory dexItemFactory;
     private final KeepReason reason;
 
-    private AnnotationReferenceMarker(
-        DexItem annotationHolder,
-        ProgramDefinition context,
-        DexItemFactory dexItemFactory,
-        KeepReason reason) {
-      this.annotationHolder = annotationHolder;
+    private AnnotationReferenceMarker(DexAnnotation annotation, ProgramDefinition context) {
       this.context = context;
-      this.dexItemFactory = dexItemFactory;
-      this.reason = reason;
+      this.reason = KeepReason.referencedInAnnotation(annotation, context);
     }
 
     @Override
@@ -4533,17 +4554,15 @@
                 : fieldAccessInfoCollection.extend(
                     fieldReference, new FieldAccessInfoImpl(fieldReference));
         fieldAccessInfo.setReadFromAnnotation();
-        markFieldAsLive(field, context, KeepReason.referencedInAnnotation(annotationHolder));
+        markFieldAsLive(field, context, reason);
         // When an annotation has a field of an enum type the JVM will use the values() method on
         // that enum class if the field is referenced.
         if (options.isGeneratingClassFiles() && field.getHolder().isEnum()) {
-          markEnumValuesAsReachable(
-              field.getHolder(), KeepReason.referencedInAnnotation(annotationHolder));
+          markEnumValuesAsReachable(field.getHolder(), reason);
         }
       } else {
         // There is no dispatch on annotations, so only keep what is directly referenced.
-        workList.enqueueMarkFieldAsReachableAction(
-            field, context, KeepReason.referencedInAnnotation(annotationHolder));
+        workList.enqueueMarkFieldAsReachableAction(field, context, reason);
       }
       return false;
     }
@@ -4560,17 +4579,13 @@
       if (target != null) {
         // There is no dispatch on annotations, so only keep what is directly referenced.
         if (target.getReference() == method) {
-          markDirectStaticOrConstructorMethodAsLive(
-              new ProgramMethod(holder, target),
-              KeepReason.referencedInAnnotation(annotationHolder));
+          markDirectStaticOrConstructorMethodAsLive(new ProgramMethod(holder, target), reason);
         }
       } else {
         target = holder.lookupVirtualMethod(method);
         // There is no dispatch on annotations, so only keep what is directly referenced.
         if (target != null && target.getReference() == method) {
-          markMethodAsTargeted(
-              new ProgramMethod(holder, target),
-              KeepReason.referencedInAnnotation(annotationHolder));
+          markMethodAsTargeted(new ProgramMethod(holder, target), reason);
         }
       }
       return false;
@@ -4598,11 +4613,7 @@
 
     @Override
     public boolean addType(DexType type) {
-      // Annotations can also contain the void type, which is not a class type, so filter it out
-      // here.
-      if (type != dexItemFactory.voidType) {
-        markTypeAsLive(type, context, reason);
-      }
+      markTypeAsLive(type, context, reason);
       return false;
     }
   }
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 07f625d..e509230 100644
--- a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
@@ -22,5 +22,11 @@
 
   boolean isKeepInnerClassesAttributeEnabled();
 
+  boolean isKeepRuntimeInvisibleAnnotationsEnabled();
+
+  boolean isKeepRuntimeInvisibleParameterAnnotationsEnabled();
+
   boolean isKeepRuntimeVisibleAnnotationsEnabled();
+
+  boolean isKeepRuntimeVisibleParameterAnnotationsEnabled();
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
index 963e036..6146c2c 100644
--- a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.graphinfo.AnnotationGraphNode;
 import com.android.tools.r8.experimental.graphinfo.ClassGraphNode;
@@ -21,7 +20,6 @@
 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.DexItem;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
@@ -51,7 +49,7 @@
   private final CollectingGraphConsumer verificationGraphConsumer;
 
   // Canonicalization of external graph-nodes and edge info.
-  private final Map<DexItem, AnnotationGraphNode> annotationNodes = new IdentityHashMap<>();
+  private final Map<DexAnnotation, AnnotationGraphNode> annotationNodes = new IdentityHashMap<>();
   private final Map<DexType, ClassGraphNode> classNodes = new IdentityHashMap<>();
   private final Map<DexMethod, MethodGraphNode> methodNodes = new IdentityHashMap<>();
   private final Map<DexField, FieldGraphNode> fieldNodes = new IdentityHashMap<>();
@@ -359,11 +357,13 @@
     return registerEdge(getClassGraphNode(clazz.type), reason);
   }
 
-  public KeepReasonWitness registerAnnotation(DexAnnotation annotation, KeepReason reason) {
+  public KeepReasonWitness registerAnnotation(
+      DexAnnotation annotation, ProgramDefinition annotatedItem) {
+    KeepReason reason = KeepReason.annotatedOn(annotatedItem.getDefinition());
     if (skipReporting(reason)) {
       return KeepReasonWitness.INSTANCE;
     }
-    return registerEdge(getAnnotationGraphNode(annotation.annotation.type), reason);
+    return registerEdge(getAnnotationGraphNode(annotation, annotatedItem), reason);
   }
 
   public KeepReasonWitness registerMethod(DexEncodedMethod method, KeepReason reason) {
@@ -432,15 +432,18 @@
     return appView.appInfo().definitionForWithoutExistenceAssert(type);
   }
 
-  AnnotationGraphNode getAnnotationGraphNode(DexItem type) {
+  AnnotationGraphNode getAnnotationGraphNode(
+      DexAnnotation annotation, ProgramDefinition annotatedItem) {
     return annotationNodes.computeIfAbsent(
-        type,
-        t -> {
-          if (t instanceof DexType) {
-            return new AnnotationGraphNode(getClassGraphNode(((DexType) t)));
-          }
-          throw new Unimplemented(
-              "Incomplete support for annotation node on item: " + type.getClass());
+        annotation,
+        key -> {
+          GraphNode annotatedNode =
+              annotatedItem
+                  .getReference()
+                  .apply(
+                      this::getClassGraphNode, this::getFieldGraphNode, this::getMethodGraphNode);
+          ClassGraphNode annotationClassNode = getClassGraphNode(annotation.getAnnotationType());
+          return new AnnotationGraphNode(annotatedNode, annotationClassNode);
         });
   }
 
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 7e9b166..ddd5bfc 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.ProgramDefinition;
 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.shaking.KeepFieldInfo.Joiner;
 import com.android.tools.r8.utils.InternalOptions;
@@ -86,6 +87,10 @@
     return clazz == null ? keepInfoForNonProgramClass() : getClassInfo(clazz);
   }
 
+  public final KeepMemberInfo<?, ?> getMemberInfo(ProgramMember<?, ?> member) {
+    return getMemberInfo(member.getDefinition(), member.getHolder());
+  }
+
   public final KeepMethodInfo getMethodInfo(ProgramMethod method) {
     return getMethodInfo(method.getDefinition(), method.getHolder());
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index 5e6b3fd..2493dd2 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -6,12 +6,13 @@
 import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
 import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo.EdgeKind;
 import com.android.tools.r8.experimental.graphinfo.GraphNode;
+import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItem;
 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.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
 
 // TODO(herhut): Canonicalize reason objects.
@@ -61,8 +62,9 @@
     return new ReferencedFrom(method.getDefinition());
   }
 
-  public static KeepReason referencedInAnnotation(DexItem holder) {
-    return new ReferencedInAnnotation(holder);
+  public static KeepReason referencedInAnnotation(
+      DexAnnotation annotation, ProgramDefinition annotatedItem) {
+    return new ReferencedInAnnotation(annotation, annotatedItem);
   }
 
   public boolean isDueToKeepRule() {
@@ -229,10 +231,12 @@
 
   private static class ReferencedInAnnotation extends KeepReason {
 
-    private final DexItem holder;
+    private final DexAnnotation annotation;
+    private final ProgramDefinition annotatedItem;
 
-    private ReferencedInAnnotation(DexItem holder) {
-      this.holder = holder;
+    private ReferencedInAnnotation(DexAnnotation annotation, ProgramDefinition annotatedItem) {
+      this.annotation = annotation;
+      this.annotatedItem = annotatedItem;
     }
 
     @Override
@@ -242,7 +246,7 @@
 
     @Override
     public GraphNode getSourceNode(GraphReporter graphReporter) {
-      return graphReporter.getAnnotationGraphNode(holder);
+      return graphReporter.getAnnotationGraphNode(annotation, annotatedItem);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java b/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
index fc2756a..67f1c75 100644
--- a/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
@@ -61,6 +61,11 @@
     }
 
     @Override
+    public void traceSafeCheckCast(DexType type, ProgramMethod context) {
+      // Intentionally empty.
+    }
+
+    @Override
     public void traceInstanceOf(DexType type, ProgramMethod context) {
       add(type, instanceOfTypes);
     }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
index 65c510c..737ffa6 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
@@ -43,6 +43,7 @@
   private DexType superType;
   private DexTypeList interfaces = DexTypeList.empty();
   private DexString sourceFile = null;
+  private boolean useSortedMethodBacking = false;
   private List<DexEncodedField> staticFields = new ArrayList<>();
   private List<DexEncodedField> instanceFields = new ArrayList<>();
   private List<DexEncodedMethod> directMethods = new ArrayList<>();
@@ -143,6 +144,11 @@
     return self();
   }
 
+  public B setUseSortedMethodBacking(boolean useSortedMethodBacking) {
+    this.useSortedMethodBacking = useSortedMethodBacking;
+    return self();
+  }
+
   public C build() {
     int flag = isAbstract ? Constants.ACC_ABSTRACT : Constants.ACC_FINAL;
     int itfFlag = isInterface ? Constants.ACC_INTERFACE : 0;
@@ -167,27 +173,34 @@
             + 11 * (long) virtualMethods.hashCode()
             + 13 * (long) staticFields.hashCode()
             + 17 * (long) instanceFields.hashCode();
-    return getClassKind()
-        .create(
-            type,
-            originKind,
-            origin,
-            accessFlags,
-            superType,
-            interfaces,
-            sourceFile,
-            nestHost,
-            nestMembers,
-            enclosingMembers,
-            innerClasses,
-            signature,
-            DexAnnotationSet.empty(),
-            staticFields.toArray(DexEncodedField.EMPTY_ARRAY),
-            instanceFields.toArray(DexEncodedField.EMPTY_ARRAY),
-            directMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
-            virtualMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
-            factory.getSkipNameValidationForTesting(),
-            c -> checksum,
-            null);
+    C clazz =
+        getClassKind()
+            .create(
+                type,
+                originKind,
+                origin,
+                accessFlags,
+                superType,
+                interfaces,
+                sourceFile,
+                nestHost,
+                nestMembers,
+                enclosingMembers,
+                innerClasses,
+                signature,
+                DexAnnotationSet.empty(),
+                staticFields.toArray(DexEncodedField.EMPTY_ARRAY),
+                instanceFields.toArray(DexEncodedField.EMPTY_ARRAY),
+                DexEncodedMethod.EMPTY_ARRAY,
+                DexEncodedMethod.EMPTY_ARRAY,
+                factory.getSkipNameValidationForTesting(),
+                c -> checksum,
+                null);
+    if (useSortedMethodBacking) {
+      clazz.getMethodCollection().useSortedBacking();
+    }
+    clazz.setDirectMethods(directMethods.toArray(DexEncodedMethod.EMPTY_ARRAY));
+    clazz.setVirtualMethods(virtualMethods.toArray(DexEncodedMethod.EMPTY_ARRAY));
+    return clazz;
   }
 }
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 2d0970f..3bd99a2 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -541,7 +541,15 @@
       }
       assert !isSyntheticClass(type);
       DexProgramClass dexProgramClass =
-          internalCreateClass(kind, fn, outerContext, type, appView.dexItemFactory());
+          internalCreateClass(
+              kind,
+              syntheticProgramClassBuilder -> {
+                syntheticProgramClassBuilder.setUseSortedMethodBacking(true);
+                fn.accept(syntheticProgramClassBuilder);
+              },
+              outerContext,
+              type,
+              appView.dexItemFactory());
       onCreationConsumer.accept(dexProgramClass);
       return dexProgramClass;
     }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
index 6ea2f40..1e4082b 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -43,6 +43,8 @@
   S(31),
   UNKNOWN(10000);
 
+  // When updating LATEST and a new version goes stable, add a new api-versions.xml to third_party
+  // and update the version and generated jar in AndroidApiDatabaseBuilderGeneratorTest.
   public static final AndroidApiLevel LATEST = S;
 
   public static final int magicApiLevelUsedByAndroidPlatformBuild = 10000;
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index 4fc6914..9a863be 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -80,6 +80,10 @@
         clazz.cast(Array.newInstance(clazz.getComponentType(), filtered.size())));
   }
 
+  public static <T> boolean isEmpty(T[] array) {
+    return array.length == 0;
+  }
+
   public static boolean isSorted(int[] array) {
     for (int i = 0; i < array.length - 1; i++) {
       assert array[i] < array[i + 1];
@@ -94,18 +98,6 @@
   /**
    * Rewrites the input array based on the given function.
    *
-   * @param clazz target type's Class to cast
-   * @param original an array of original elements
-   * @param mapper a mapper that rewrites an original element to a new one, maybe `null`
-   * @return an array with written elements
-   */
-  public static <T> T[] map(Class<T[]> clazz, T[] original, Function<T, T> mapper) {
-    return map(original, mapper, clazz.cast(Array.newInstance(clazz.getComponentType(), 0)));
-  }
-
-  /**
-   * Rewrites the input array based on the given function.
-   *
    * @param original an array of original elements
    * @param mapper a mapper that rewrites an original element to a new one, maybe `null`
    * @param emptyArray an empty array
@@ -179,4 +171,8 @@
     newArray[ts.length] = element;
     return newArray;
   }
+
+  public static <T> T first(T[] ts) {
+    return ts[0];
+  }
 }
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 f96e8ce..b75ac5a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -37,6 +37,7 @@
 import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
@@ -617,10 +618,25 @@
   }
 
   @Override
+  public boolean isKeepRuntimeInvisibleAnnotationsEnabled() {
+    return proguardConfiguration.getKeepAttributes().runtimeInvisibleAnnotations;
+  }
+
+  @Override
+  public boolean isKeepRuntimeInvisibleParameterAnnotationsEnabled() {
+    return proguardConfiguration.getKeepAttributes().runtimeInvisibleParameterAnnotations;
+  }
+
+  @Override
   public boolean isKeepRuntimeVisibleAnnotationsEnabled() {
     return proguardConfiguration.getKeepAttributes().runtimeVisibleAnnotations;
   }
 
+  @Override
+  public boolean isKeepRuntimeVisibleParameterAnnotationsEnabled() {
+    return proguardConfiguration.getKeepAttributes().runtimeVisibleParameterAnnotations;
+  }
+
   /**
    * If any non-static class merging is enabled, information about types referred to by instanceOf
    * and check cast instructions needs to be collected.
@@ -1306,6 +1322,19 @@
     public Map<TypeReference, AndroidApiLevel> typeApiMapping = new HashMap<>();
 
     public boolean enableApiCallerIdentification = false;
+
+    public void appendToApiLevelMap(
+        Map<DexReference, AndroidApiLevel> apiLevelMap, DexItemFactory factory) {
+      methodApiMapping.forEach(
+          (methodReference, apiLevel) ->
+              apiLevelMap.put(factory.createMethod(methodReference), apiLevel));
+      fieldApiMapping.forEach(
+          (fieldReference, apiLevel) ->
+              apiLevelMap.put(factory.createField(fieldReference), apiLevel));
+      typeApiMapping.forEach(
+          (typeReference, apiLevel) ->
+              apiLevelMap.put(factory.createType(typeReference.getDescriptor()), apiLevel));
+    }
   }
 
   public static class ProtoShrinkingOptions {
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 98ea2c4..070e63f 100644
--- a/src/main/java/com/android/tools/r8/utils/SetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -25,6 +25,12 @@
     return result;
   }
 
+  public static <T> Set<T> newIdentityHashSet(ForEachable<T> forEachable) {
+    Set<T> result = Sets.newIdentityHashSet();
+    forEachable.forEach(result::add);
+    return result;
+  }
+
   public static <T> Set<T> newIdentityHashSet(Iterable<T> c) {
     Set<T> result = Sets.newIdentityHashSet();
     c.forEach(result::add);
diff --git a/src/main/java/com/android/tools/r8/utils/SupplierUtils.java b/src/main/java/com/android/tools/r8/utils/SupplierUtils.java
index 13a02cb..84dac5f 100644
--- a/src/main/java/com/android/tools/r8/utils/SupplierUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SupplierUtils.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.utils;
 
+import com.google.common.base.Suppliers;
 import java.util.function.Supplier;
 
 public class SupplierUtils {
@@ -12,4 +13,15 @@
     Box<T> box = new Box<>();
     return () -> box.computeIfAbsent(supplier);
   }
+
+  public static <T, E extends Throwable> Supplier<T> memoize(ThrowingSupplier<T, E> supplier) {
+    return Suppliers.memoize(
+        () -> {
+          try {
+            return supplier.get();
+          } catch (Throwable e) {
+            throw new RuntimeException(e);
+          }
+        });
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java b/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
index 294a210..df59f20 100644
--- a/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
+++ b/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.utils;
 
 /** Two value continuation value to indicate the continuation of a loop/traversal. */
+/* This class is used for building up api class member traversals. */
 public enum TraversalContinuation {
   CONTINUE,
   BREAK;
diff --git a/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java b/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
index e9e7a58..064d98f 100644
--- a/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
+++ b/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
@@ -7,6 +7,7 @@
 import java.lang.annotation.Annotation;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Method;
 import lambdadesugaringnplus.other.ClassWithDefaultPackagePrivate;
 import lambdadesugaringnplus.other.InterfaceWithDefaultPackagePrivate;
 
@@ -372,8 +373,15 @@
       // I don't know how to keep this method moved to the companion class
       // without the direct call.
       AnnotatedInterface.annotatedStaticMethod();
-      if (checkAnnotationValue(
-          getCompanionClassOrInterface().getMethod("annotatedStaticMethod").getAnnotations(), 4)) {
+      Method annotatedStaticMethod =
+          getCompanionClassOrInterface().getMethod("annotatedStaticMethod");
+      if (checkAnnotationValue(annotatedStaticMethod.getAnnotations(), 4)) {
+        System.out.println("Check 4: OK");
+      } else if (isR8()
+          && !isProguardCompatibilityMode()
+          && annotatedStaticMethod.getAnnotations().length == 0) {
+        // There is currently no way to retain annotations on static interface methods, since we
+        // drop -keep rules for such methods.
         System.out.println("Check 4: OK");
       } else {
         System.out.println("Check 4: NOT OK");
@@ -515,4 +523,12 @@
     B62168701.test();
     B78901754.test();
   }
+
+  public static boolean isR8() {
+    return false;
+  }
+
+  public static boolean isProguardCompatibilityMode() {
+    return false;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 1a9800a..f13a704 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -39,7 +39,7 @@
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
-import java.util.function.UnaryOperator;
+import java.util.function.Consumer;
 import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Test;
@@ -171,10 +171,10 @@
       D8Command.Builder builder = D8Command.builder();
       addClasspathReference(testJarFile, builder);
       for (String inputFile : inputFiles) {
-        builder = builder.addProgramFiles(Paths.get(inputFile));
+        builder.addProgramFiles(Paths.get(inputFile));
       }
-      for (UnaryOperator<D8Command.Builder> transformation : builderTransformations) {
-        builder = transformation.apply(builder);
+      for (Consumer<D8Command.Builder> transformation : builderTransformations) {
+        transformation.accept(builder);
       }
       if (outputPath != null) {
         builder.setOutput(outputPath, outputMode);
@@ -207,8 +207,8 @@
       for (ProgramResource dexFile : dexFiles) {
         builder.addDexProgramData(readResource(dexFile), dexFile.getOrigin());
       }
-      for (UnaryOperator<Builder> transformation : builderTransformations) {
-        builder = transformation.apply(builder);
+      for (Consumer<D8Command.Builder> transformation : builderTransformations) {
+        transformation.accept(builder);
       }
       if (outputPath != null) {
         builder.setOutput(outputPath, outputMode);
diff --git a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
index 1bf86e3..b4cc992 100644
--- a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.OffOrAuto;
 import java.nio.file.Path;
-import java.util.function.UnaryOperator;
+import java.util.function.Consumer;
 import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Test;
@@ -34,8 +34,8 @@
     @Override
     void build(Path inputFile, Path out, OutputMode mode) throws CompilationFailedException {
       D8Command.Builder builder = D8Command.builder().setOutput(out, mode);
-      for (UnaryOperator<D8Command.Builder> transformation : builderTransformations) {
-        builder = transformation.apply(builder);
+      for (Consumer<D8Command.Builder> transformation : builderTransformations) {
+        transformation.accept(builder);
       }
       builder.addLibraryFiles(
           ToolHelper.getAndroidJar(
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 608f635..a92437f 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -26,7 +26,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
-import java.util.function.UnaryOperator;
+import java.util.function.Consumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -41,16 +41,24 @@
       "-allowaccessmodification"
   );
 
-  private static final ArrayList<String> PROGUARD_OPTIONS_N_PLUS =
-      Lists.newArrayList(
-          "-keepclasseswithmembers public class * {",
-          "    public static void main(java.lang.String[]);",
-          "}",
-          "-keepclasseswithmembers interface **$AnnotatedInterface { <methods>; }",
-          "-neverinline interface **$AnnotatedInterface { static void annotatedStaticMethod(); }",
-          "-keepattributes *Annotation*",
-          "-dontobfuscate",
-          "-allowaccessmodification");
+  private static ArrayList<String> getProguardOptionsNPlus(
+      boolean enableProguardCompatibilityMode) {
+    return Lists.newArrayList(
+        "-keepclasseswithmembers public class * {",
+        "    public static void main(java.lang.String[]);",
+        "}",
+        "-keepclasseswithmembers interface **$AnnotatedInterface { <methods>; }",
+        "-neverinline interface **$AnnotatedInterface { static void annotatedStaticMethod(); }",
+        "-keepattributes *Annotation*",
+        "-dontobfuscate",
+        "-allowaccessmodification",
+        "-assumevalues class lambdadesugaringnplus.LambdasWithStaticAndDefaultMethods {",
+        "  public static boolean isR8() return true;",
+        "  public static boolean isProguardCompatibilityMode() return "
+            + enableProguardCompatibilityMode
+            + ";",
+        "}");
+  }
 
   private static Map<DexVm.Version, List<String>> alsoFailsOn =
       ImmutableMap.<DexVm.Version, List<String>>builder()
@@ -108,14 +116,14 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 16, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 10, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 2, "lambdadesugaring"))
         .run();
   }
 
@@ -147,59 +155,91 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 16, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 10, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
         .withMinApiLevel(AndroidApiLevel.N)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 2, "lambdadesugaring"))
         .run();
   }
 
   @Override
   @Test
   public void lambdaDesugaringNPlus() throws Throwable {
+    lambdaDesugaringNPlus(false);
+  }
+
+  @Test
+  public void lambdaDesugaringNPlusCompat() throws Throwable {
+    lambdaDesugaringNPlus(true);
+  }
+
+  private void lambdaDesugaringNPlus(boolean enableProguardCompatibilityMode) throws Throwable {
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
+        .withProguardCompatibilityMode(enableProguardCompatibilityMode)
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
-            b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaringnplus"))
+            b ->
+                b.addProguardConfiguration(
+                    getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 6, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
+        .withProguardCompatibilityMode(enableProguardCompatibilityMode)
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
-            b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 4, "lambdadesugaringnplus"))
+            b ->
+                b.addProguardConfiguration(
+                    getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaringnplus"))
         .run();
   }
 
   @Test
   @IgnoreIfVmOlderThan(Version.V7_0_0)
   public void lambdaDesugaringNPlusWithDefaultMethods() throws Throwable {
+    lambdaDesugaringNPlusWithDefaultMethods(false);
+  }
+
+  @Test
+  @IgnoreIfVmOlderThan(Version.V7_0_0)
+  public void lambdaDesugaringNPlusWithDefaultMethodsCompat() throws Throwable {
+    lambdaDesugaringNPlusWithDefaultMethods(true);
+  }
+
+  private void lambdaDesugaringNPlusWithDefaultMethods(boolean enableProguardCompatibilityMode)
+      throws Throwable {
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
+        .withProguardCompatibilityMode(enableProguardCompatibilityMode)
         .withMinApiLevel(AndroidApiLevel.N)
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
-            b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+            b ->
+                b.addProguardConfiguration(
+                    getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown()))
         .withDexCheck(inspector -> checkLambdaCount(inspector, 6, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
+        .withProguardCompatibilityMode(enableProguardCompatibilityMode)
         .withMinApiLevel(AndroidApiLevel.N)
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
-            b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+            b ->
+                b.addProguardConfiguration(
+                    getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown()))
         .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaringnplus"))
         .run();
   }
@@ -236,6 +276,8 @@
 
   class R8TestRunner extends TestRunner<R8TestRunner> {
 
+    private boolean enableProguardCompatibilityMode = false;
+
     R8TestRunner(String testName, String packageName, String mainClass) {
       super(testName, packageName, mainClass);
     }
@@ -253,11 +295,18 @@
               .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown()));
     }
 
+    public R8TestRunner withProguardCompatibilityMode(boolean enableProguardCompatibilityMode) {
+      this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
+      return this;
+    }
+
     @Override
     void build(Path inputFile, Path out, OutputMode mode) throws Throwable {
-      R8Command.Builder builder = R8Command.builder().setOutput(out, mode);
-      for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
-        builder = transformation.apply(builder);
+      CompatProguardCommandBuilder builder =
+          new CompatProguardCommandBuilder(enableProguardCompatibilityMode);
+      builder.setOutput(out, mode);
+      for (Consumer<R8Command.Builder> transformation : builderTransformations) {
+        transformation.accept(builder);
       }
 
       builder.addLibraryFiles(ToolHelper.getAndroidJar(
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index bd9bd27..1dd7194 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -49,7 +49,6 @@
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
-import java.util.function.UnaryOperator;
 import java.util.stream.Collectors;
 import java.util.zip.ZipFile;
 import org.junit.Assert;
@@ -74,7 +73,7 @@
 
     final List<Consumer<InternalOptions>> optionConsumers = new ArrayList<>();
     final List<Consumer<CodeInspector>> dexInspectorChecks = new ArrayList<>();
-    final List<UnaryOperator<B>> builderTransformations = new ArrayList<>();
+    final List<Consumer<B>> builderTransformations = new ArrayList<>();
 
     TestRunner(String testName, String packageName, String mainClass) {
       this.testName = testName;
@@ -131,7 +130,6 @@
             } else {
               fail("Unexpected builder type: " + builder.getClass());
             }
-            return builder;
           });
     }
 
@@ -143,7 +141,7 @@
       return withOptionConsumer(o -> o.tryWithResourcesDesugaring = behavior);
     }
 
-    C withBuilderTransformation(UnaryOperator<B> builderTransformation) {
+    C withBuilderTransformation(Consumer<B> builderTransformation) {
       builderTransformations.add(builderTransformation);
       return self();
     }
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 19c77fb..e51cf99 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1708,6 +1708,21 @@
     return DescriptorUtils.javaTypeToDescriptor(typeName(clazz));
   }
 
+  public static String methodDescriptor(Method method) {
+    return Reference.methodFromMethod(method).getMethodDescriptor();
+  }
+
+  public static String methodDescriptor(Class<?> returnType, Class<?>... parameters) {
+    StringBuilder sb = new StringBuilder();
+    sb.append("(");
+    for (Class<?> parameter : parameters) {
+      sb.append(descriptor(parameter));
+    }
+    sb.append(")");
+    sb.append(returnType == null ? "V" : descriptor(returnType));
+    return sb.toString();
+  }
+
   public static PathOrigin getOrigin(Class<?> clazz) {
     return new PathOrigin(ToolHelper.getClassFileForTestClass(clazz));
   }
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index e3570be..fddd0d4 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -62,6 +62,10 @@
     return withCfRuntimeFilter(vm -> vm == runtime);
   }
 
+  public TestParametersBuilder withSystemRuntime() {
+    return withCfRuntimeFilter(TestParametersBuilder::isSystemJdk);
+  }
+
   /** Add all available CF runtimes. */
   public TestParametersBuilder withCfRuntimes() {
     return withCfRuntimeFilter(vm -> true);
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
index 8b99ca8..a51fcf0 100644
--- a/src/test/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -159,7 +159,7 @@
         .build();
   }
 
-  public static TestRuntime getSystemRuntime() {
+  public static CfRuntime getSystemRuntime() {
     String version = System.getProperty("java.version");
     String home = System.getProperty("java.home");
     if (version == null || version.isEmpty() || home == null || home.isEmpty()) {
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index c421c83..73abcee 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -351,6 +351,14 @@
     return addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE);
   }
 
+  public T addKeepRuntimeInvisibleAnnotations() {
+    return addKeepAttributes(ProguardKeepAttributes.RUNTIME_INVISIBLE_ANNOTATIONS);
+  }
+
+  public T addKeepRuntimeInvisibleParameterAnnotations() {
+    return addKeepAttributes(ProguardKeepAttributes.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS);
+  }
+
   public T addKeepRuntimeVisibleAnnotations() {
     return addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS);
   }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 50f5346..f3108e5 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -165,6 +165,8 @@
   public static final Path D8_JAR = Paths.get(LIBS_DIR, "d8.jar");
   public static final Path R8_JAR = Paths.get(LIBS_DIR, "r8.jar");
   public static final Path R8_WITH_DEPS_JAR = Paths.get(LIBS_DIR, "r8_with_deps.jar");
+  public static final Path R8_WITHOUT_DEPS_JAR =
+      Paths.get(LIBS_DIR, "r8_no_manifest_without_deps.jar");
   public static final Path R8_WITH_RELOCATED_DEPS_JAR =
       Paths.get(LIBS_DIR, "r8_with_relocated_deps.jar");
   public static final Path R8_WITH_DEPS_11_JAR = Paths.get(LIBS_DIR, "r8_with_deps_11.jar");
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java
new file mode 100644
index 0000000..bc25cd4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java
@@ -0,0 +1,465 @@
+// 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 com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
+import static org.objectweb.asm.Opcodes.AASTORE;
+import static org.objectweb.asm.Opcodes.ACONST_NULL;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.ANEWARRAY;
+import static org.objectweb.asm.Opcodes.ARETURN;
+import static org.objectweb.asm.Opcodes.DUP;
+import static org.objectweb.asm.Opcodes.F_SAME1;
+import static org.objectweb.asm.Opcodes.IFEQ;
+import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKESTATIC;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+import static org.objectweb.asm.Opcodes.NEW;
+import static org.objectweb.asm.Opcodes.POP;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.apimodel.AndroidApiVersionsXmlParser.ParsedApiClass;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.Opcodes;
+
+public class AndroidApiDatabaseBuilderGenerator extends TestBase {
+
+  public static String generatedMainDescriptor() {
+    return descriptor(AndroidApiDatabaseBuilderTemplate.class).replace("Template", "");
+  }
+
+  public static ClassReference ANDROID_API_LEVEL =
+      Reference.classFromBinaryName("com/android/tools/r8/utils/AndroidApiLevel");
+  public static ClassReference ANDROID_API_CLASS =
+      Reference.classFromBinaryName("com/android/tools/r8/androidapi/AndroidApiClass");
+  public static ClassReference TRAVERSAL_CONTINUATION =
+      Reference.classFromBinaryName("com/android/tools/r8/utils/TraversalContinuation");
+
+  /**
+   * Generate the classes needed for looking up api level of references in the android.jar.
+   *
+   * <p>For each api class we generate a from AndroidApiDatabaseClassTemplate, extending
+   * AndroidApiClass, such that all members can be traversed. Looking up the class reference
+   * directly in one method will generate to much code and would probably be inefficient so we first
+   * do a case on the package and then do a check on the simple name.
+   *
+   * <p>We therefore create a class file for each package, based on
+   * AndroidApiDatabasePackageTemplate, that will do the dispatch to create a new
+   * AndroidApiDatabaseClass for class in the package.
+   *
+   * <p>We then have a single entry-point, AndroidApiDatabaseBuilder, based on
+   * AndroidApiDatabaseBuilderTemplate, that will do the dispatch to AndroidApiDatabasePackage based
+   * on the package name.
+   */
+  public static void generate(List<ParsedApiClass> apiClasses, BiConsumer<String, byte[]> consumer)
+      throws Exception {
+    Map<String, List<ParsedApiClass>> packageToClassesMap = new HashMap<>();
+    List<String> packages =
+        apiClasses.stream()
+            .map(
+                apiClass -> {
+                  String packageName =
+                      DescriptorUtils.getPackageNameFromDescriptor(
+                          apiClass.getClassReference().getDescriptor());
+                  packageToClassesMap
+                      .computeIfAbsent(packageName, ignoreArgument(ArrayList::new))
+                      .add(apiClass);
+                  return packageName;
+                })
+            .sorted()
+            .distinct()
+            .collect(Collectors.toList());
+
+    for (ParsedApiClass apiClass : apiClasses) {
+      consumer.accept(
+          getApiClassDescriptor(apiClass),
+          transformer(AndroidApiDatabaseClassTemplate.class)
+              .setClassDescriptor(getApiClassDescriptor(apiClass))
+              .addMethodTransformer(getInitTransformer(apiClass))
+              .addMethodTransformer(getApiLevelTransformer(apiClass))
+              .addMethodTransformer(getVisitFieldsTransformer(apiClass))
+              .addMethodTransformer(getVisitMethodsTransformer(apiClass))
+              .removeMethods(MethodPredicate.onName("placeHolderForInit"))
+              .removeMethods(MethodPredicate.onName("placeHolderForGetApiLevel"))
+              .removeMethods(MethodPredicate.onName("placeHolderForVisitFields"))
+              .removeMethods(MethodPredicate.onName("placeHolderForVisitMethods"))
+              .transform());
+    }
+
+    for (String pkg : packages) {
+      consumer.accept(
+          getPackageBuilderDescriptor(pkg),
+          transformer(AndroidApiDatabasePackageTemplate.class)
+              .setClassDescriptor(getPackageBuilderDescriptor(pkg))
+              .addMethodTransformer(getBuildClassTransformer(packageToClassesMap.get(pkg)))
+              .removeMethods(MethodPredicate.onName("placeHolder"))
+              .transform());
+    }
+
+    consumer.accept(
+        generatedMainDescriptor(),
+        transformer(AndroidApiDatabaseBuilderTemplate.class)
+            .setClassDescriptor(generatedMainDescriptor())
+            .addMethodTransformer(getVisitApiClassesTransformer(apiClasses))
+            .addMethodTransformer(getBuildPackageTransformer(packages))
+            .removeMethods(MethodPredicate.onName("placeHolderForVisitApiClasses"))
+            .removeMethods(MethodPredicate.onName("placeHolderForBuildClass"))
+            .transform());
+  }
+
+  private static String getPackageBuilderDescriptor(String pkg) {
+    return DescriptorUtils.javaTypeToDescriptor(
+        AndroidApiDatabasePackageTemplate.class
+            .getTypeName()
+            .replace("Template", "ForPackage_" + pkg.replace(".", "_")));
+  }
+
+  private static String getApiClassDescriptor(ParsedApiClass apiClass) {
+    return DescriptorUtils.javaTypeToDescriptor(
+        AndroidApiDatabaseClassTemplate.class
+            .getTypeName()
+            .replace(
+                "Template",
+                "ForClass_" + apiClass.getClassReference().getTypeName().replace(".", "_")));
+  }
+
+  // The transformer below changes AndroidApiDatabaseClassTemplate.<init> from:
+  //     super(Reference.classFromDescriptor(placeHolderForInit()));
+  // into
+  //     super(Reference.classFromDescriptor("<class-descriptor>"));
+  private static MethodTransformer getInitTransformer(ParsedApiClass apiClass) {
+    return replaceCode(
+        "placeHolderForInit",
+        transformer -> {
+          transformer.visitLdcInsn(apiClass.getClassReference().getDescriptor());
+        });
+  }
+
+  // The transformer below changes AndroidApiDatabaseClassTemplate.getApiLevel from:
+  //     return placeHolderForGetApiLevel();
+  // into
+  //    return AndroidApiLevel.getAndroidApiLevel(<apiLevel>);
+  private static MethodTransformer getApiLevelTransformer(ParsedApiClass apiClass) {
+    return replaceCode(
+        "placeHolderForGetApiLevel",
+        transformer -> {
+          transformer.visitLdcInsn(apiClass.getApiLevel().getLevel());
+          transformer.visitMethodInsn(
+              INVOKESTATIC,
+              ANDROID_API_LEVEL.getBinaryName(),
+              "getAndroidApiLevel",
+              "(I)" + ANDROID_API_LEVEL.getDescriptor(),
+              false);
+        });
+  }
+
+  // The transformer below changes AndroidApiDatabaseClassTemplate.visitFields from:
+  //     placeHolder();
+  //     return TraversalContinuation.CONTINUE;
+  // into
+  //    TraversalContinuation s1 = visitField("field1", "descriptor1", apiLevel1, visitor)
+  //    if (s1.shouldBreak()) {
+  //       return s1;
+  //    }
+  //    TraversalContinuation s2 = visitField("field2", "descriptor2", apiLevel2, visitor)
+  //    if (s2.shouldBreak()) {
+  //       return s2;
+  //    }
+  //    ...
+  //    return TraversalContinuation.CONTINUE;
+  private static MethodTransformer getVisitFieldsTransformer(ParsedApiClass apiClass) {
+    return replaceCode(
+        "placeHolderForVisitFields",
+        transformer -> {
+          apiClass.visitFieldReferences(
+              (apiLevel, references) -> {
+                references.forEach(
+                    reference -> {
+                      transformer.visitVarInsn(ALOAD, 0);
+                      transformer.visitLdcInsn(reference.getFieldName());
+                      transformer.visitLdcInsn(reference.getFieldType().getDescriptor());
+                      transformer.visitLdcInsn(apiLevel.getLevel());
+                      transformer.visitVarInsn(ALOAD, 1);
+                      transformer.visitMethodInsn(
+                          INVOKEVIRTUAL,
+                          ANDROID_API_CLASS.getBinaryName(),
+                          "visitField",
+                          "(Ljava/lang/String;"
+                              + "Ljava/lang/String;"
+                              + "I"
+                              + "Ljava/util/function/BiFunction;)"
+                              + TRAVERSAL_CONTINUATION.getDescriptor(),
+                          false);
+                      // Note that instead of storing the result here, we dup it on the stack.
+                      transformer.visitInsn(DUP);
+                      transformer.visitMethodInsn(
+                          INVOKEVIRTUAL,
+                          TRAVERSAL_CONTINUATION.getBinaryName(),
+                          "shouldBreak",
+                          "()Z",
+                          false);
+                      Label label = new Label();
+                      transformer.visitJumpInsn(IFEQ, label);
+                      transformer.visitInsn(ARETURN);
+                      transformer.visitLabel(label);
+                      transformer.visitFrame(
+                          F_SAME1,
+                          0,
+                          new Object[] {},
+                          1,
+                          new Object[] {TRAVERSAL_CONTINUATION.getBinaryName()});
+                      // The pop here is needed to remove the dupped value in the case we do not
+                      // return.
+                      transformer.visitInsn(POP);
+                    });
+              });
+        });
+  }
+
+  // The transformer below changes AndroidApiDatabaseClassTemplate.visitMethods from:
+  //     placeHolderForVisitMethods();
+  //     return TraversalContinuation.CONTINUE;
+  // into
+  //    TraversalContinuation s1 = visitMethod(
+  //      "method1", new String[] { "param11", ... , "param1X" }, null/return1, apiLevel1, visitor)
+  //    if (s1.shouldBreak()) {
+  //       return s1;
+  //    }
+  //    TraversalContinuation s1 = visitMethod(
+  //      "method2", new String[] { "param21", ... , "param2X" }, null/return2, apiLevel2, visitor)
+  //    if (s2.shouldBreak()) {
+  //       return s2;
+  //    }
+  //    ...
+  //    return TraversalContinuation.CONTINUE;
+  private static MethodTransformer getVisitMethodsTransformer(ParsedApiClass apiClass) {
+    return replaceCode(
+        "placeHolderForVisitMethods",
+        transformer -> {
+          apiClass.visitMethodReferences(
+              (apiLevel, references) -> {
+                references.forEach(
+                    reference -> {
+                      transformer.visitVarInsn(ALOAD, 0);
+                      transformer.visitLdcInsn(reference.getMethodName());
+                      List<TypeReference> formalTypes = reference.getFormalTypes();
+                      transformer.visitLdcInsn(formalTypes.size());
+                      transformer.visitTypeInsn(ANEWARRAY, binaryName(String.class));
+                      for (int i = 0; i < formalTypes.size(); i++) {
+                        transformer.visitInsn(DUP);
+                        transformer.visitLdcInsn(i);
+                        transformer.visitLdcInsn(formalTypes.get(i).getDescriptor());
+                        transformer.visitInsn(AASTORE);
+                      }
+                      if (reference.getReturnType() != null) {
+                        transformer.visitLdcInsn(reference.getReturnType().getDescriptor());
+                      } else {
+                        transformer.visitInsn(ACONST_NULL);
+                      }
+                      transformer.visitLdcInsn(apiLevel.getLevel());
+                      transformer.visitVarInsn(ALOAD, 1);
+                      transformer.visitMethodInsn(
+                          INVOKEVIRTUAL,
+                          ANDROID_API_CLASS.getBinaryName(),
+                          "visitMethod",
+                          "(Ljava/lang/String;"
+                              + "[Ljava/lang/String;Ljava/lang/String;"
+                              + "I"
+                              + "Ljava/util/function/BiFunction;)"
+                              + TRAVERSAL_CONTINUATION.getDescriptor(),
+                          false);
+                      // Note that instead of storing the result here, we dup it on the stack.
+                      transformer.visitInsn(DUP);
+                      transformer.visitMethodInsn(
+                          INVOKEVIRTUAL,
+                          TRAVERSAL_CONTINUATION.getBinaryName(),
+                          "shouldBreak",
+                          "()Z",
+                          false);
+                      Label label = new Label();
+                      transformer.visitJumpInsn(IFEQ, label);
+                      transformer.visitInsn(ARETURN);
+                      transformer.visitLabel(label);
+                      transformer.visitFrame(
+                          F_SAME1,
+                          0,
+                          new Object[] {},
+                          1,
+                          new Object[] {TRAVERSAL_CONTINUATION.getBinaryName()});
+                      // The pop here is needed to remove the dupped value in the case we do not
+                      // return.
+                      transformer.visitInsn(POP);
+                    });
+              });
+        });
+  }
+
+  // The transformer below changes AndroidApiDatabasePackageTemplate.buildClass from:
+  //    placeHolder();
+  //    return null;
+  // into
+  //    if ("<simple_class1>".equals(className)) {
+  //      return new AndroidApiClassForClass_class_name1();
+  //    }
+  //    if ("<simple_class2>".equals(className)) {
+  //      return new AndroidApiClassForClass_class_name2();
+  //    }
+  //    ...
+  //    return null;
+  private static MethodTransformer getBuildClassTransformer(List<ParsedApiClass> classesForPackage)
+      throws NoSuchMethodException {
+    Method equals = Object.class.getMethod("equals", Object.class);
+    return replaceCode(
+        "placeHolder",
+        transformer -> {
+          classesForPackage.forEach(
+              apiClass -> {
+                transformer.visitLdcInsn(
+                    DescriptorUtils.getSimpleClassNameFromDescriptor(
+                        apiClass.getClassReference().getDescriptor()));
+                transformer.visitVarInsn(ALOAD, 0);
+                transformer.visitMethodInsn(
+                    INVOKEVIRTUAL,
+                    binaryName(String.class),
+                    equals.getName(),
+                    methodDescriptor(equals),
+                    false);
+                Label label = new Label();
+                transformer.visitJumpInsn(IFEQ, label);
+                String binaryName =
+                    DescriptorUtils.getBinaryNameFromDescriptor(getApiClassDescriptor(apiClass));
+                transformer.visitTypeInsn(NEW, binaryName);
+                transformer.visitInsn(DUP);
+                transformer.visitMethodInsn(INVOKESPECIAL, binaryName, "<init>", "()V", false);
+                transformer.visitInsn(ARETURN);
+                transformer.visitLabel(label);
+                transformer.visitFrame(Opcodes.F_SAME, 0, new Object[] {}, 0, new Object[0]);
+              });
+        });
+  }
+
+  // The transformer below changes AndroidApiDatabaseBuilderTemplate.buildClass from:
+  //    String descriptor = classReference.getDescriptor();
+  //    String packageName = DescriptorUtils.getPackageNameFromDescriptor(descriptor);
+  //    String simpleClassName = DescriptorUtils.getSimpleClassNameFromDescriptor(descriptor);
+  //    placeHolderForBuildClass();
+  //    return null;
+  // into
+  //    String descriptor = classReference.getDescriptor();
+  //    String packageName = DescriptorUtils.getPackageNameFromDescriptor(descriptor);
+  //    String simpleClassName = DescriptorUtils.getSimpleClassNameFromDescriptor(descriptor);
+  //    if ("<package_name1>".equals(packageName)) {
+  //      return AndroidApiClassForPackage_package_name1(simpleClassName);
+  //    }
+  //    if ("<package_name2>".equals(simpleClassName)) {
+  //      return AndroidApiClassForPackage_package_name2(simpleClassName);
+  //    }
+  //    ...
+  //    return null;
+  private static MethodTransformer getBuildPackageTransformer(List<String> packages)
+      throws NoSuchMethodException {
+    Method equals = String.class.getMethod("equals", Object.class);
+    Method buildClass =
+        AndroidApiDatabasePackageTemplate.class.getMethod("buildClass", String.class);
+    return replaceCode(
+        "placeHolderForBuildClass",
+        transformer -> {
+          packages.forEach(
+              pkg -> {
+                transformer.visitLdcInsn(pkg);
+                transformer.visitVarInsn(ALOAD, 2);
+                transformer.visitMethodInsn(
+                    INVOKEVIRTUAL,
+                    binaryName(String.class),
+                    equals.getName(),
+                    methodDescriptor(equals),
+                    false);
+                Label label = new Label();
+                transformer.visitJumpInsn(IFEQ, label);
+                transformer.visitVarInsn(ALOAD, 3);
+                transformer.visitMethodInsn(
+                    INVOKESTATIC,
+                    DescriptorUtils.getBinaryNameFromDescriptor(getPackageBuilderDescriptor(pkg)),
+                    buildClass.getName(),
+                    methodDescriptor(buildClass),
+                    false);
+                transformer.visitInsn(ARETURN);
+                transformer.visitLabel(label);
+                transformer.visitFrame(
+                    Opcodes.F_FULL,
+                    4,
+                    new Object[] {
+                      binaryName(ClassReference.class),
+                      binaryName(String.class),
+                      binaryName(String.class),
+                      binaryName(String.class)
+                    },
+                    0,
+                    new Object[0]);
+              });
+        });
+  }
+
+  // The transformer below changes AndroidApiDatabaseBuilderTemplate.buildClass from:
+  //    placeHolderForVisitApiClasses();
+  // into
+  //    classDescriptorConsumer.accept("<descriptor_class_1>");
+  //    classDescriptorConsumer.accept("<descriptor_class_2>");
+  //    ...
+  //    return null;
+  private static MethodTransformer getVisitApiClassesTransformer(List<ParsedApiClass> apiClasses) {
+    return replaceCode(
+        "placeHolderForVisitApiClasses",
+        transformer -> {
+          apiClasses.forEach(
+              apiClass -> {
+                transformer.visitVarInsn(ALOAD, 0);
+                transformer.visitLdcInsn(apiClass.getClassReference().getDescriptor());
+                transformer.visitMethodInsn(
+                    INVOKEINTERFACE,
+                    binaryName(Consumer.class),
+                    "accept",
+                    "(Ljava/lang/Object;)V",
+                    true);
+              });
+        });
+  }
+
+  private static MethodTransformer replaceCode(
+      String placeholderName, Consumer<MethodTransformer> consumer) {
+    return new MethodTransformer() {
+
+      @Override
+      public void visitMaxs(int maxStack, int maxLocals) {
+        super.visitMaxs(-1, maxLocals);
+      }
+
+      @Override
+      public void visitMethodInsn(
+          int opcode, String owner, String name, String descriptor, boolean isInterface) {
+        if (name.equals(placeholderName)) {
+          consumer.accept(this);
+        } else {
+          super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+        }
+      }
+    };
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java
new file mode 100644
index 0000000..d85e3e5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java
@@ -0,0 +1,343 @@
+// 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 com.android.tools.r8.apimodel.AndroidApiDatabaseBuilderGenerator.generatedMainDescriptor;
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.JvmTestBuilder;
+import com.android.tools.r8.JvmTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestState;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.apimodel.AndroidApiVersionsXmlParser.ParsedApiClass;
+import com.android.tools.r8.cf.bootstrap.BootstrapCurrentEqualityTest;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.IntBox;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiFunction;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AndroidApiDatabaseBuilderGeneratorTest extends TestBase {
+
+  protected final TestParameters parameters;
+  private static final Path API_VERSIONS_XML =
+      Paths.get(ToolHelper.THIRD_PARTY_DIR, "android_jar", "api-versions", "api-versions.xml");
+  private static final Path API_DATABASE_JAR =
+      Paths.get(ToolHelper.THIRD_PARTY_DIR, "android_jar", "api-database", "api-database.jar");
+  private static final AndroidApiLevel API_LEVEL = AndroidApiLevel.R;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public AndroidApiDatabaseBuilderGeneratorTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private static Path generateJar() throws Exception {
+    return generateJar(
+        AndroidApiVersionsXmlParser.getParsedApiClasses(API_VERSIONS_XML.toFile(), API_LEVEL));
+  }
+
+  private static Path generateJar(List<ParsedApiClass> apiClasses) throws Exception {
+    TemporaryFolder temp = new TemporaryFolder();
+    temp.create();
+    ZipBuilder builder = ZipBuilder.builder(temp.newFile("out.jar").toPath());
+    AndroidApiDatabaseBuilderGenerator.generate(
+        apiClasses,
+        (descriptor, content) -> {
+          try {
+            String binaryName = DescriptorUtils.getBinaryNameFromDescriptor(descriptor) + ".class";
+            builder.addBytes(binaryName, content);
+          } catch (IOException exception) {
+            throw new RuntimeException(exception);
+          }
+        });
+    return builder.build();
+  }
+
+  @Test
+  public void testCanParseApiVersionsXml() throws Exception {
+    // This tests makes a rudimentary check on the number of classes, fields and methods in
+    // api-versions.xml to ensure that the runtime tests do not vacuously succeed.
+    List<ParsedApiClass> parsedApiClasses =
+        AndroidApiVersionsXmlParser.getParsedApiClasses(API_VERSIONS_XML.toFile(), API_LEVEL);
+    IntBox numberOfFields = new IntBox(0);
+    IntBox numberOfMethods = new IntBox(0);
+    parsedApiClasses.forEach(
+        apiClass -> {
+          apiClass.visitFieldReferences(
+              ((apiLevel, fieldReferences) -> {
+                fieldReferences.forEach(field -> numberOfFields.increment());
+              }));
+          apiClass.visitMethodReferences(
+              ((apiLevel, methodReferences) -> {
+                methodReferences.forEach(field -> numberOfMethods.increment());
+              }));
+        });
+    // These numbers will change when updating api-versions.xml
+    assertEquals(4742, parsedApiClasses.size());
+    assertEquals(25144, numberOfFields.get());
+    assertEquals(38661, numberOfMethods.get());
+  }
+
+  @Test
+  public void testDatabaseGenerationUpToDate() throws Exception {
+    BootstrapCurrentEqualityTest.filesAreEqual(generateJar(), API_DATABASE_JAR);
+  }
+
+  /**
+   * Main entry point for building a database over references in framework to the api level they
+   * were introduced. Running main will generate a new jar and run tests on it to ensure it is
+   * compatible with R8 sources and works as expected.
+   *
+   * <p>If the generated jar passes tests it will be moved to third_party/android_jar/api-database/
+   * and override the current file in there.
+   */
+  public static void main(String[] args) throws Exception {
+    List<ParsedApiClass> parsedApiClasses =
+        AndroidApiVersionsXmlParser.getParsedApiClasses(API_VERSIONS_XML.toFile(), API_LEVEL);
+    Path generatedJar = generateJar(parsedApiClasses);
+    validateJar(generatedJar, parsedApiClasses);
+    Files.move(generatedJar, API_DATABASE_JAR, REPLACE_EXISTING);
+  }
+
+  private static void validateJar(Path generated, List<ParsedApiClass> apiClasses) {
+    List<BiFunction<Path, List<ParsedApiClass>, Boolean>> tests =
+        ImmutableList.of(
+            AndroidApiDatabaseBuilderGeneratorTest::testGeneratedOutputForVisitClasses,
+            AndroidApiDatabaseBuilderGeneratorTest::testBuildClassesContinue,
+            AndroidApiDatabaseBuilderGeneratorTest::testBuildClassesBreak,
+            AndroidApiDatabaseBuilderGeneratorTest::testNoPlaceHolder);
+    tests.forEach(
+        test -> {
+          if (!test.apply(generated, apiClasses)) {
+            throw new RuntimeException("Generated jar did not pass tests");
+          }
+        });
+  }
+
+  private static boolean testGeneratedOutputForVisitClasses(
+      Path generated, List<ParsedApiClass> parsedApiClasses) {
+    String expectedOutput =
+        StringUtils.lines(
+            ListUtils.map(
+                parsedApiClasses, apiClass -> apiClass.getClassReference().getDescriptor()));
+    return runTest(generated, TestGeneratedMainVisitClasses.class)
+        .getStdOut()
+        .equals(expectedOutput);
+  }
+
+  private static boolean testBuildClassesContinue(
+      Path generated, List<ParsedApiClass> parsedApiClasses) {
+    return runTest(generated, TestBuildClassesContinue.class)
+        .getStdOut()
+        .equals(getExpected(parsedApiClasses, false));
+  }
+
+  private static boolean testBuildClassesBreak(
+      Path generated, List<ParsedApiClass> parsedApiClasses) {
+    return runTest(generated, TestBuildClassesBreak.class)
+        .getStdOut()
+        .equals(getExpected(parsedApiClasses, true));
+  }
+
+  private static boolean testNoPlaceHolder(Path generated, List<ParsedApiClass> parsedApiClasses) {
+    try {
+      CodeInspector inspector = new CodeInspector(generated);
+      inspector
+          .allClasses()
+          .forEach(
+              clazz -> {
+                clazz.forAllMethods(
+                    methods -> {
+                      if (methods.getFinalName().startsWith("placeHolder")) {
+                        throw new RuntimeException("Found placeHolder method in generated jar");
+                      }
+                    });
+              });
+    } catch (Exception ex) {
+      throw new RuntimeException(ex);
+    }
+    return true;
+  }
+
+  private static JvmTestRunResult runTest(Path generated, Class<?> testClass) {
+    try {
+      TemporaryFolder temporaryFolder = new TemporaryFolder();
+      temporaryFolder.create();
+      return JvmTestBuilder.create(new TestState(temporaryFolder))
+          .addProgramClassFileData(
+              transformer(testClass)
+                  .replaceClassDescriptorInMethodInstructions(
+                      descriptor(AndroidApiDatabaseBuilderTemplate.class),
+                      generatedMainDescriptor())
+                  .transform())
+          .addLibraryFiles(
+              generated,
+              ToolHelper.R8_WITHOUT_DEPS_JAR,
+              getDepsWithoutGeneratedApiModelClasses(),
+              ToolHelper.getJava8RuntimeJar())
+          .run(TestRuntime.getSystemRuntime(), testClass)
+          .apply(
+              result -> {
+                if (result.getExitCode() != 0) {
+                  throw new RuntimeException(result.getStdErr());
+                }
+              });
+    } catch (IOException | ExecutionException | CompilationFailedException ex) {
+      throw new RuntimeException(ex);
+    }
+  }
+
+  private static Path getDepsWithoutGeneratedApiModelClasses() throws IOException {
+    Path tempDeps = Files.createTempDirectory("temp_deps");
+    ZipUtils.unzip(
+        ToolHelper.DEPS.toString(),
+        tempDeps.toFile(),
+        entry -> {
+          if (entry.getName().startsWith("com/android/tools/r8/apimodel/")) {
+            return false;
+          }
+          return true;
+        });
+    Path modifiedDeps = Files.createTempFile("modified_deps", ".jar");
+    ZipUtils.zip(modifiedDeps, tempDeps);
+    return modifiedDeps;
+  }
+
+  private static String getExpected(List<ParsedApiClass> parsedApiClasses, boolean abort) {
+    List<String> expected = new ArrayList<>();
+    parsedApiClasses.forEach(
+        apiClass -> {
+          expected.add(apiClass.getClassReference().getDescriptor());
+          expected.add(apiClass.getApiLevel().getName());
+          BooleanBox added = new BooleanBox(false);
+          apiClass.visitFieldReferences(
+              (apiLevel, fieldReferences) -> {
+                fieldReferences.forEach(
+                    fieldReference -> {
+                      if (added.isTrue() && abort) {
+                        return;
+                      }
+                      added.set();
+                      expected.add(fieldReference.getFieldType().getDescriptor());
+                      expected.add(fieldReference.getFieldName());
+                      expected.add(apiLevel.getName());
+                    });
+              });
+          added.set(false);
+          apiClass.visitMethodReferences(
+              (apiLevel, methodReferences) -> {
+                methodReferences.forEach(
+                    methodReference -> {
+                      if (added.isTrue() && abort) {
+                        return;
+                      }
+                      added.set();
+                      expected.add(methodReference.getMethodDescriptor());
+                      expected.add(methodReference.getMethodName());
+                      expected.add(apiLevel.getName());
+                    });
+              });
+        });
+    return StringUtils.lines(expected);
+  }
+
+  public static class TestGeneratedMainVisitClasses {
+
+    public static void main(String[] args) {
+      AndroidApiDatabaseBuilderTemplate.visitApiClasses(System.out::println);
+    }
+  }
+
+  public static class TestBuildClassesContinue {
+
+    public static void main(String[] args) {
+      AndroidApiDatabaseBuilderTemplate.visitApiClasses(
+          descriptor -> {
+            com.android.tools.r8.androidapi.AndroidApiClass apiClass =
+                AndroidApiDatabaseBuilderTemplate.buildClass(
+                    Reference.classFromDescriptor(descriptor));
+            if (apiClass != null) {
+              System.out.println(descriptor);
+              System.out.println(apiClass.getApiLevel().getName());
+              apiClass.visitFields(
+                  (reference, apiLevel) -> {
+                    System.out.println(reference.getFieldType().getDescriptor());
+                    System.out.println(reference.getFieldName());
+                    System.out.println(apiLevel.getName());
+                    return TraversalContinuation.CONTINUE;
+                  });
+              apiClass.visitMethods(
+                  (reference, apiLevel) -> {
+                    System.out.println(reference.getMethodDescriptor());
+                    System.out.println(reference.getMethodName());
+                    System.out.println(apiLevel.getName());
+                    return TraversalContinuation.CONTINUE;
+                  });
+            }
+          });
+    }
+  }
+
+  public static class TestBuildClassesBreak {
+
+    public static void main(String[] args) {
+      AndroidApiDatabaseBuilderTemplate.visitApiClasses(
+          descriptor -> {
+            com.android.tools.r8.androidapi.AndroidApiClass apiClass =
+                AndroidApiDatabaseBuilderTemplate.buildClass(
+                    Reference.classFromDescriptor(descriptor));
+            if (apiClass != null) {
+              System.out.println(descriptor);
+              System.out.println(apiClass.getApiLevel().getName());
+              apiClass.visitFields(
+                  (reference, apiLevel) -> {
+                    System.out.println(reference.getFieldType().getDescriptor());
+                    System.out.println(reference.getFieldName());
+                    System.out.println(apiLevel.getName());
+                    return TraversalContinuation.BREAK;
+                  });
+              apiClass.visitMethods(
+                  (reference, apiLevel) -> {
+                    System.out.println(reference.getMethodDescriptor());
+                    System.out.println(reference.getMethodName());
+                    System.out.println(apiLevel.getName());
+                    return TraversalContinuation.BREAK;
+                  });
+            }
+          });
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderTemplate.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderTemplate.java
new file mode 100644
index 0000000..4b21f6e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderTemplate.java
@@ -0,0 +1,32 @@
+// 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 com.android.tools.r8.androidapi.AndroidApiClass;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.function.Consumer;
+
+/** This is a template for generating the AndroidApiDatabaseBuilder. */
+public class AndroidApiDatabaseBuilderTemplate /* AndroidApiDatabaseBuilder */ {
+
+  public static void visitApiClasses(Consumer<String> classDescriptorConsumer) {
+    // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
+    placeHolderForVisitApiClasses();
+  }
+
+  public static AndroidApiClass buildClass(ClassReference classReference) {
+    String descriptor = classReference.getDescriptor();
+    String packageName = DescriptorUtils.getPackageNameFromDescriptor(descriptor);
+    String simpleClassName = DescriptorUtils.getSimpleClassNameFromDescriptor(descriptor);
+    // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
+    placeHolderForBuildClass();
+    return null;
+  }
+
+  private static void placeHolderForVisitApiClasses() {}
+
+  private static void placeHolderForBuildClass() {}
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseClassTemplate.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseClassTemplate.java
new file mode 100644
index 0000000..2598898
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseClassTemplate.java
@@ -0,0 +1,56 @@
+// 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 com.android.tools.r8.androidapi.AndroidApiClass;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.function.BiFunction;
+
+/** This is a template for generating AndroidApiDatabaseClass extending AndroidApiClass */
+public class AndroidApiDatabaseClassTemplate extends AndroidApiClass {
+
+  protected AndroidApiDatabaseClassTemplate() {
+    // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
+    super(Reference.classFromDescriptor(placeHolderForInit()));
+  }
+
+  @Override
+  public AndroidApiLevel getApiLevel() {
+    // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
+    return placeHolderForGetApiLevel();
+  }
+
+  @Override
+  public TraversalContinuation visitFields(
+      BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor) {
+    // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
+    placeHolderForVisitFields();
+    return TraversalContinuation.CONTINUE;
+  }
+
+  @Override
+  public TraversalContinuation visitMethods(
+      BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor) {
+    // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
+    placeHolderForVisitMethods();
+    return TraversalContinuation.CONTINUE;
+  }
+
+  private static String placeHolderForInit() {
+    return null;
+  }
+
+  private static AndroidApiLevel placeHolderForGetApiLevel() {
+    return null;
+  }
+
+  private static void placeHolderForVisitFields() {}
+
+  private static void placeHolderForVisitMethods() {}
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabasePackageTemplate.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabasePackageTemplate.java
new file mode 100644
index 0000000..3919d11
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabasePackageTemplate.java
@@ -0,0 +1,19 @@
+// 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 com.android.tools.r8.androidapi.AndroidApiClass;
+
+/** This is a template for generating the AndroidApiDatabasePackage. */
+public class AndroidApiDatabasePackageTemplate {
+
+  public static AndroidApiClass buildClass(String className) {
+    // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
+    placeHolder();
+    return null;
+  }
+
+  private static void placeHolder() {}
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
new file mode 100644
index 0000000..c904dff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
@@ -0,0 +1,175 @@
+// 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 com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.function.BiConsumer;
+import javax.xml.parsers.DocumentBuilderFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class AndroidApiVersionsXmlParser {
+
+  private final List<ParsedApiClass> classes = new ArrayList<>();
+
+  private final File apiVersionsXml;
+  private final AndroidApiLevel maxApiLevel;
+
+  private AndroidApiVersionsXmlParser(File apiVersionsXml, AndroidApiLevel maxApiLevel) {
+    this.apiVersionsXml = apiVersionsXml;
+    this.maxApiLevel = maxApiLevel;
+  }
+
+  private ParsedApiClass register(ClassReference reference, AndroidApiLevel apiLevel) {
+    ParsedApiClass parsedApiClass = new ParsedApiClass(reference, apiLevel);
+    classes.add(parsedApiClass);
+    return parsedApiClass;
+  }
+
+  private void readApiVersionsXmlFile() throws Exception {
+    CodeInspector inspector = new CodeInspector(ToolHelper.getAndroidJar(maxApiLevel));
+    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+    Document document = factory.newDocumentBuilder().parse(apiVersionsXml);
+    NodeList classes = document.getElementsByTagName("class");
+    for (int i = 0; i < classes.getLength(); i++) {
+      Node node = classes.item(i);
+      assert node.getNodeType() == Node.ELEMENT_NODE;
+      AndroidApiLevel apiLevel = getMaxAndroidApiLevelFromNode(node, AndroidApiLevel.B);
+      String type = DescriptorUtils.getJavaTypeFromBinaryName(getName(node));
+      ClassSubject clazz = inspector.clazz(type);
+      if (!clazz.isPresent()) {
+        // TODO(b/190326408): Investigate why the class is not present.
+        continue;
+      }
+      ClassReference originalReference = clazz.getOriginalReference();
+      ParsedApiClass parsedApiClass = register(originalReference, apiLevel);
+      NodeList members = node.getChildNodes();
+      for (int j = 0; j < members.getLength(); j++) {
+        Node memberNode = members.item(j);
+        if (isMethod(memberNode)) {
+          // TODO(b/190326408): Check for existence.
+          parsedApiClass.register(
+              getMethodReference(originalReference, memberNode),
+              getMaxAndroidApiLevelFromNode(memberNode, apiLevel));
+        } else if (isField(memberNode)) {
+          // The field do not have descriptors and are supposed to be unique.
+          FieldSubject fieldSubject = clazz.uniqueFieldWithName(getName(memberNode));
+          if (!fieldSubject.isPresent()) {
+            // TODO(b/190326408): Investigate why the member is not present.
+            continue;
+          }
+          parsedApiClass.register(
+              fieldSubject.getOriginalReference(),
+              getMaxAndroidApiLevelFromNode(memberNode, apiLevel));
+        }
+      }
+    }
+  }
+
+  private boolean isMethod(Node node) {
+    return node.getNodeName().equals("method");
+  }
+
+  private String getName(Node node) {
+    return node.getAttributes().getNamedItem("name").getNodeValue();
+  }
+
+  private MethodReference getMethodReference(ClassReference classDescriptor, Node node) {
+    assert isMethod(node);
+    String name = getName(node);
+    int signatureStart = name.indexOf('(');
+    assert signatureStart > 0;
+    String parsedName = name.substring(0, signatureStart).replace("&lt;", "<");
+    assert !parsedName.contains("&");
+    return Reference.methodFromDescriptor(
+        classDescriptor.getDescriptor(), parsedName, name.substring(signatureStart));
+  }
+
+  private boolean isField(Node node) {
+    return node.getNodeName().equals("field");
+  }
+
+  private AndroidApiLevel getMaxAndroidApiLevelFromNode(Node node, AndroidApiLevel defaultValue) {
+    if (node == null) {
+      return defaultValue;
+    }
+    Node since = node.getAttributes().getNamedItem("since");
+    if (since == null) {
+      return defaultValue;
+    }
+    return defaultValue.max(
+        AndroidApiLevel.getAndroidApiLevel(Integer.parseInt(since.getNodeValue())));
+  }
+
+  public static List<ParsedApiClass> getParsedApiClasses(
+      File apiVersionsXml, AndroidApiLevel apiLevel) throws Exception {
+    AndroidApiVersionsXmlParser parser = new AndroidApiVersionsXmlParser(apiVersionsXml, apiLevel);
+    parser.readApiVersionsXmlFile();
+    return parser.classes;
+  }
+
+  public static class ParsedApiClass {
+
+    private final ClassReference classReference;
+    private final AndroidApiLevel apiLevel;
+    private final TreeMap<AndroidApiLevel, List<FieldReference>> fieldReferences = new TreeMap<>();
+    private final Map<AndroidApiLevel, List<MethodReference>> methodReferences = new TreeMap<>();
+
+    public ClassReference getClassReference() {
+      return classReference;
+    }
+
+    public AndroidApiLevel getApiLevel() {
+      return apiLevel;
+    }
+
+    private ParsedApiClass(ClassReference classReference, AndroidApiLevel apiLevel) {
+      this.classReference = classReference;
+      this.apiLevel = apiLevel;
+    }
+
+    private void register(FieldReference reference, AndroidApiLevel apiLevel) {
+      fieldReferences.computeIfAbsent(apiLevel, ignoreArgument(ArrayList::new)).add(reference);
+    }
+
+    private void register(MethodReference reference, AndroidApiLevel apiLevel) {
+      methodReferences.computeIfAbsent(apiLevel, ignoreArgument(ArrayList::new)).add(reference);
+    }
+
+    public void visitFieldReferences(BiConsumer<AndroidApiLevel, List<FieldReference>> consumer) {
+      fieldReferences.forEach(
+          (apiLevel, references) -> {
+            references.sort(Comparator.comparing(FieldReference::getFieldName));
+            consumer.accept(apiLevel, references);
+          });
+    }
+
+    public void visitMethodReferences(BiConsumer<AndroidApiLevel, List<MethodReference>> consumer) {
+      methodReferences.forEach(
+          (apiLevel, references) -> {
+            references.sort(Comparator.comparing(MethodReference::getMethodName));
+            consumer.accept(apiLevel, references);
+          });
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiFieldsTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java
similarity index 92%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiFieldsTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java
index 8045b48..5d0c0b8 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java
@@ -2,9 +2,9 @@
 // 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.apimodeling;
+package com.android.tools.r8.apimodel;
 
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForType;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForType;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
@@ -74,7 +74,7 @@
       // a method reference to `Api` and only because of the type reference in the field `api`.
       Class<?> aClass =
           Class.forName(
-              "com.android.tools.r8.apimodeling.ApiModelClassMergingWithDifferentApiFieldsTest_Api"
+              "com.android.tools.r8.apimodel.ApiModelClassMergingWithDifferentApiFieldsTest_Api"
                   .replace("_", "$"));
       Method foo = aClass.getDeclaredMethod("foo");
       foo.invoke(api);
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiMethodsTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiMethodsTest.java
similarity index 94%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiMethodsTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiMethodsTest.java
index b56d6ae..f174546 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiMethodsTest.java
@@ -2,9 +2,9 @@
 // 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.apimodeling;
+package com.android.tools.r8.apimodel;
 
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelInlineMethodWithApiTypeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java
similarity index 77%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelInlineMethodWithApiTypeTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java
index 2e24790..f6996e9 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelInlineMethodWithApiTypeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java
@@ -2,10 +2,13 @@
 // 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.apimodeling;
+package com.android.tools.r8.apimodel;
 
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForType;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForType;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestBase;
@@ -34,9 +37,8 @@
 
   @Test
   public void testR8() throws Exception {
-    Method apiCallerApiLevel1 = ApiCaller.class.getDeclaredMethod("apiLevel22");
-    Method apiCallerCallerApiLevel1 = ApiCallerCaller.class.getDeclaredMethod("apiLevel22");
-    Method otherCallerApiLevel1 = OtherCaller.class.getDeclaredMethod("apiLevel1");
+    Method apiCallerApiLevel22 = ApiCaller.class.getDeclaredMethod("apiLevel22");
+    Method apiCallerCallerApiLevel22 = ApiCallerCaller.class.getDeclaredMethod("apiLevel22");
     testForR8(parameters.getBackend())
         .addProgramClasses(ApiCaller.class, ApiCallerCaller.class, OtherCaller.class, Main.class)
         .addLibraryClasses(ApiType.class)
@@ -50,22 +52,21 @@
         .addRunClasspathClasses(ApiType.class)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(ApiType.class.getName())
-        .inspect(verifyThat(parameters, apiCallerApiLevel1).inlinedInto(apiCallerCallerApiLevel1))
-        // TODO(b/138781768): Should not be inlined
-        .inspect(
-            verifyThat(parameters, apiCallerCallerApiLevel1).inlinedInto(otherCallerApiLevel1));
+        .inspect(verifyThat(parameters, apiCallerApiLevel22).inlinedInto(apiCallerCallerApiLevel22))
+        .inspect(inspector -> assertThat(inspector.clazz(OtherCaller.class), not(isPresent())));
   }
 
   public static class ApiType {}
 
   @NoHorizontalClassMerging
   public static class ApiCaller {
+
     public static ApiType apiLevel22() throws Exception {
       // The reflective call here is to ensure that the setting of A's api level is not based on
       // a method reference to `Api` and only because of the type reference in the field `api`.
       Class<?> reflectiveCall =
           Class.forName(
-              "com.android.tools.r8.apimodeling.ApiModelInlineMethodWithApiTypeTest_ApiType"
+              "com.android.tools.r8.apimodel.ApiModelInlineMethodWithApiTypeTest_ApiType"
                   .replace("_", "$"));
       return (ApiType) reflectiveCall.getDeclaredConstructor().newInstance();
     }
@@ -81,6 +82,7 @@
     }
   }
 
+  @NoHorizontalClassMerging
   public static class OtherCaller {
 
     public static void apiLevel1() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningFieldTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoClassInliningFieldTest.java
similarity index 95%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningFieldTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelNoClassInliningFieldTest.java
index 3bd5b8a..777b250 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningFieldTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoClassInliningFieldTest.java
@@ -2,9 +2,9 @@
 // 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.apimodeling;
+package com.android.tools.r8.apimodel;
 
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForField;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForField;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningMethodTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoClassInliningMethodTest.java
similarity index 92%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningMethodTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelNoClassInliningMethodTest.java
index a7804e0..ac42465 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningMethodTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoClassInliningMethodTest.java
@@ -2,10 +2,10 @@
 // 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.apimodeling;
+package com.android.tools.r8.apimodel;
 
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java
similarity index 88%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java
index f210e6f..c7b2b1c 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java
@@ -2,10 +2,10 @@
 // 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.apimodeling;
+package com.android.tools.r8.apimodel;
 
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForField;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForField;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
@@ -50,8 +50,9 @@
         .apply(setMockApiLevelForField(apiField, AndroidApiLevel.L_MR1))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .compile()
-        // TODO(b/138781768): Should not be inlined
-        .inspect(verifyThat(parameters, apiCaller).inlinedInto(apiCallerCaller))
+        .inspect(
+            verifyThat(parameters, apiCaller)
+                .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1))
         .addRunClasspathClasses(Api.class)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("Hello World!");
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
similarity index 92%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
index 4e5c39d..602f4a1 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
@@ -2,10 +2,10 @@
 // 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.apimodeling;
+package com.android.tools.r8.apimodel;
 
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest.java
similarity index 91%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest.java
index 048874d..e79cfed 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest.java
@@ -2,10 +2,10 @@
 // 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.apimodeling;
+package com.android.tools.r8.apimodel;
 
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java
similarity index 88%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java
index 0087275..28a2b00 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java
@@ -2,10 +2,10 @@
 // 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.apimodeling;
+package com.android.tools.r8.apimodel;
 
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForField;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForField;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
@@ -50,8 +50,9 @@
         .apply(setMockApiLevelForField(apiField, AndroidApiLevel.L_MR1))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .compile()
-        // TODO(b/138781768): Should not be inlined
-        .inspect(verifyThat(parameters, apiCaller).inlinedInto(apiCallerCaller))
+        .inspect(
+            verifyThat(parameters, apiCaller)
+                .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1))
         .addRunClasspathClasses(Api.class)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("Hello World!");
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelStaticTest.java
similarity index 92%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelStaticTest.java
index dfb8628..b5c7134 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelStaticTest.java
@@ -2,10 +2,10 @@
 // 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.apimodeling;
+package com.android.tools.r8.apimodel;
 
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelSuperTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelSuperTest.java
similarity index 92%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelSuperTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelSuperTest.java
index 415a163..7d6230b 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelSuperTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelSuperTest.java
@@ -2,10 +2,10 @@
 // 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.apimodeling;
+package com.android.tools.r8.apimodel;
 
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelVirtualTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelVirtualTest.java
similarity index 89%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelVirtualTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelVirtualTest.java
index 31af9e3..d87ed82 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelVirtualTest.java
@@ -2,11 +2,11 @@
 // 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.apimodeling;
+package com.android.tools.r8.apimodel;
 
-import static com.android.tools.r8.apimodeling.ApiModelNoInliningOfHigherApiLevelVirtualTest.ApiCaller.callVirtualMethod;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.apimodel.ApiModelNoInliningOfHigherApiLevelVirtualTest.ApiCaller.callVirtualMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelVerticalMergingOfSuperClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelVerticalMergingOfSuperClassTest.java
similarity index 94%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelVerticalMergingOfSuperClassTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelVerticalMergingOfSuperClassTest.java
index a04774b..80e213e 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelVerticalMergingOfSuperClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelVerticalMergingOfSuperClassTest.java
@@ -2,9 +2,9 @@
 // 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.apimodeling;
+package com.android.tools.r8.apimodel;
 
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForType;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForType;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
similarity index 98%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelingTestHelper.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
index b20e395..33d1deb 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -2,7 +2,7 @@
 // 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.apimodeling;
+package com.android.tools.r8.apimodel;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
diff --git a/src/test/java/com/android/tools/r8/classmerging/StatefulSingletonClassesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/StatefulSingletonClassesMergingTest.java
new file mode 100644
index 0000000..fadf934
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/StatefulSingletonClassesMergingTest.java
@@ -0,0 +1,87 @@
+// 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;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
+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;
+
+@RunWith(Parameterized.class)
+public class StatefulSingletonClassesMergingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public StatefulSingletonClassesMergingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertIsCompleteMergeGroup(A.class, B.class))
+        .enableInliningAnnotations()
+        .enableMemberValuePropagationAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .noClassStaticizing()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A", "B");
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      A.INSTANCE.f();
+      B.INSTANCE.g();
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    static final A INSTANCE = new A("A");
+
+    @NeverPropagateValue private final String data;
+
+    A(String data) {
+      this.data = data;
+    }
+
+    @NeverInline
+    void f() {
+      System.out.println(data);
+    }
+  }
+
+  @NeverClassInline
+  static class B {
+
+    static final B INSTANCE = new B("B");
+
+    @NeverPropagateValue private final String data;
+
+    B(String data) {
+      this.data = data;
+    }
+
+    @NeverInline
+    void g() {
+      System.out.println(data);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/DistinguishExceptionClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/DistinguishExceptionClassesTest.java
index e063a4b..d784a77 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/DistinguishExceptionClassesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/DistinguishExceptionClassesTest.java
@@ -8,6 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import org.junit.Test;
 
 public class DistinguishExceptionClassesTest extends HorizontalClassMergingTestBase {
@@ -20,6 +21,8 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("test success")
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergingWithSafeCheckCastTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergingWithSafeCheckCastTest.java
new file mode 100644
index 0000000..02f0f5b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergingWithSafeCheckCastTest.java
@@ -0,0 +1,125 @@
+// 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.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+
+public class MergingWithSafeCheckCastTest extends HorizontalClassMergingTestBase {
+
+  public MergingWithSafeCheckCastTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .enableMemberValuePropagationAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableNoUnusedInterfaceRemovalAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector
+                    .assertIsCompleteMergeGroup(A.class, B.class)
+                    .assertIsCompleteMergeGroup(I.class, J.class)
+                    .assertNoOtherClassesMerged())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+
+              // Check that the field f has been changed to have type java.lang.Object.
+              assertEquals(1, aClassSubject.allFields().size());
+              assertEquals(
+                  Object.class.getTypeName(),
+                  aClassSubject.allFields().get(0).getField().getType().getTypeName());
+
+              // Check that casts have been inserted into main().
+              MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
+              assertTrue(
+                  mainMethodSubject.streamInstructions().anyMatch(InstructionSubject::isCheckCast));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("I", "J");
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      new A(new IImpl()).f.printI();
+      new B(new JImpl()).f.printJ();
+    }
+  }
+
+  @NeverClassInline
+  public static class A {
+
+    @NeverPropagateValue I f;
+
+    A(I f) {
+      this.f = f;
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+
+    @NeverPropagateValue J f;
+
+    B(J f) {
+      this.f = f;
+    }
+  }
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface I {
+    void printI();
+  }
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface J {
+    void printJ();
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class IImpl implements I {
+    @NeverInline
+    public void printI() {
+      System.out.println("I");
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class JImpl implements J {
+    @NeverInline
+    public void printJ() {
+      System.out.println("J");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
index 385a3fe..3858a1e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
@@ -6,45 +6,72 @@
 
 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.onlyIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.BooleanUtils;
 import java.lang.annotation.Annotation;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.util.List;
 import org.junit.Test;
+import org.junit.runners.Parameterized;
 
 public class NoClassesOrMembersWithAnnotationsTest extends HorizontalClassMergingTestBase {
 
-  public NoClassesOrMembersWithAnnotationsTest(TestParameters parameters) {
+  private final boolean enableProguardCompatibilityMode;
+
+  @Parameterized.Parameters(name = "{1}, compat: {0}")
+  public static List<Object[]> parameters() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public NoClassesOrMembersWithAnnotationsTest(
+      boolean enableProguardCompatibilityMode, TestParameters parameters) {
     super(parameters);
+    this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
   }
 
   @Test
   public void testR8() throws Exception {
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .addHorizontallyMergedClassesInspector(
-            inspector -> inspector.assertIsCompleteMergeGroup(A.class, C.class))
+            inspector -> {
+              if (enableProguardCompatibilityMode) {
+                inspector.assertIsCompleteMergeGroup(A.class, C.class);
+              } else {
+                inspector.assertIsCompleteMergeGroup(A.class, B.class, C.class);
+              }
+            })
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(
-            "a", "b", "c", "foo", "null", "annotation 2", "annotation 1", "annotation 2")
+        .applyIf(
+            enableProguardCompatibilityMode,
+            result ->
+                result.assertSuccessWithOutputLines(
+                    "a", "b", "c", "foo", "null", "annotation 2", "annotation 1", "annotation 2"),
+            result ->
+                result.assertSuccessWithOutputLines("a", "b", "c", "foo", "null", "annotation 2"))
         .inspect(
             codeInspector -> {
               assertThat(codeInspector.clazz(TypeAnnotation.class), isPresent());
               assertThat(codeInspector.clazz(MethodAnnotation.class), isPresent());
               assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(codeInspector.clazz(B.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(B.class),
+                  onlyIf(enableProguardCompatibilityMode, isPresent()));
               assertThat(codeInspector.clazz(C.class), isAbsent());
             });
   }
@@ -110,14 +137,18 @@
               return null;
             }
           });
-      System.out.println(
-          b.getClass().getAnnotations()[0].toString().replaceFirst(".*", "annotation 1"));
-      System.out.println(
-          c.getClass()
-              .getMethods()[0]
-              .getAnnotations()[0]
-              .toString()
-              .replaceFirst(".*", "annotation 2"));
+      if (b.getClass().getAnnotations().length > 0) {
+        System.out.println(
+            b.getClass().getAnnotations()[0].toString().replaceFirst(".*", "annotation 1"));
+      }
+      if (c.getClass().getMethods()[0].getAnnotations().length > 0) {
+        System.out.println(
+            c.getClass()
+                .getMethods()[0]
+                .getAnnotations()[0]
+                .toString()
+                .replaceFirst(".*", "annotation 2"));
+      }
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java
index 795837e..c3a7cfc 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.classmerging.vertical;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 
@@ -41,7 +40,7 @@
 
   @Test
   public void testR8() throws Exception {
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend())
         .addInnerClasses(VerticalClassMergingWithNonVisibleAnnotationTestClasses.class)
         .addProgramClasses(Sub.class)
         .setMinApi(parameters.getApiLevel())
@@ -50,14 +49,14 @@
             VerticalClassMergingWithNonVisibleAnnotationTestClasses.class.getTypeName()
                 + "$Private* { *; }")
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .addVerticallyMergedClassesInspector(
+            inspector -> inspector.assertMergedIntoSubtype(Base.class))
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .run(parameters.getRuntime(), Sub.class)
         .assertSuccessWithOutputLines("Base::foo()", "Sub::bar()")
         .inspect(
             codeInspector -> {
-              // Assert that base has been vertically merged into Sub.
-              assertThat(codeInspector.clazz(Base.class), not(isPresent()));
               ClassSubject sub = codeInspector.clazz(Sub.class);
               assertThat(sub, isPresent());
               // Assert that the merged class has no annotations from Base
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
new file mode 100644
index 0000000..e705ca6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java
@@ -0,0 +1,259 @@
+// 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.desugaredlibrary;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.errors.DesugaredLibraryMismatchDiagnostic;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.nio.file.Path;
+import java.util.Collection;
+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 DesugaredLibraryMismatchTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final AndroidApiLevel apiLevel;
+
+  @Parameters(name = "API level: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withNoneRuntime().build(),
+        new AndroidApiLevel[] {
+          AndroidApiLevel.LATEST, AndroidApiLevel.O, AndroidApiLevel.N_MR1, AndroidApiLevel.B
+        });
+  }
+
+  public DesugaredLibraryMismatchTest(TestParameters parameters, AndroidApiLevel apiLevel) {
+    this.parameters = parameters;
+    this.apiLevel = apiLevel;
+  }
+
+  @Test
+  public void testInputDexed() throws Exception {
+    // DEX code without library desugaring.
+    Path libraryDex =
+        testForD8(Backend.DEX)
+            .addProgramClasses(Library.class)
+            .setMinApi(apiLevel)
+            .compile()
+            .writeToZip();
+
+    // Combine DEX input without library desugaring with dexing with library desugaring.
+    try {
+      testForD8()
+          .addProgramFiles(libraryDex)
+          .addProgramClasses(TestRunner.class)
+          .setMinApi(apiLevel)
+          .enableCoreLibraryDesugaring(apiLevel)
+          .compileWithExpectedDiagnostics(
+              diagnostics -> {
+                diagnostics.assertNoInfos();
+                diagnostics.assertAllWarningsMatch(
+                    diagnosticMessage(
+                        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();
+                }
+              });
+
+    } catch (CompilationFailedException e) {
+    }
+  }
+
+  @Test
+  public void testInputCfDesugared() throws Exception {
+    // CF to CF desugared code without library desugaring.
+    Path desugaredLibrary =
+        testForD8(Backend.CF)
+            .addProgramClasses(Library.class)
+            .setMinApi(apiLevel)
+            .compile()
+            .writeToZip();
+
+    // Combine CF desugared input without library desugaring with dexing with library desugaring.
+    testForD8()
+        .addProgramFiles(desugaredLibrary)
+        .addProgramClasses(TestRunner.class)
+        .setMinApi(apiLevel)
+        .enableCoreLibraryDesugaring(apiLevel)
+        .compile();
+  }
+
+  @Test
+  public void testInputCfDesugaredAndDexed() throws Exception {
+    // CF to CF desugared code without library desugaring.
+    Path desugaredLibrary =
+        testForD8(Backend.CF)
+            .addProgramClasses(Library.class)
+            .setMinApi(apiLevel)
+            .compile()
+            .writeToZip();
+
+    // CF to CF desugared code without library desugaring compiled to DEX.
+    Path desugaredLibraryDex =
+        testForD8(Backend.DEX)
+            .addProgramFiles(desugaredLibrary)
+            .setMinApi(apiLevel)
+            .disableDesugaring()
+            .compile()
+            .writeToZip();
+
+    testForD8()
+        .addProgramFiles(desugaredLibraryDex)
+        .addProgramClasses(TestRunner.class)
+        .setMinApi(apiLevel)
+        .enableCoreLibraryDesugaring(apiLevel)
+        .compile();
+  }
+
+  @Test
+  public void testCfInputLibraryDesugared() throws Exception {
+    // CF to CF desugared code with library desugaring.
+    Path desugaredLibrary =
+        testForD8(Backend.CF)
+            .addProgramClasses(Library.class)
+            .setMinApi(apiLevel)
+            .enableCoreLibraryDesugaring(apiLevel)
+            .compile()
+            .writeToZip();
+
+    // Combine CF input with library desugaring with dexing without library desugaring.
+    try {
+      testForD8()
+          .addProgramFiles(desugaredLibrary)
+          .addProgramClasses(TestRunner.class)
+          .setMinApi(apiLevel)
+          .compileWithExpectedDiagnostics(
+              diagnostics -> {
+                if (apiLevel.isLessThan(AndroidApiLevel.O)) {
+                  diagnostics.assertOnlyErrors();
+                  diagnostics.assertErrorsMatch(
+                      diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
+                } else {
+                  diagnostics.assertNoMessages();
+                }
+              });
+    } catch (CompilationFailedException e) {
+    }
+  }
+
+  @Test
+  public void testMergeLibraryDesugaredWithNotLibraryDesugared() throws Exception {
+    // DEX code with library desugaring.
+    Path libraryDex =
+        testForD8(Backend.DEX)
+            .addProgramClasses(Library.class)
+            .setMinApi(apiLevel)
+            .enableCoreLibraryDesugaring(apiLevel)
+            .compile()
+            .writeToZip();
+
+    // DEX code without library desugaring.
+    Path programDex =
+        testForD8(Backend.DEX)
+            .addProgramClasses(TestRunner.class)
+            .setMinApi(apiLevel)
+            .compile()
+            .writeToZip();
+
+    try {
+      testForD8()
+          .addProgramFiles(libraryDex)
+          .addProgramFiles(programDex)
+          .setMinApi(apiLevel)
+          .compileWithExpectedDiagnostics(
+              diagnostics -> {
+                if (apiLevel.isLessThan(AndroidApiLevel.O)) {
+                  diagnostics.assertOnlyErrors();
+                  diagnostics.assertErrorsMatch(
+                      diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
+                } else {
+                  diagnostics.assertNoMessages();
+                }
+              });
+    } catch (CompilationFailedException e) {
+    }
+  }
+
+  @Test
+  public void testMergeDifferentLibraryDesugarVersions() throws Exception {
+    // DEX code with library desugaring using a desugared library configuration with a
+    // different identifier.
+    String identifier = "my-identifier";
+    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)
+            .compile()
+            .writeToZip();
+
+    // DEX code without library desugaring.
+    Path programDex =
+        testForD8(Backend.DEX)
+            .addProgramClasses(TestRunner.class)
+            .setMinApi(apiLevel)
+            .compile()
+            .writeToZip();
+
+    try {
+      testForD8()
+          .addProgramFiles(libraryDex)
+          .addProgramFiles(programDex)
+          .setMinApi(apiLevel)
+          .compileWithExpectedDiagnostics(
+              diagnostics -> {
+                if (apiLevel.isLessThan(AndroidApiLevel.O)) {
+                  diagnostics.assertOnlyErrors();
+                  diagnostics.assertErrorsMatch(
+                      allOf(
+                          diagnosticType(DesugaredLibraryMismatchDiagnostic.class),
+                          diagnosticMessage(containsString(identifier))));
+                } else {
+                  diagnostics.assertNoMessages();
+                }
+              });
+    } catch (CompilationFailedException e) {
+    }
+  }
+
+  static class Library {}
+
+  static class TestRunner {
+
+    public static void main(String[] args) {
+      System.out.println(Library.class);
+    }
+  }
+}
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 7c46fe3..be9ae1a 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
@@ -160,31 +160,27 @@
 
     // Build an app using library desugaring merging with library not using library desugaring.
     Path app;
-    try {
-      app =
-          testForD8()
-              .addProgramFiles(buildPart1DesugaredLibrary(), desugaredLibDex)
-              .setMinApi(parameters.getApiLevel())
-              .compile()
-              .writeToZip();
+    app =
+        testForD8()
+            .addProgramFiles(buildPart1DesugaredLibrary(), desugaredLibDex)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .writeToZip();
 
-      // When there is no class-file resources we are adding the marker for the last compilation.
-      assertMarkersMatch(
-          ExtractMarker.extractMarkerFromDexFile(app),
-          ImmutableList.of(
-              markerMatcher,
-              allOf(
-                  markerTool(Tool.D8),
-                  markerCompilationMode(CompilationMode.DEBUG),
-                  markerBackend(Backend.DEX),
-                  markerIsDesugared(),
-                  markerMinApi(parameters.getApiLevel()),
-                  not(markerHasDesugaredLibraryIdentifier()))));
-    } catch (CompilationFailedException e) {
-      assertTrue(someLibraryDesugaringRequired());
-      return;
-    }
-    assert !someLibraryDesugaringRequired();
+    // When there is no class-file resources we are adding the marker for the last compilation.
+    assertMarkersMatch(
+        ExtractMarker.extractMarkerFromDexFile(app),
+        ImmutableList.of(
+            markerMatcher,
+            allOf(
+                markerTool(Tool.D8),
+                markerCompilationMode(CompilationMode.DEBUG),
+                markerBackend(Backend.DEX),
+                markerIsDesugared(),
+                markerMinApi(parameters.getApiLevel()),
+                someLibraryDesugaringRequired()
+                    ? markerHasDesugaredLibraryIdentifier()
+                    : not(markerHasDesugaredLibraryIdentifier()))));
   }
 
   private void assertError(TestDiagnosticMessages m) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SimpleStreamTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SimpleStreamTest.java
new file mode 100644
index 0000000..aec1045
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SimpleStreamTest.java
@@ -0,0 +1,84 @@
+// 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.desugaredlibrary;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SimpleStreamTest extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED_RESULT = StringUtils.lines("3");
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public SimpleStreamTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testStreamD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addInnerClasses(SimpleStreamTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testStreamR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(Backend.DEX)
+        .addInnerClasses(SimpleStreamTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepClassAndMembersRules(Executor.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @SuppressWarnings("unchecked")
+  static class Executor {
+
+    public static void main(String[] args) {
+      ArrayList<Integer> integers = new ArrayList<>();
+      integers.add(1);
+      integers.add(2);
+      integers.add(3);
+      List<Integer> collectedList = integers.stream().map(i -> i + 3).collect(Collectors.toList());
+      System.out.println(collectedList.size());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/MethodParametersTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/MethodParametersTest.java
index 7d70d21..43373b9 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/MethodParametersTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/MethodParametersTest.java
@@ -5,18 +5,18 @@
 
 import static com.android.tools.r8.TestRuntime.getCheckedInJdk;
 import static com.android.tools.r8.TestRuntime.getCheckedInJdk11;
-import static org.hamcrest.CoreMatchers.anyOf;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.desugaring.interfacemethods.methodparameters.I;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.SupplierUtils;
 import java.nio.file.Path;
+import java.util.function.Supplier;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -25,6 +25,7 @@
 public class MethodParametersTest extends TestBase {
 
   private final TestParameters parameters;
+  private final Supplier<Path> compiledWithParameters;
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -38,16 +39,46 @@
 
   public MethodParametersTest(TestParameters parameters) {
     this.parameters = parameters;
+    compiledWithParameters =
+        SupplierUtils.memoize(
+            () ->
+                javac(
+                        parameters.isCfRuntime()
+                            ? getCheckedInJdk(parameters.getRuntime().asCf().getVm())
+                            : getCheckedInJdk11())
+                    .addSourceFiles(ToolHelper.getSourceFileForTestClass(I.class))
+                    .addOptions("-parameters")
+                    .compile());
   }
 
+  private final String EXPECTED =
+      StringUtils.lines(
+          "0", "1", "a: 1", "2", "a: 1", "b: 2", "0", "1", "a: 1", "2", "a: 1", "b: 2");
+  private final String EXPECTED_DESUGARED =
+      StringUtils.lines(
+          "1",
+          "2",
+          "_this: 0",
+          "a: 1",
+          "3",
+          "_this: 0",
+          "a: 1",
+          "b: 2",
+          "0",
+          "1",
+          "a: 1",
+          "2",
+          "a: 1",
+          "b: 2");
+
   @Test
   public void testJvm() throws Exception {
     assumeTrue(parameters.isCfRuntime());
     testForJvm()
-        .addProgramClassesAndInnerClasses(I.class)
+        .addProgramFiles(compiledWithParameters.get())
         .addInnerClasses(getClass())
         .run(parameters.getRuntime(), TestRunner.class)
-        .assertSuccessWithOutputLines("0", "1", "2", "0", "1", "2");
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   @Test
@@ -56,18 +87,9 @@
     assumeTrue(
         parameters.isDexRuntime()
             || getCheckedInJdk(parameters.getRuntime().asCf().getVm()) != null);
-    Path compiledWithParameters =
-        javac(
-                parameters.isCfRuntime()
-                    ? getCheckedInJdk(parameters.getRuntime().asCf().getVm())
-                    : getCheckedInJdk11())
-            .addSourceFiles(ToolHelper.getSourceFileForTestClass(I.class))
-            .addOptions("-parameters")
-            .compile();
-
     Path interfaceDesugared =
         testForD8(Backend.CF)
-            .addProgramFiles(compiledWithParameters)
+            .addProgramFiles(compiledWithParameters.get())
             .setMinApi(parameters.getApiLevel())
             .compile()
             .writeToZip();
@@ -77,17 +99,6 @@
             .addProgramFiles(interfaceDesugared)
             .setMinApi(parameters.getApiLevel())
             .compile()
-            // TODO(b/189743726): These warnings should not be there.
-            .applyIf(
-                parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring(),
-                TestCompileResult::assertNoInfoMessages,
-                r ->
-                    r.assertAtLeastOneInfoMessage()
-                        .assertAllInfoMessagesMatch(
-                            anyOf(
-                                containsString(
-                                    "Invalid parameter counts in MethodParameter attributes"),
-                                containsString("Methods with invalid MethodParameter attributes"))))
             .writeToZip();
 
     Path programDesugared =
@@ -103,26 +114,11 @@
         .addProgramFiles(programDesugared)
         .setMinApi(parameters.getApiLevel())
         .compile()
-        // TODO(b/189743726): These warnings should not be there.
-        .applyIf(
-            parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring(),
-            TestCompileResult::assertNoInfoMessages,
-            r ->
-                r.assertAtLeastOneInfoMessage()
-                    .assertAllInfoMessagesMatch(
-                        anyOf(
-                            containsString(
-                                "Invalid parameter counts in MethodParameter attributes"),
-                            containsString("Methods with invalid MethodParameter attributes"))))
         .run(parameters.getRuntime(), TestRunner.class)
         .applyIf(
             parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring(),
-            r -> r.assertSuccessWithOutputLines("0", "1", "2", "0", "1", "2"),
-            // TODO(b/189743726): Should not fail at runtime (but will have different parameter
-            // count for non-static methods when desugared).
-            r ->
-                r.assertFailureWithErrorThatMatches(
-                    containsString("Wrong number of parameters in MethodParameters attribute")));
+            r -> r.assertSuccessWithOutput(EXPECTED),
+            r -> r.assertSuccessWithOutput(EXPECTED_DESUGARED));
   }
 
   static class A implements I {}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/methodparameters/I.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/methodparameters/I.java
index 4c7ba69..ccf787e 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/methodparameters/I.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/methodparameters/I.java
@@ -3,29 +3,67 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugaring.interfacemethods.methodparameters;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Parameter;
+
 public interface I {
 
   default void zeroArgsDefault() {
     System.out.println(new Object() {}.getClass().getEnclosingMethod().getParameters().length);
   }
 
-  default void oneArgDefault(int a) {
-    System.out.println(new Object() {}.getClass().getEnclosingMethod().getParameters().length);
+  default void oneArgDefault(@RuntimeAnnotation1(n = 0) int a) {
+    Parameter[] parameters = new Object() {}.getClass().getEnclosingMethod().getParameters();
+    System.out.println(parameters.length);
+    for (Parameter parameter : parameters) {
+      System.out.println(parameter.getName() + ": " + parameter.getAnnotations().length);
+    }
   }
 
-  default void twoArgDefault(int a, int b) {
-    System.out.println(new Object() {}.getClass().getEnclosingMethod().getParameters().length);
+  default void twoArgDefault(
+      @RuntimeAnnotation1(n = 1) int a,
+      @RuntimeAnnotation1(n = 2) @RuntimeAnnotation2(n = 2) int b) {
+    Parameter[] parameters = new Object() {}.getClass().getEnclosingMethod().getParameters();
+    System.out.println(parameters.length);
+    for (Parameter parameter : parameters) {
+      System.out.println(parameter.getName() + ": " + parameter.getAnnotations().length);
+    }
   }
 
   static void zeroArgStatic() {
-    System.out.println(new Object() {}.getClass().getEnclosingMethod().getParameters().length);
+    Parameter[] parameters = new Object() {}.getClass().getEnclosingMethod().getParameters();
+    System.out.println(parameters.length);
+    for (Parameter parameter : parameters) {
+      System.out.println(parameter.getName() + ": " + parameter.getAnnotations().length);
+    }
   }
 
-  static void oneArgStatic(int a) {
-    System.out.println(new Object() {}.getClass().getEnclosingMethod().getParameters().length);
+  static void oneArgStatic(@RuntimeAnnotation1(n = 0) int a) {
+    Parameter[] parameters = new Object() {}.getClass().getEnclosingMethod().getParameters();
+    System.out.println(parameters.length);
+    for (Parameter parameter : parameters) {
+      System.out.println(parameter.getName() + ": " + parameter.getAnnotations().length);
+    }
   }
 
-  static void twoArgsStatic(int a, int b) {
-    System.out.println(new Object() {}.getClass().getEnclosingMethod().getParameters().length);
+  static void twoArgsStatic(
+      @RuntimeAnnotation1(n = 1) int a,
+      @RuntimeAnnotation1(n = 2) @RuntimeAnnotation2(n = 2) int b) {
+    Parameter[] parameters = new Object() {}.getClass().getEnclosingMethod().getParameters();
+    System.out.println(parameters.length);
+    for (Parameter parameter : parameters) {
+      System.out.println(parameter.getName() + ": " + parameter.getAnnotations().length);
+    }
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface RuntimeAnnotation1 {
+    int n();
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface RuntimeAnnotation2 {
+    int n();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
index 6382436..c6094ae 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
@@ -42,9 +42,8 @@
     Assume.assumeFalse(
         "The methods values and valueOf are required for reflection.",
         enumKeepRules.toString().equals("none"));
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend())
         .addInnerClasses(AnnotationEnumUnboxingTest.class)
-        .noMinification()
         .addKeepMainRule(Main.class)
         .addKeepRules(enumKeepRules.getKeepRules())
         .addKeepClassRules(ClassAnnotationDefault.class)
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
index 0326e0b..e7b5dce 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
@@ -8,9 +8,8 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.enumunboxing.DoubleProcessingEnumUnboxingTest.App.AppEnum;
 import com.android.tools.r8.enumunboxing.examplelib1.JavaLibrary1;
-import com.android.tools.r8.ir.optimize.enums.UnboxedEnumMemberRelocator;
+import com.android.tools.r8.ir.optimize.enums.SharedEnumUnboxingUtilityClass;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -78,19 +77,21 @@
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
         .setMinApi(parameters.getApiLevel())
         .compile()
-        .inspect(this::assertUtilityClassPresent)
+        .inspect(this::assertSharedUtilityClassPresent)
         .run(parameters.getRuntime(), App.class)
         .assertSuccess()
         .inspectStdOut(this::assertLines2By2Correct);
   }
 
-  private void assertUtilityClassPresent(CodeInspector codeInspector) {
+  private void assertSharedUtilityClassPresent(CodeInspector codeInspector) {
     assertTrue(
         codeInspector.allClasses().stream()
             .anyMatch(
                 c ->
                     c.getOriginalName()
-                        .contains(UnboxedEnumMemberRelocator.ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)));
+                        .contains(
+                            SharedEnumUnboxingUtilityClass
+                                .ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX)));
   }
 
   static class App {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
index 44cdff0..4ef092d 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.enumunboxing.examplelib1.JavaLibrary1;
 import com.android.tools.r8.enumunboxing.examplelib2.JavaLibrary2;
-import com.android.tools.r8.ir.optimize.enums.UnboxedEnumMemberRelocator;
+import com.android.tools.r8.ir.optimize.enums.SharedEnumUnboxingUtilityClass;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -68,7 +68,7 @@
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
         .setMinApi(parameters.getApiLevel())
         .compile()
-        .inspect(this::assertUtilityClassPresent)
+        .inspect(this::assertSharedUtilityClassPresent)
         .run(parameters.getRuntime(), App.class)
         .assertSuccess()
         .inspectStdOut(this::assertLines2By2Correct);
@@ -88,13 +88,15 @@
         .writeToZip();
   }
 
-  private void assertUtilityClassPresent(CodeInspector codeInspector) {
+  private void assertSharedUtilityClassPresent(CodeInspector codeInspector) {
     assertTrue(
         codeInspector.allClasses().stream()
             .anyMatch(
                 c ->
                     c.getOriginalName()
-                        .contains(UnboxedEnumMemberRelocator.ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)));
+                        .contains(
+                            SharedEnumUnboxingUtilityClass
+                                .ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX)));
   }
 
   static class App {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/RedundantValuesCloneEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/RedundantValuesCloneEnumUnboxingTest.java
new file mode 100644
index 0000000..82c48eb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/RedundantValuesCloneEnumUnboxingTest.java
@@ -0,0 +1,61 @@
+// 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.enumunboxing;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import 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 RedundantValuesCloneEnumUnboxingTest extends TestBase {
+
+  @Parameter public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        // Verify that there are no calls to clone().
+        .inspect(
+            inspector ->
+                inspector.forAllClasses(
+                    clazz ->
+                        clazz.forAllMethods(
+                            method -> assertThat(method, not(invokesMethodWithName("clone"))))))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      for (MyEnum e : MyEnum.values()) {
+        System.out.println(e.name());
+      }
+    }
+  }
+
+  enum MyEnum {
+    A
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/StringValueOfEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/StringValueOfEnumUnboxingTest.java
index 904fc48f..2211d08 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/StringValueOfEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/StringValueOfEnumUnboxingTest.java
@@ -59,12 +59,76 @@
     public static void main(String[] args) {
       System.out.println(MyEnum.A.ordinal());
       System.out.println(0);
+      stringValueOf();
+      stringBuilder();
+    }
+
+    private static void stringValueOf() {
       System.out.println(getString(MyEnum.A));
       System.out.println("A");
       System.out.println(getString(null));
       System.out.println("null");
     }
 
+    private static void stringBuilder() {
+      StringBuilder stringBuilder = new StringBuilder();
+      append(stringBuilder, MyEnum.A);
+      append(stringBuilder, MyEnum.B);
+      append(stringBuilder, null);
+      appendTryCatch(stringBuilder, MyEnum.A);
+      appendTryCatch(stringBuilder, MyEnum.B);
+      appendTryCatch(stringBuilder, null);
+      System.out.println(stringBuilder.toString());
+      System.out.println("ABnullABnull");
+
+      StringBuffer stringBuffer = new StringBuffer();
+      append(stringBuffer, MyEnum.A);
+      append(stringBuffer, MyEnum.B);
+      append(stringBuffer, null);
+      appendTryCatch(stringBuffer, MyEnum.A);
+      appendTryCatch(stringBuffer, MyEnum.B);
+      appendTryCatch(stringBuffer, null);
+      System.out.println(stringBuffer.toString());
+      System.out.println("ABnullABnull");
+    }
+
+    @NeverInline
+    private static StringBuilder append(StringBuilder sb, MyEnum e) {
+      return sb.append(e);
+    }
+
+    @NeverInline
+    private static StringBuffer append(StringBuffer sb, MyEnum e) {
+      return sb.append(e);
+    }
+
+    @NeverInline
+    private static StringBuilder appendTryCatch(StringBuilder sb, MyEnum e) {
+      try {
+        sb.append(e);
+        throwNull();
+      } catch (NullPointerException ignored) {
+      }
+      return sb;
+    }
+
+    @NeverInline
+    private static StringBuffer appendTryCatch(StringBuffer sb, MyEnum e) {
+      try {
+        sb.append(e);
+        throwNull();
+      } catch (NullPointerException ignored) {
+      }
+      return sb;
+    }
+
+    @NeverInline
+    private static void throwNull() {
+      if (System.currentTimeMillis() > 0) {
+        throw new NullPointerException("exception");
+      }
+    }
+
     @NeverInline
     private static String getString(MyEnum e) {
       return String.valueOf(e);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationPinnedMethodOverridePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationPinnedMethodOverridePropagationTest.java
new file mode 100644
index 0000000..9e42eb6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationPinnedMethodOverridePropagationTest.java
@@ -0,0 +1,166 @@
+// 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.optimize.callsites;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+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 CallSiteOptimizationPinnedMethodOverridePropagationTest extends TestBase {
+
+  private static final String CLASS_PREFIX =
+      "com.android.tools.r8.ir.optimize.callsites.CallSiteOptimizationPinnedMethodOverridePropagationTest$";
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public CallSiteOptimizationPinnedMethodOverridePropagationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    R8TestCompileResult compiled =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(
+                Arg.class, Arg1.class, Arg2.class, Call.class, CallImpl.class, Main2.class)
+            .addKeepRules(
+                ImmutableList.of(
+                    "-keep interface " + CLASS_PREFIX + "Arg",
+                    "-keep interface "
+                        + CLASS_PREFIX
+                        + "Call { \npublic void print("
+                        + CLASS_PREFIX
+                        + "Arg); \n}",
+                    "-keep class "
+                        + CLASS_PREFIX
+                        + "Main2 { \npublic static void main(java.lang.String[]); \npublic static "
+                        + CLASS_PREFIX
+                        + "Arg getArg1(); \npublic static "
+                        + CLASS_PREFIX
+                        + "Arg getArg2(); \npublic static "
+                        + CLASS_PREFIX
+                        + "Call getCaller(); \n}"))
+            .enableNoVerticalClassMergingAnnotations()
+            .enableNoHorizontalClassMergingAnnotations()
+            .enableInliningAnnotations()
+            .enableMemberValuePropagationAnnotations()
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+    CodeInspector inspector = compiled.inspector();
+    compiled.run(parameters.getRuntime(), Main2.class).assertSuccessWithOutputLines("Arg1");
+    testForD8()
+        .addProgramClasses(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addRunClasspathFiles(compiled.writeToZip())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Arg1", "Arg2");
+  }
+
+  // Kept
+  @NoVerticalClassMerging
+  interface Arg {
+
+    @NeverInline
+    @NeverPropagateValue
+    String getString();
+  }
+
+  @NoVerticalClassMerging
+  @NoHorizontalClassMerging
+  static class Arg1 implements Arg {
+
+    @Override
+    @NeverInline
+    @NeverPropagateValue
+    public String getString() {
+      return "Arg1";
+    }
+  }
+
+  @NoVerticalClassMerging
+  @NoHorizontalClassMerging
+  static class Arg2 implements Arg {
+
+    @Override
+    @NeverInline
+    @NeverPropagateValue
+    public String getString() {
+      return "Arg2";
+    }
+  }
+
+  @NoVerticalClassMerging
+  interface Call {
+
+    // Kept.
+    @NeverInline
+    @NeverPropagateValue
+    void print(Arg arg);
+  }
+
+  @NoVerticalClassMerging
+  static class CallImpl implements Call {
+
+    @Override
+    @NeverInline
+    @NeverPropagateValue
+    public void print(Arg arg) {
+      System.out.println(arg.getString());
+    }
+  }
+
+  @NoVerticalClassMerging
+  static class Main2 {
+
+    // Kept.
+    public static void main(String[] args) {
+      // This would propagate Arg1 to print while it should not.
+      getCaller().print(new Arg1());
+    }
+
+    // Kept.
+    public static Arg getArg1() {
+      return new Arg1();
+    }
+
+    // Kept.
+    public static Arg getArg2() {
+      return new Arg2();
+    }
+
+    // Kept.
+    public static Call getCaller() {
+      return new CallSiteOptimizationPinnedMethodOverridePropagationTest.CallImpl();
+    }
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      Arg arg1 = Main2.getArg1();
+      Arg arg2 = Main2.getArg2();
+      Call caller = Main2.getCaller();
+      caller.print(arg1);
+      caller.print(arg2);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
index 2e3904a..339bbbc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
@@ -36,35 +36,42 @@
 @RunWith(Parameterized.class)
 public class UninstantiatedAnnotatedArgumentsTest extends TestBase {
 
+  private final boolean enableProguardCompatibilityMode;
   private final boolean keepUninstantiatedArguments;
   private final TestParameters parameters;
 
-  @Parameters(name = "{1}, keep uninstantiated arguments: {0}")
+  @Parameters(name = "{2}, compat: {0}, keep uninstantiated arguments: {1}")
   public static List<Object[]> params() {
-    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+    return buildParameters(
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
   public UninstantiatedAnnotatedArgumentsTest(
-      boolean keepUninstantiatedArguments, TestParameters parameters) {
+      boolean enableProguardCompatibilityMode,
+      boolean keepUninstantiatedArguments,
+      TestParameters parameters) {
+    this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
     this.keepUninstantiatedArguments = keepUninstantiatedArguments;
     this.parameters = parameters;
   }
 
   @Test
   public void test() throws Exception {
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
         .addInnerClasses(UninstantiatedAnnotatedArgumentsTest.class)
         .addConstantArgumentAnnotations()
         .addKeepMainRule(TestClass.class)
         .addKeepClassRules(Instantiated.class, Uninstantiated.class)
-        .addKeepAttributes("RuntimeVisibleParameterAnnotations")
+        .addKeepRuntimeVisibleParameterAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableConstantArgumentAnnotations(keepUninstantiatedArguments)
         .enableInliningAnnotations()
         .enableUnusedArgumentAnnotations()
         // TODO(b/123060011): Mapping not working in presence of argument removal.
         .minification(keepUninstantiatedArguments)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::verifyOutput)
         .run(parameters.getRuntime(), TestClass.class)
@@ -94,38 +101,35 @@
       assertThat(methodSubject, isPresent());
 
       // TODO(b/131735725): Should also remove arguments from the virtual methods.
-      if (keepUninstantiatedArguments || methodSubject.getOriginalName().contains("Virtual")) {
-        assertEquals(3, methodSubject.getMethod().getReference().proto.parameters.size());
-        assertEquals(3, methodSubject.getMethod().parameterAnnotationsList.size());
+      boolean shouldHaveArgumentRemoval =
+          keepUninstantiatedArguments || methodSubject.getOriginalName().contains("Virtual");
+      if (shouldHaveArgumentRemoval) {
+        assertEquals(3, methodSubject.getMethod().getParameters().size());
 
-        for (int i = 0; i < 3; ++i) {
-          DexAnnotationSet annotationSet =
-              methodSubject.getMethod().parameterAnnotationsList.get(i);
-          assertEquals(1, annotationSet.annotations.length);
-
-          DexAnnotation annotation = annotationSet.annotations[0];
-          if (i == getPositionOfUnusedArgument(methodSubject)) {
-            assertEquals(
-                uninstantiatedClassSubject.getFinalName(),
-                annotation.annotation.type.toSourceString());
-          } else {
-            assertEquals(
-                instantiatedClassSubject.getFinalName(),
-                annotation.annotation.type.toSourceString());
-          }
-        }
+        // In non-compat mode, R8 removes annotations from non-pinned items.
+        assertEquals(
+            enableProguardCompatibilityMode ? 3 : 0,
+            methodSubject.getMethod().getParameterAnnotations().size());
       } else {
         assertEquals(2, methodSubject.getMethod().getReference().proto.parameters.size());
-        assertEquals(2, methodSubject.getMethod().parameterAnnotationsList.size());
+        assertEquals(
+            enableProguardCompatibilityMode ? 2 : 0,
+            methodSubject.getMethod().getParameterAnnotations().size());
+      }
 
-        for (int i = 0; i < 2; ++i) {
-          DexAnnotationSet annotationSet =
-              methodSubject.getMethod().parameterAnnotationsList.get(i);
-          assertEquals(1, annotationSet.annotations.length);
+      for (int i = 0; i < methodSubject.getMethod().getParameterAnnotations().size(); ++i) {
+        DexAnnotationSet annotationSet = methodSubject.getMethod().getParameterAnnotation(i);
+        assertEquals(1, annotationSet.size());
 
-          DexAnnotation annotation = annotationSet.annotations[0];
+        DexAnnotation annotation = annotationSet.getFirst();
+        if (shouldHaveArgumentRemoval && i == getPositionOfUnusedArgument(methodSubject)) {
           assertEquals(
-              instantiatedClassSubject.getFinalName(), annotation.annotation.type.toSourceString());
+              uninstantiatedClassSubject.getFinalName(),
+              annotation.getAnnotationType().getTypeName());
+        } else {
+          assertEquals(
+              instantiatedClassSubject.getFinalName(),
+              annotation.getAnnotationType().getTypeName());
         }
       }
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
index 955c5d4..7cf6193 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
@@ -35,22 +35,30 @@
 @RunWith(Parameterized.class)
 public class UnusedAnnotatedArgumentsTest extends TestBase {
 
+  private final boolean enableProguardCompatibilityMode;
   private final boolean keepUnusedArguments;
   private final TestParameters parameters;
 
-  @Parameters(name = "{1}, keep unused arguments: {0}")
+  @Parameters(name = "{2}, compat: {0}, keep unused arguments: {1}")
   public static List<Object[]> params() {
-    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+    return buildParameters(
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
-  public UnusedAnnotatedArgumentsTest(boolean keepUnusedArguments, TestParameters parameters) {
+  public UnusedAnnotatedArgumentsTest(
+      boolean enableProguardCompatibilityMode,
+      boolean keepUnusedArguments,
+      TestParameters parameters) {
+    this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
     this.keepUnusedArguments = keepUnusedArguments;
     this.parameters = parameters;
   }
 
   @Test
   public void test() throws Exception {
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
         .addInnerClasses(UnusedAnnotatedArgumentsTest.class)
         .addUnusedArgumentAnnotations()
         .addKeepMainRule(TestClass.class)
@@ -61,7 +69,7 @@
         .enableUnusedArgumentAnnotations(keepUnusedArguments)
         // TODO(b/123060011): Mapping not working in presence of unused argument removal.
         .minification(keepUnusedArguments)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::verifyOutput)
         .run(parameters.getRuntime(), TestClass.class)
@@ -90,36 +98,24 @@
     for (MethodSubject methodSubject : methodSubjects) {
       assertThat(methodSubject, isPresent());
 
-      if (keepUnusedArguments) {
-        assertEquals(3, methodSubject.getMethod().getReference().proto.parameters.size());
-        assertEquals(3, methodSubject.getMethod().parameterAnnotationsList.size());
+      assertEquals(keepUnusedArguments ? 3 : 2, methodSubject.getMethod().getParameters().size());
 
-        for (int i = 0; i < 3; ++i) {
-          DexAnnotationSet annotationSet =
-              methodSubject.getMethod().parameterAnnotationsList.get(i);
-          assertEquals(1, annotationSet.annotations.length);
+      // R8 non-compat removes annotations from non-pinned items.
+      assertEquals(
+          enableProguardCompatibilityMode ? methodSubject.getMethod().getParameters().size() : 0,
+          methodSubject.getMethod().getParameterAnnotations().size());
 
-          DexAnnotation annotation = annotationSet.annotations[0];
-          if (i == getPositionOfUnusedArgument(methodSubject)) {
-            assertEquals(
-                unusedClassSubject.getFinalName(), annotation.annotation.type.toSourceString());
-          } else {
-            assertEquals(
-                usedClassSubject.getFinalName(), annotation.annotation.type.toSourceString());
-          }
-        }
-      } else {
-        assertEquals(2, methodSubject.getMethod().getReference().proto.parameters.size());
-        assertEquals(2, methodSubject.getMethod().parameterAnnotationsList.size());
+      for (int i = 0; i < methodSubject.getMethod().getParameterAnnotations().size(); ++i) {
+        DexAnnotationSet annotationSet = methodSubject.getMethod().getParameterAnnotation(i);
+        assertEquals(1, annotationSet.size());
 
-        for (int i = 0; i < 2; ++i) {
-          DexAnnotationSet annotationSet =
-              methodSubject.getMethod().parameterAnnotationsList.get(i);
-          assertEquals(1, annotationSet.annotations.length);
-
-          DexAnnotation annotation = annotationSet.annotations[0];
+        DexAnnotation annotation = annotationSet.annotations[0];
+        if (keepUnusedArguments && i == getPositionOfUnusedArgument(methodSubject)) {
           assertEquals(
-              usedClassSubject.getFinalName(), annotation.annotation.type.toSourceString());
+              unusedClassSubject.getFinalName(), annotation.getAnnotationType().toSourceString());
+        } else {
+          assertEquals(
+              usedClassSubject.getFinalName(), annotation.getAnnotationType().toSourceString());
         }
       }
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java
index 49b90e7..c008972 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java
@@ -11,10 +11,11 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 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;
@@ -28,14 +29,18 @@
 public class UnusedAnnotatedArgumentsWithMissingAnnotationsTest extends TestBase
     implements Opcodes {
 
+  private final boolean enableProguardCompatibilityMode;
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameterized.Parameters(name = "{1}, compat: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
-  public UnusedAnnotatedArgumentsWithMissingAnnotationsTest(TestParameters parameters) {
+  public UnusedAnnotatedArgumentsWithMissingAnnotationsTest(
+      boolean enableProguardCompatibilityMode, TestParameters parameters) {
+    this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
     this.parameters = parameters;
   }
 
@@ -43,26 +48,28 @@
     assertThat(clazz, isPresent());
     MethodSubject init = clazz.init("Test", "java.lang.String");
     assertThat(init, isPresent());
-    assertEquals(2, init.getMethod().parameterAnnotationsList.size());
-    assertTrue(init.getMethod().parameterAnnotationsList.get(0).isEmpty());
-    assertEquals(1, init.getMethod().parameterAnnotationsList.get(1).annotations.length);
-    assertEquals(
-        "L" + expectedAnnotationClass + ";",
-        init.getMethod()
-            .parameterAnnotationsList
-            .get(1)
-            .annotations[0]
-            .annotation
-            .type
-            .descriptor
-            .toString());
+    if (enableProguardCompatibilityMode) {
+      assertEquals(2, init.getMethod().getParameterAnnotations().size());
+      assertTrue(init.getMethod().getParameterAnnotation(0).isEmpty());
+      assertEquals(1, init.getMethod().getParameterAnnotation(1).size());
+      assertEquals(
+          "L" + expectedAnnotationClass + ";",
+          init.getMethod()
+              .getParameterAnnotation(1)
+              .annotations[0]
+              .annotation
+              .type
+              .toDescriptorString());
+    } else {
+      assertTrue(init.getMethod().getParameterAnnotations().isEmpty());
+    }
   }
 
   @Test
   public void test() throws Exception {
     String expectedOutput =
         StringUtils.lines("In Inner1() used", "In Inner2() used", "In Inner3() used", "In main()");
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
         .addProgramClassFileData(
             dumpTest(),
             dumpInner1(),
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexRemovedAnnotationIfTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexRemovedAnnotationIfTest.java
new file mode 100644
index 0000000..5e0fa6b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexRemovedAnnotationIfTest.java
@@ -0,0 +1,86 @@
+// 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.maindexlist;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/190623364.
+@RunWith(Parameterized.class)
+public class MainDexRemovedAnnotationIfTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+        .build();
+  }
+
+  public MainDexRemovedAnnotationIfTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testMainDexTracing() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(MainDex.class, Inside.class, Main.class)
+        .addKeepClassAndMembersRules(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableInliningAnnotations()
+        .addMainDexRules(
+            "-if @" + MainDex.class.getTypeName() + " class *", "-keep class <1> { *; }")
+        .collectMainDexClasses()
+        .compile()
+        .inspectMainDexClasses(
+            mainDexClasses -> {
+              // TODO(b/190623364): Should not be empty.
+              assertTrue(mainDexClasses.isEmpty());
+            })
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(MainDex.class), not(isPresent()));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World!");
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.TYPE})
+  public @interface MainDex {}
+
+  @MainDex
+  public static class Inside {
+
+    @NeverInline
+    public static void foo() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      Inside.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexRemovedAnnotationTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexRemovedAnnotationTest.java
new file mode 100644
index 0000000..67b113c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexRemovedAnnotationTest.java
@@ -0,0 +1,85 @@
+// 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.maindexlist;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/190623364.
+@RunWith(Parameterized.class)
+public class MainDexRemovedAnnotationTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+        .build();
+  }
+
+  public MainDexRemovedAnnotationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testMainDexTracing() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(MainDex.class, Inside.class, Main.class)
+        .addKeepClassAndMembersRules(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableInliningAnnotations()
+        .addMainDexRules("-keep @" + MainDex.class.getTypeName() + " class * { *; }")
+        .collectMainDexClasses()
+        .compile()
+        .inspectMainDexClasses(
+            mainDexClasses -> {
+              // TODO(b/190623364): Should not be empty.
+              assertTrue(mainDexClasses.isEmpty());
+            })
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(MainDex.class), not(isPresent()));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World!");
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.TYPE})
+  public @interface MainDex {}
+
+  @MainDex
+  public static class Inside {
+
+    @NeverInline
+    public static void foo() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      Inside.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
index cc558a1..5ed7de6 100644
--- a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
+++ b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
@@ -171,8 +171,7 @@
         .inspector();
   }
 
-  private CodeInspector compileR8(Class... classes)
-      throws CompilationFailedException, IOException, ExecutionException {
+  private CodeInspector compileR8(Class... classes) throws CompilationFailedException, IOException {
     List<String> keepRules =
         Arrays.stream(classes)
             .map(c -> "-keep class " + c.getCanonicalName() + " { <methods>; }")
@@ -186,11 +185,16 @@
         // Keep the input class and its methods.
         .addKeepRules(keepRules)
         // Keep the annotation class.
-        .addKeepRules("-keep class dalvik.annotation.optimization.ReachabilitySensitive")
+        .addKeepRules(
+            "-keep class dalvik.annotation.optimization.ReachabilitySensitive",
+            "-keep,allowshrinking,allowobfuscation class * {",
+            "  @dalvik.annotation.optimization.ReachabilitySensitive <fields>;",
+            "  @dalvik.annotation.optimization.ReachabilitySensitive <methods>;",
+            "}")
         // Keep the annotation so R8 can find it and honor it. It also needs to be available
         // at runtime so that the Art runtime can honor it as well, so if it is not kept we
         // do not have to honor it as the runtime will not know to do so in any case.
-        .addKeepRules("-keepattributes RuntimeVisibleAnnotations")
+        .addKeepRuntimeVisibleAnnotations()
         .compile()
         .inspector();
   }
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageAnnotationTest.java
index 61526d4..fe19c51 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageAnnotationTest.java
@@ -4,29 +4,61 @@
 
 package com.android.tools.r8.repackage;
 
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.google.common.collect.ImmutableList;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+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 RepackageAnnotationTest extends RepackageTestBase {
 
-  private final String EXPECTED = "Hello World";
+  private static final String EXPECTED = "Hello World";
+  private static final String EXPECTED_WITH_ANNOTATION_REMOVAL = "null";
+
+  private final boolean enableProguardCompatibilityMode;
+  private final boolean keepAllowShrinking;
+
+  @Parameters(name = "{3}, compat: {0}, keep: {1}, kind: {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
 
   public RepackageAnnotationTest(
-      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+      boolean enableProguardCompatibilityMode,
+      boolean keepAllowShrinking,
+      String flattenPackageHierarchyOrRepackageClasses,
+      TestParameters parameters) {
     super(flattenPackageHierarchyOrRepackageClasses, parameters);
+    this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
+    this.keepAllowShrinking = keepAllowShrinking;
   }
 
   @Test
   public void testRuntime() throws Exception {
+    assumeFalse(enableProguardCompatibilityMode);
+    assumeFalse(keepAllowShrinking);
+    assumeFalse(isFlattenPackageHierarchy());
     testForRuntime(parameters)
         .addProgramClasses(Main.class, Annotation.class, A.class)
         .run(parameters.getRuntime(), Main.class)
@@ -35,8 +67,17 @@
 
   @Test
   public void testR8() throws Exception {
-    testForR8(parameters.getBackend())
+    assumeTrue(!enableProguardCompatibilityMode || !keepAllowShrinking);
+    testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
         .addProgramClasses(Main.class, A.class, Annotation.class)
+        .applyIf(
+            keepAllowShrinking,
+            builder -> {
+              // Add a keep rule to ensure annotation is retained with R8 non-compat.
+              assertFalse(enableProguardCompatibilityMode);
+              builder.addKeepRules(
+                  "-keep,allowshrinking,allowobfuscation class " + A.class.getTypeName());
+            })
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
@@ -47,8 +88,13 @@
             "  *;",
             "}")
         .apply(this::configureRepackaging)
+        .compile()
+        .inspect(inspector -> assertThat(Annotation.class, isRepackaged(inspector)))
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(EXPECTED);
+        .assertSuccessWithOutputLines(
+            enableProguardCompatibilityMode || keepAllowShrinking
+                ? EXPECTED
+                : EXPECTED_WITH_ANNOTATION_REMOVAL);
   }
 
   public static class Main {
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateClassAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateClassAnnotationTest.java
index 4672079..34cd1af 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateClassAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateClassAnnotationTest.java
@@ -30,7 +30,7 @@
 
   @Test
   public void test() throws Exception {
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
         .addKeepClassRules(NonPublicKeptAnnotation.class)
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldAnnotationTest.java
index 46a83ff..af48e82 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldAnnotationTest.java
@@ -31,7 +31,7 @@
 
   @Test
   public void test() throws Exception {
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
         .addKeepClassRules(NonPublicKeptAnnotation.class)
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodAnnotationTest.java
index 068cc89..20f8704 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodAnnotationTest.java
@@ -30,7 +30,7 @@
 
   @Test
   public void test() throws Exception {
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
         .addKeepClassRules(NonPublicKeptAnnotation.class)
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodParameterAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodParameterAnnotationTest.java
index c7cd698..d9833ae 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodParameterAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodParameterAnnotationTest.java
@@ -30,7 +30,7 @@
 
   @Test
   public void test() throws Exception {
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
         .addKeepClassRules(NonPublicKeptAnnotation.class)
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 33dfbaf..926b2c0 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -279,7 +279,7 @@
                     + " *** *(...); } "
                     + "-keep class <1> { @"
                     + PRESENT_ANNOTATION
-                    + " *** <2>(...); }")
+                    + " <2> <3>(...); }")
             .addDontWarnGoogle()
             .addDontWarnJavaxNullableAnnotation()
             .apply(this::configureHorizontalClassMerging)
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AlwaysRetainRetentionAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AlwaysRetainRetentionAnnotationTest.java
new file mode 100644
index 0000000..6e25e4ae
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/AlwaysRetainRetentionAnnotationTest.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2018, 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.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.shaking.enums.EnumInAnnotationTest.MyAnnotation;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import 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 AlwaysRetainRetentionAnnotationTest extends TestBase {
+
+  private final boolean enableProguardCompatibilityMode;
+  private final boolean keepAllowShrinking;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{2}, compat: {0}, keep: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public AlwaysRetainRetentionAnnotationTest(
+      boolean enableProguardCompatibilityMode,
+      boolean keepAllowShrinking,
+      TestParameters parameters) {
+    this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
+    this.keepAllowShrinking = keepAllowShrinking;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    assumeTrue(!enableProguardCompatibilityMode || !keepAllowShrinking);
+    testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addKeepRuntimeVisibleAnnotations()
+        .applyIf(
+            keepAllowShrinking,
+            builder -> {
+              assertFalse(enableProguardCompatibilityMode);
+              builder.addKeepRules(
+                  "-keep,allowshrinking,allowobfuscation class "
+                      + MyAnnotation.class.getTypeName());
+            })
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject annotationClassSubject = inspector.clazz(MyAnnotation.class);
+              assertThat(annotationClassSubject, isPresent());
+
+              AnnotationSubject retentionAnnotationSubject =
+                  annotationClassSubject.annotation(Retention.class.getTypeName());
+              assertThat(retentionAnnotationSubject, isPresent());
+
+              AnnotationSubject targetAnnotationSubject =
+                  annotationClassSubject.annotation(Target.class.getTypeName());
+              assertThat(targetAnnotationSubject, onlyIf(shouldOnlyRetainRetention(), isAbsent()));
+
+              AnnotationSubject myAnnotationAnnotationSubject =
+                  annotationClassSubject.annotation(MyAnnotation.class.getTypeName());
+              assertThat(
+                  myAnnotationAnnotationSubject, onlyIf(shouldOnlyRetainRetention(), isAbsent()));
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines(shouldOnlyRetainRetention() ? "1" : "3");
+  }
+
+  private boolean shouldOnlyRetainRetention() {
+    return !enableProguardCompatibilityMode && !keepAllowShrinking;
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(MyAnnotation.class.getAnnotations().length);
+    }
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.TYPE})
+  @MyAnnotation
+  @interface MyAnnotation {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationShakingBehaviorTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationShakingBehaviorTest.java
index 9c98c6e..e98541c 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationShakingBehaviorTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationShakingBehaviorTest.java
@@ -59,6 +59,10 @@
         .addProgramClasses(Factory.class, MainWithMethodAnnotation.class, C.class)
         .addKeepMainRule(MainWithMethodAnnotation.class)
         .addKeepClassAndMembersRules(Factory.class)
+        .addKeepRules(
+            "-keepclassmembers,allowobfuscation,allowshrinking class "
+                + MainWithMethodAnnotation.class.getTypeName()
+                + "{void test();}")
         .addKeepAttributes("*Annotation*")
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -123,7 +127,7 @@
       test();
     }
 
-    @Factory(ref = C.class) // <-- We are not explicitly saying that test() should be kept.
+    @Factory(ref = C.class) // <-- We are explicitly saying that test() should be kept.
     @NeverInline
     public static void test() {
       System.out.println("Hello World!");
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
index 9246164..d737c41 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
@@ -4,13 +4,16 @@
 
 package com.android.tools.r8.shaking.annotations;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeTrue;
+
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.lang.annotation.Annotation;
 import java.lang.annotation.ElementType;
@@ -18,6 +21,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import java.lang.reflect.Method;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -32,29 +36,56 @@
           "In OtherInterfaceImpl.targetedMethod()",
           MyAnnotation.class.getName());
 
+  private static final String expectedOutputWithAnnotationRemoval =
+      StringUtils.lines(
+          "In InterfaceImpl.targetedMethod()", "In OtherInterfaceImpl.targetedMethod()");
+
+  private final boolean enableProguardCompatibilityMode;
+  private final boolean keepAllowShrinking;
   private final TestParameters parameters;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{2}, compat: {0}, keep: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
-  public AnnotationsOnTargetedMethodTest(TestParameters parameters) {
+  public AnnotationsOnTargetedMethodTest(
+      boolean enableProguardCompatibilityMode,
+      boolean keepAllowShrinking,
+      TestParameters parameters) {
+    this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
+    this.keepAllowShrinking = keepAllowShrinking;
     this.parameters = parameters;
   }
 
   @Test
   public void test() throws Exception {
+    // No need to run R8 compat mode with extra -keep,allowshrinking rule.
+    assumeTrue(!enableProguardCompatibilityMode || !keepAllowShrinking);
     if (parameters.isCfRuntime()) {
       testForJvm()
           .addTestClasspath()
           .run(parameters.getRuntime(), TestClass.class)
           .assertSuccessWithOutput(expectedOutput);
     }
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
         .addInnerClasses(AnnotationsOnTargetedMethodTest.class)
         .addKeepMainRule(TestClass.class)
         .addKeepRuntimeVisibleAnnotations()
+        .applyIf(
+            keepAllowShrinking,
+            builder -> {
+              // Add extra rule to retain the annotation on Interface.targetedMethod() in non-compat
+              // mode.
+              assertFalse(enableProguardCompatibilityMode); // See assumeTrue() above.
+              builder.addKeepRules(
+                  "-keepclassmembers,allowshrinking class " + Interface.class.getTypeName() + " {",
+                  "  void targetedMethod();",
+                  "}");
+            })
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
@@ -62,7 +93,10 @@
         .noMinification()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(expectedOutput);
+        .assertSuccessWithOutput(
+            !enableProguardCompatibilityMode && !keepAllowShrinking
+                ? expectedOutputWithAnnotationRemoval
+                : expectedOutput);
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/ProgramAnnotationRemovalTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/ProgramAnnotationRemovalTest.java
index 13fbb3e..da0b781 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/ProgramAnnotationRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/ProgramAnnotationRemovalTest.java
@@ -14,6 +14,8 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -43,12 +45,21 @@
   }
 
   @Test
-  public void test() throws Exception {
+  public void testCompat() throws Exception {
+    runTest(true);
+  }
+
+  @Test
+  public void testNonCompat() throws Exception {
+    runTest(false);
+  }
+
+  private void runTest(boolean enableCompatMode) throws Exception {
     R8TestRunResult result =
-        testForR8(parameters.getBackend())
+        testForR8Compat(parameters.getBackend(), enableCompatMode)
             .addInnerClasses(ProgramAnnotationRemovalTest.class)
             .addKeepMainRule(TestClass.class)
-            .addKeepAttributes("RuntimeVisibleAnnotations")
+            .addKeepRuntimeVisibleAnnotations()
             .enableInliningAnnotations()
             .setMinApi(parameters.getApiLevel())
             .compile()
@@ -68,14 +79,19 @@
     MethodSubject methodWithLiveProgramAnnotationSubject =
         testClassSubject.uniqueMethodWithName("methodWithLiveProgramAnnotation");
     assertThat(methodWithLiveProgramAnnotationSubject, isPresent());
-    assertEquals(1, methodWithLiveProgramAnnotationSubject.getMethod().annotations().size());
+    assertEquals(
+        BooleanUtils.intValue(enableCompatMode),
+        methodWithLiveProgramAnnotationSubject.getMethod().annotations().size());
 
     MethodSubject methodWithDeadProgramAnnotationSubject =
         testClassSubject.uniqueMethodWithName("methodWithDeadProgramAnnotation");
     assertThat(methodWithDeadProgramAnnotationSubject, isPresent());
     assertEquals(0, methodWithDeadProgramAnnotationSubject.getMethod().annotations().size());
 
-    result.assertSuccessWithOutputLines("@" + liveAnnotationClassSubject.getFinalName() + "()");
+    result.applyIf(
+        enableCompatMode,
+        r -> r.assertSuccessWithOutputLines("@" + liveAnnotationClassSubject.getFinalName() + "()"),
+        TestRunResult::assertSuccessWithEmptyOutput);
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java
index 16c7d4f..187d79c 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java
@@ -8,21 +8,27 @@
 import static junit.framework.TestCase.assertTrue;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
+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.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -32,27 +38,50 @@
 @RunWith(Parameterized.class)
 public class PrunedOrMergedAnnotationTest extends TestBase {
 
+  private final boolean enableProguardCompatibilityMode;
+  private final boolean keepForAnnotations;
   private final TestParameters parameters;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{2}, compat: {0}, keep: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
-  public PrunedOrMergedAnnotationTest(TestParameters parameters) {
+  public PrunedOrMergedAnnotationTest(
+      boolean enableProguardCompatibilityMode,
+      boolean keepForAnnotations,
+      TestParameters parameters) {
+    this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
+    this.keepForAnnotations = keepForAnnotations;
     this.parameters = parameters;
   }
 
   @Test
   public void testRewritingInFactory()
       throws IOException, CompilationFailedException, ExecutionException {
-    testForR8(parameters.getBackend())
+    // No need to add extra keep rules for retaining annotations in compat mode.
+    assumeTrue(!enableProguardCompatibilityMode || !keepForAnnotations);
+    testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
         .addInnerClasses(PrunedOrMergedAnnotationTest.class)
         .addKeepMainRule(Main.class)
-        .addKeepAttributes("*Annotation*")
         .addKeepClassAndMembersRules(Factory.class)
+        .addKeepRuntimeInvisibleAnnotations()
+        .addKeepRuntimeInvisibleParameterAnnotations()
+        .addVerticallyMergedClassesInspector(
+            inspector -> inspector.assertMergedIntoSubtype(A.class))
+        .applyIf(
+            keepForAnnotations,
+            builder -> {
+              assertFalse(enableProguardCompatibilityMode);
+              builder.addKeepRules(
+                  "-keep,allowshrinking,allowobfuscation class " + C.class.getTypeName());
+            })
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("Hello", "World!")
@@ -62,15 +91,21 @@
               DexType mergedType = inspector.clazz(B.class).getDexProgramClass().type;
               ClassSubject classC = inspector.clazz(C.class);
               assertThat(classC, isPresent());
-              DexEncodedAnnotation annotation =
-                  classC.annotation(Factory.class.getTypeName()).getAnnotation();
-              assertTrue(valueIsDexType(mergedType, annotation.elements[0].value));
-              assertTrue(
-                  Arrays.stream(annotation.elements[1].value.asDexValueArray().getValues())
-                      .allMatch(value -> valueIsDexType(mergedType, value)));
-              // Check that method parameter annotations are rewritten as well.
-              DexEncodedMethod method = inspector.clazz(Main.class).mainMethod().getMethod();
-              DexAnnotationSet annotationSet = method.parameterAnnotationsList.get(0);
+
+              MethodSubject mainMethod = inspector.clazz(Main.class).mainMethod();
+              if (enableProguardCompatibilityMode || keepForAnnotations) {
+                DexEncodedAnnotation annotation =
+                    classC.annotation(Factory.class.getTypeName()).getAnnotation();
+                assertTrue(valueIsDexType(mergedType, annotation.elements[0].value));
+                assertTrue(
+                    Arrays.stream(annotation.elements[1].value.asDexValueArray().getValues())
+                        .allMatch(value -> valueIsDexType(mergedType, value)));
+              } else {
+                assertTrue(classC.getDexProgramClass().annotations().isEmpty());
+              }
+
+              // Check that method parameter annotations are rewritten.
+              DexAnnotationSet annotationSet = mainMethod.getMethod().getParameterAnnotation(0);
               DexEncodedAnnotation parameterAnnotation = annotationSet.annotations[0].annotation;
               assertTrue(valueIsDexType(mergedType, parameterAnnotation.elements[0].value));
             });
@@ -82,6 +117,7 @@
     return true;
   }
 
+  @Retention(RetentionPolicy.CLASS)
   public @interface Factory {
 
     Class<?> extending() default Object.class;
@@ -92,6 +128,7 @@
   public static class A {}
 
   @NeverClassInline
+  @NoHorizontalClassMerging
   public static class B extends A {
     @NeverInline
     public void world() {
@@ -102,6 +139,7 @@
   @Factory(
       extending = A.class,
       other = {A.class, B.class})
+  @NoHorizontalClassMerging
   public static class C {
     @NeverInline
     public static void hello() {
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
index f102535..662ad5e 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
@@ -95,7 +95,7 @@
   @Test
   public void b120951621_keepAll() throws Exception {
     CodeInspector inspector =
-        testForR8(parameters.getBackend())
+        testForR8Compat(parameters.getBackend())
             .addProgramFiles(
                 compiledJars.getForConfiguration(kotlinc, targetVersion),
                 getKotlinAnnotationJar(kotlinc))
@@ -133,7 +133,7 @@
   @Test
   public void b120951621_partiallyKeep() throws Exception {
     CodeInspector inspector =
-        testForR8(parameters.getBackend())
+        testForR8Compat(parameters.getBackend())
             .addProgramFiles(
                 compiledJars.getForConfiguration(kotlinc, targetVersion),
                 getKotlinAnnotationJar(kotlinc))
@@ -175,7 +175,7 @@
   @Test
   public void b120951621_keepAnnotation() throws Exception {
     CodeInspector inspector =
-        testForR8(parameters.getBackend())
+        testForR8Compat(parameters.getBackend())
             .addProgramFiles(
                 compiledJars.getForConfiguration(kotlinc, targetVersion),
                 getKotlinAnnotationJar(kotlinc))
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java b/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
index ddc9a2b..184cd68 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
@@ -71,6 +71,16 @@
             "com.squareup.wire.WireField", "com.squareup.demo.myapplication.Test")
         .addKeepMainRule(TestClass.class)
         .addKeepAttributes("*Annotation*")
+        .applyIf(
+            parameters.isCfRuntime(),
+            builder ->
+                // When parsing the enum default value, the JVM tries to find the enum with the
+                // given name, but after shrinking the enum field names and the enum instance names
+                // no longer match.
+                builder.addKeepRules(
+                    "-keepclassmembers,allowshrinking class com.squareup.wire.WireField$Label {",
+                    "  static com.squareup.wire.WireField$Label OPTIONAL;",
+                    "}"))
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::checkEnumUses)
diff --git a/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java
index d42d3ad..50f1954 100644
--- a/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java
@@ -43,8 +43,14 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(EnumInAnnotationTest.class)
         .addKeepMainRule(Main.class)
+        .applyIf(
+            parameters.isCfRuntime(),
+            builder ->
+                builder.addKeepRules(
+                    "-keepclassmembernames class " + Enum.class.getTypeName() + " { <fields>; }"))
         .setMinApi(parameters.getApiLevel())
         .addKeepRuntimeVisibleAnnotations()
+        .compile()
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("TEST_ONE");
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 9933b84..e00615a 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -6,7 +6,9 @@
 
 import static com.android.tools.r8.references.Reference.classFromClass;
 import static com.android.tools.r8.references.Reference.methodFromMethod;
+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.onlyIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -122,7 +124,6 @@
                 mainClass, MentionedClass.class, mentionedClassWithAnnotations, annotationClass)
             .addKeepMainRule(mainClass)
             .allowAccessModification()
-            .noMinification()
             .addKeepClassAndMembersRules(annotationClass)
             .map(b -> keepAnnotations ? b.addKeepAttributes("*Annotation*") : b)
             .setMinApi(parameters.getApiLevel())
@@ -134,10 +135,15 @@
     assertThat(clazz, isPresent());
 
     // The test contains only a member class so the enclosing-method attribute will be null.
-    assertEquals(
-        forceProguardCompatibility, !clazz.getDexProgramClass().getInnerClasses().isEmpty());
-    assertEquals(forceProguardCompatibility || keepAnnotations,
-        clazz.annotation(annotationClass.getCanonicalName()).isPresent());
+    assertTrue(clazz.getDexProgramClass().getInnerClasses().isEmpty());
+
+    if (keepAnnotations) {
+      assertThat(
+          clazz.annotation(annotationClass.getCanonicalName()),
+          onlyIf(forceProguardCompatibility, isPresent()));
+    } else {
+      assertThat(clazz.annotation(annotationClass.getCanonicalName()), isAbsent());
+    }
   }
 
   @Test
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 880ed52..edbcd98 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,8 +54,11 @@
     CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     verifyClassesAbsent(codeInspector,
         UnusedAnnotation.class, UnusedAnnotationDependent.class);
-    verifyClassesPresent(codeInspector,
-        UsedAnnotation.class, UsedAnnotationDependent.class);
+    if (shrinker.isFullModeR8()) {
+      verifyClassesAbsent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
+    } else {
+      verifyClassesPresent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
+    }
   }
 
   @Test
@@ -80,8 +83,11 @@
     CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     verifyClassesAbsent(codeInspector,
         UnusedAnnotation.class, UnusedAnnotationDependent.class);
-    verifyClassesPresent(codeInspector,
-        UsedAnnotation.class, UsedAnnotationDependent.class);
+    if (shrinker.isFullModeR8()) {
+      verifyClassesAbsent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
+    } else {
+      verifyClassesPresent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
+    }
   }
 
   @Test
@@ -104,8 +110,11 @@
     CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     verifyClassesAbsent(codeInspector,
         UnusedAnnotation.class, UnusedAnnotationDependent.class);
-    verifyClassesPresent(codeInspector,
-        UsedAnnotation.class, UsedAnnotationDependent.class);
+    if (shrinker.isFullModeR8()) {
+      verifyClassesAbsent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
+    } else {
+      verifyClassesPresent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
+    }
   }
 
 }
diff --git a/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java b/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java
index d578ae2..b97fd41 100644
--- a/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java
+++ b/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java
@@ -6,12 +6,29 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import java.util.Map;
 import java.util.function.Function;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
-public class ArrayUtilsTest {
+@RunWith(Parameterized.class)
+public class ArrayUtilsTest extends TestBase {
+
+  private static final Integer[] EMPTY_INTEGER_ARRAY = new Integer[0];
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public ArrayUtilsTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
 
   private Integer[] createInputData(int size) {
     Integer[] input = new Integer[size];
@@ -124,7 +141,7 @@
   public void testMap_identity() {
     int size = 3;
     Integer[] input = createInputData(size);
-    Integer[] output = ArrayUtils.map(Integer[].class, input, Function.identity());
+    Integer[] output = ArrayUtils.map(input, Function.identity(), EMPTY_INTEGER_ARRAY);
     assertEquals(input, output);
   }
 
@@ -132,7 +149,7 @@
   public void testMap_dropOdd() {
     int size = 3;
     Integer[] input = createInputData(size);
-    Integer[] output = ArrayUtils.map(Integer[].class, input, x -> x % 2 != 0 ? null : x);
+    Integer[] output = ArrayUtils.map(input, x -> x % 2 != 0 ? null : x, EMPTY_INTEGER_ARRAY);
     assertNotEquals(input, output);
     assertEquals(2, output.length);
     assertEquals(0, (int) output[0]);
@@ -143,7 +160,7 @@
   public void testMap_dropAll() {
     int size = 3;
     Integer[] input = createInputData(size);
-    Integer[] output = ArrayUtils.map(Integer[].class, input, x -> null);
+    Integer[] output = ArrayUtils.map(input, x -> null, EMPTY_INTEGER_ARRAY);
     assertNotEquals(input, output);
     assertEquals(0, output.length);
   }
@@ -152,7 +169,7 @@
   public void testMap_double() {
     int size = 3;
     Integer[] input = createInputData(size);
-    Integer[] output = ArrayUtils.map(Integer[].class, input, x -> 2 * x);
+    Integer[] output = ArrayUtils.map(input, x -> 2 * x, EMPTY_INTEGER_ARRAY);
     assertNotEquals(input, output);
     assertEquals(size, output.length);
     for (int i = 0; i < size; i++) {
@@ -164,7 +181,7 @@
   public void testMap_double_onlyOdd() {
     int size = 3;
     Integer[] input = createInputData(size);
-    Integer[] output = ArrayUtils.map(Integer[].class, input, x -> x % 2 != 0 ? 2 * x : x);
+    Integer[] output = ArrayUtils.map(input, x -> x % 2 != 0 ? 2 * x : x, EMPTY_INTEGER_ARRAY);
     assertNotEquals(input, output);
     assertEquals(size, output.length);
     for (int i = 0; i < size; i++) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
index ceafd09..6d7067a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.references.FieldReference;
 
 public class AbsentFieldSubject extends FieldSubject {
 
@@ -68,6 +69,16 @@
   }
 
   @Override
+  public FieldReference getOriginalReference() {
+    return null;
+  }
+
+  @Override
+  public FieldReference getFinalReference() {
+    return null;
+  }
+
+  @Override
   public AccessFlags<?> getAccessFlags() {
     throw new Unreachable("Absent field has no access flags");
   }
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 0a4f65a..452669a 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
@@ -193,6 +193,7 @@
 
   public abstract AnnotationSubject annotation(String name);
 
+  @Override
   public abstract String getOriginalName();
 
   public abstract String getOriginalDescriptor();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
index aae1912..9b9d373 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.references.FieldReference;
 
 public abstract class FieldSubject extends MemberSubject {
 
@@ -43,4 +44,8 @@
   }
 
   public abstract String getJvmFieldSignatureAsString();
+
+  public abstract FieldReference getOriginalReference();
+
+  public abstract FieldReference getFinalReference();
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
index 26466c1..023c088 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
@@ -13,6 +13,8 @@
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.signature.GenericSignatureParser;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.Reference;
 
 public class FoundFieldSubject extends FieldSubject {
 
@@ -130,6 +132,23 @@
   }
 
   @Override
+  public FieldReference getOriginalReference() {
+    DexField originalDexField = getOriginalDexField(codeInspector.getFactory());
+    return Reference.field(
+        Reference.classFromDescriptor(originalDexField.holder.toDescriptorString()),
+        getOriginalName(),
+        Reference.typeFromDescriptor(originalDexField.type.toDescriptorString()));
+  }
+
+  @Override
+  public FieldReference getFinalReference() {
+    return Reference.field(
+        Reference.classFromDescriptor(getField().getHolderType().toDescriptorString()),
+        getOriginalName(),
+        Reference.typeFromDescriptor(getField().getType().toDescriptorString()));
+  }
+
+  @Override
   public AccessFlags<?> getAccessFlags() {
     return getField().getAccessFlags();
   }
diff --git a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
index b08651f..e51c162 100644
--- a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
@@ -11,6 +11,7 @@
 
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.experimental.graphinfo.AnnotationGraphNode;
 import com.android.tools.r8.experimental.graphinfo.ClassGraphNode;
 import com.android.tools.r8.experimental.graphinfo.FieldGraphNode;
 import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
@@ -624,6 +625,14 @@
   private final Map<MethodReference, MethodGraphNode> methods;
   private final Map<FieldReference, FieldGraphNode> fields;
 
+  // Maps (annotated item, annotation type) to annotation node.
+  private final Map<ClassReference, Map<ClassReference, AnnotationGraphNode>> classAnnotations =
+      new HashMap<>();
+  private final Map<FieldReference, Map<ClassReference, AnnotationGraphNode>> fieldAnnotations =
+      new HashMap<>();
+  private final Map<MethodReference, Map<ClassReference, AnnotationGraphNode>> methodAnnotations =
+      new HashMap<>();
+
   public GraphInspector(CollectingGraphConsumer consumer, CodeInspector inspector) {
     this.consumer = consumer;
     this.inspector = inspector;
@@ -646,8 +655,30 @@
       } else if (target instanceof KeepRuleGraphNode) {
         KeepRuleGraphNode node = (KeepRuleGraphNode) target;
         rules.add(node);
+      } else if (target instanceof AnnotationGraphNode) {
+        AnnotationGraphNode node = (AnnotationGraphNode) target;
+        GraphNode annotatedNode = node.getAnnotatedNode();
+        Map<ClassReference, AnnotationGraphNode> annotationsOnAnnotatedNode;
+        if (annotatedNode instanceof ClassGraphNode) {
+          annotationsOnAnnotatedNode =
+              classAnnotations.computeIfAbsent(
+                  ((ClassGraphNode) annotatedNode).getReference(), key -> new HashMap<>());
+        } else if (annotatedNode instanceof FieldGraphNode) {
+          annotationsOnAnnotatedNode =
+              fieldAnnotations.computeIfAbsent(
+                  ((FieldGraphNode) annotatedNode).getReference(), key -> new HashMap<>());
+        } else if (annotatedNode instanceof MethodGraphNode) {
+          annotationsOnAnnotatedNode =
+              methodAnnotations.computeIfAbsent(
+                  ((MethodGraphNode) annotatedNode).getReference(), key -> new HashMap<>());
+        } else {
+          throw new Unreachable(
+              "Incomplete support for annotations on non-class, non-field, non-method items: "
+                  + annotatedNode.getClass().getTypeName());
+        }
+        annotationsOnAnnotatedNode.put(node.getAnnotationClassNode().getReference(), node);
       } else {
-        throw new Unimplemented("Incomplet support for graph node type: " + target.getClass());
+        throw new Unimplemented("Incomplete support for graph node type: " + target.getClass());
       }
       Map<GraphNode, Set<GraphEdgeInfo>> sources = consumer.getSourcesTargeting(target);
       for (GraphNode source : sources.keySet()) {
diff --git a/third_party/android_jar/api-database.tar.gz.sha1 b/third_party/android_jar/api-database.tar.gz.sha1
new file mode 100644
index 0000000..84abf49
--- /dev/null
+++ b/third_party/android_jar/api-database.tar.gz.sha1
@@ -0,0 +1 @@
+e4da4b29079ac393e0012e7676dcca0799841e29
\ No newline at end of file
diff --git a/third_party/android_jar/api-versions.tar.gz.sha1 b/third_party/android_jar/api-versions.tar.gz.sha1
new file mode 100644
index 0000000..849abf3
--- /dev/null
+++ b/third_party/android_jar/api-versions.tar.gz.sha1
@@ -0,0 +1 @@
+021b38c29b9be789f4312f95543a3f08baf66a19
\ No newline at end of file
diff --git a/third_party/android_jar/lib-v30.tar.gz.sha1 b/third_party/android_jar/lib-v30.tar.gz.sha1
index 700ad7c..31ebc31 100644
--- a/third_party/android_jar/lib-v30.tar.gz.sha1
+++ b/third_party/android_jar/lib-v30.tar.gz.sha1
@@ -1 +1 @@
-629e0bfb886d247246268130d946678fa1a4ca9e
\ No newline at end of file
+0173220e35349154afdde596c5bfaf21cb7df486
\ No newline at end of file
diff --git a/third_party/android_jar/lib-v31.tar.gz.sha1 b/third_party/android_jar/lib-v31.tar.gz.sha1
index 20eb896..0af2989 100644
--- a/third_party/android_jar/lib-v31.tar.gz.sha1
+++ b/third_party/android_jar/lib-v31.tar.gz.sha1
@@ -1 +1 @@
-758bc25bf1aa97fab86205b0dd5db396f9b129cd
\ No newline at end of file
+3f0543d825941eee32abca750ce30e83ed451f30
\ No newline at end of file
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 7914bf9..bf98dd3 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -394,7 +394,13 @@
         change_result = g4_change(options.version)
         change_result += 'Run \'(g4d ' + args.p4_client \
                          + ' && tap_presubmit -p all --train -c ' \
-                         + get_cl_id(change_result) + ')\' for running TAP presubmit.'
+                         + get_cl_id(change_result) + ')\' for running TAP global' \
+                         + ' presubmit using the train.\n' \
+                         + 'Run \'(g4d ' + args.p4_client \
+                         + ' && tap_presubmit -p all --notrain --detach --email' \
+                         + ' --skip_flaky_targets --skip_already_failing -c ' \
+                         + get_cl_id(change_result) + ')\' for running an isolated' \
+                         + ' TAP global presubmit.'
         return change_result
 
   return release_google3