Merge commit 'c225205824ce58503ff38eec05c3b31ade65cfb5' into dev-release
diff --git a/.gitignore b/.gitignore
index 28f7415..b929f34 100644
--- a/.gitignore
+++ b/.gitignore
@@ -106,6 +106,8 @@
 third_party/kotlin/kotlin-compiler-1.3.72
 third_party/kotlin/kotlin-compiler-1.4.20.tar.gz
 third_party/kotlin/kotlin-compiler-1.4.20
+third_party/kotlin/kotlin-compiler-1.5.0-M2.tar.gz
+third_party/kotlin/kotlin-compiler-1.5.0-M2
 third_party/kotlinx-coroutines-1.3.6.tar.gz
 third_party/kotlinx-coroutines-1.3.6
 third_party/nest/*
diff --git a/build.gradle b/build.gradle
index 30c839c..4feb96d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -46,7 +46,7 @@
     // The kotlin version is only here to specify the kotlin language level,
     // all kotlin compilations are done in tests.
     kotlinVersion = '1.3.72'
-    kotlinExtMetadataJVMVersion = '0.1.0'
+    kotlinExtMetadataJVMVersion = '0.2.0'
     smaliVersion = '2.2b4'
     errorproneVersion = '2.3.2'
     testngVersion = '6.10'
@@ -327,6 +327,7 @@
                 "kotlin/kotlin-compiler-1.3.41",
                 "kotlin/kotlin-compiler-1.3.72",
                 "kotlin/kotlin-compiler-1.4.20",
+                "kotlin/kotlin-compiler-1.5.0-M2",
                 "kotlinx-coroutines-1.3.6",
                 "openjdk/openjdk-rt-1.8",
                 "openjdk/desugar_jdk_libs",
@@ -441,7 +442,8 @@
         "youtube/youtube.android_14.44",
         "youtube/youtube.android_15.08",
         "youtube/youtube.android_15.09",
-        "youtube/youtube.android_15.33"
+        "youtube/youtube.android_15.33",
+        "youtube/youtube.android_16.12"
     ],
 ]
 
@@ -731,7 +733,7 @@
 task repackageDepsNew(type: ShadowJar) {
     configurations = [project.configurations.runtimeClasspath]
     mergeServiceFiles(it)
-    exclude { it.getRelativePath().getPathString() == "module-info.class" }
+    exclude { it.getRelativePath().getPathString().endsWith("module-info.class") }
     exclude { it.getRelativePath().getPathString().startsWith("META-INF/maven/") }
     baseName 'deps_all'
 }
diff --git a/infra/config/global/luci-scheduler.cfg b/infra/config/global/luci-scheduler.cfg
index b782dbe..f819ee4 100644
--- a/infra/config/global/luci-scheduler.cfg
+++ b/infra/config/global/luci-scheduler.cfg
@@ -150,7 +150,6 @@
   acl_sets: "default"
   triggering_policy: {
     max_concurrent_invocations: 1
-    max_batch_size: 1
   }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index f12d33c..8470275 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -97,6 +97,8 @@
             // Desugar to class file format and turn off switch optimizations, as the final
             // compilation with D8 or R8 will do that.
             options.cfToCfDesugar = true;
+            assert !options.forceAnnotateSynthetics;
+            options.forceAnnotateSynthetics = true;
             assert options.enableSwitchRewriting;
             options.enableSwitchRewriting = false;
             assert options.enableStringSwitchConversion;
@@ -105,6 +107,7 @@
             desugar(app, options, executorService);
 
             options.cfToCfDesugar = false;
+            options.forceAnnotateSynthetics = false;
             options.enableSwitchRewriting = true;
             options.enableStringSwitchConversion = true;
           });
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index a6f1b9c..546fad4 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -374,7 +374,7 @@
               shrinker -> shrinker.run(Mode.INITIAL_TREE_SHAKING));
 
           TreePruner pruner = new TreePruner(appViewWithLiveness);
-          DirectMappedDexApplication prunedApp = pruner.run();
+          DirectMappedDexApplication prunedApp = pruner.run(executorService);
 
           // Recompute the subtyping information.
           Set<DexType> removedClasses = pruner.getRemovedClasses();
@@ -609,7 +609,7 @@
                     DefaultTreePrunerConfiguration.getInstance());
 
             TreePruner pruner = new TreePruner(appViewWithLiveness, treePrunerConfiguration);
-            DirectMappedDexApplication application = pruner.run();
+            DirectMappedDexApplication application = pruner.run(executorService);
             Set<DexType> removedClasses = pruner.getRemovedClasses();
 
             if (options.usageInformationConsumer != null) {
diff --git a/src/main/java/com/android/tools/r8/cf/CfVersion.java b/src/main/java/com/android/tools/r8/cf/CfVersion.java
index a69cc03..47eebe9 100644
--- a/src/main/java/com/android/tools/r8/cf/CfVersion.java
+++ b/src/main/java/com/android/tools/r8/cf/CfVersion.java
@@ -8,6 +8,8 @@
 import com.android.tools.r8.utils.structural.StructuralItem;
 import com.android.tools.r8.utils.structural.StructuralMapping;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
+import java.util.Arrays;
+import java.util.stream.Collectors;
 import org.objectweb.asm.Opcodes;
 
 public final class CfVersion implements StructuralItem<CfVersion> {
@@ -30,6 +32,24 @@
 
   private final int version;
 
+  private static CfVersion[] versions = {
+    CfVersion.V1_1,
+    CfVersion.V1_2,
+    CfVersion.V1_3,
+    CfVersion.V1_4,
+    CfVersion.V1_5,
+    CfVersion.V1_6,
+    CfVersion.V1_7,
+    CfVersion.V1_8,
+    CfVersion.V9,
+    CfVersion.V10,
+    CfVersion.V11,
+    CfVersion.V12,
+    CfVersion.V13,
+    CfVersion.V14,
+    CfVersion.V15
+  };
+
   // Private constructor in case we want to canonicalize versions.
   private CfVersion(int version) {
     this.version = version;
@@ -55,6 +75,14 @@
     spec.withInt(CfVersion::major).withInt(CfVersion::minor);
   }
 
+  public static Iterable<CfVersion> rangeInclusive(CfVersion from, CfVersion to) {
+    assert from.isLessThanOrEqualTo(to);
+    return Arrays.stream(versions)
+        .filter(version -> version.isGreaterThanOrEqualTo(from))
+        .filter(version -> version.isLessThanOrEqualTo(to))
+        .collect(Collectors.toList());
+  }
+
   @Override
   public CfVersion self() {
     return this;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 775a909..a80dc16 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -157,6 +157,10 @@
     return false;
   }
 
+  public boolean isInvokeVirtual() {
+    return false;
+  }
+
   public boolean isInvokeInterface() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index b725c80..b19a183 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -185,6 +185,7 @@
     return opcode == Opcodes.INVOKESTATIC;
   }
 
+  @Override
   public boolean isInvokeVirtual() {
     return opcode == Opcodes.INVOKEVIRTUAL;
   }
diff --git a/src/main/java/com/android/tools/r8/errors/Unreachable.java b/src/main/java/com/android/tools/r8/errors/Unreachable.java
index 0eb71d2..0232c99 100644
--- a/src/main/java/com/android/tools/r8/errors/Unreachable.java
+++ b/src/main/java/com/android/tools/r8/errors/Unreachable.java
@@ -8,6 +8,14 @@
  */
 public class Unreachable extends InternalCompilerError {
 
+  public static Unreachable raise() {
+    throw new Unreachable();
+  }
+
+  public static Unreachable raise(Object... ignore) {
+    throw new Unreachable();
+  }
+
   public Unreachable() {
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 2ee3013..c2c02ad 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -504,15 +504,15 @@
    * <p>The result is the field that will be hit at runtime, if such field is known. A result of
    * null indicates that the field is either undefined or not a static field.
    */
-  public DexEncodedField lookupStaticTargetOn(DexType type, DexField field) {
+  public DexClassAndField lookupStaticTargetOn(DexType type, DexField field) {
     assert checkIfObsolete();
     assert type.isClassType();
-    DexEncodedField result = resolveFieldOn(type, field).getResolvedField();
-    return result == null || !result.accessFlags.isStatic() ? null : result;
+    DexClassAndField result = resolveFieldOn(type, field).getResolutionPair();
+    return result == null || !result.getAccessFlags().isStatic() ? null : result;
   }
 
-  public DexEncodedField lookupStaticTarget(DexField field) {
-    return lookupStaticTargetOn(field.holder, field);
+  public DexClassAndField lookupStaticTarget(DexField field) {
+    return lookupStaticTargetOn(field.getHolderType(), field);
   }
 
   /**
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 b2bc948..0074673 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -221,6 +221,10 @@
     return Iterables.filter(directMethods(), predicate::test);
   }
 
+  public void addDirectMethod(DexEncodedMethod method) {
+    methodCollection.addDirectMethod(method);
+  }
+
   public void addDirectMethods(Collection<DexEncodedMethod> methods) {
     methodCollection.addDirectMethods(methods);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
index 6ab995d..58862d7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -48,6 +48,10 @@
     return getReference().asMethodReference();
   }
 
+  public DexType getParameter(int index) {
+    return getReference().getParameter(index);
+  }
+
   public DexTypeList getParameters() {
     return getReference().getParameters();
   }
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 412a66d..ceb543f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -390,6 +390,8 @@
   public final DexType stringBuilderType = createStaticallyKnownType(stringBuilderDescriptor);
   public final DexType stringBufferType = createStaticallyKnownType(stringBufferDescriptor);
 
+  public final DexType javaLangReflectArrayType =
+      createStaticallyKnownType("Ljava/lang/reflect/Array;");
   public final DexType javaLangSystemType = createStaticallyKnownType(javaLangSystemDescriptor);
   public final DexType javaIoPrintStreamType = createStaticallyKnownType("Ljava/io/PrintStream;");
 
@@ -557,6 +559,8 @@
   public final ClassMethods classMethods = new ClassMethods();
   public final ConstructorMethods constructorMethods = new ConstructorMethods();
   public final EnumMembers enumMembers = new EnumMembers();
+  public final JavaLangReflectArrayMembers javaLangReflectArrayMembers =
+      new JavaLangReflectArrayMembers();
   public final JavaLangSystemMethods javaLangSystemMethods = new JavaLangSystemMethods();
   public final NullPointerExceptionMethods npeMethods = new NullPointerExceptionMethods();
   public final IllegalArgumentExceptionMethods illegalArgumentExceptionMethods =
@@ -1453,6 +1457,17 @@
     }
   }
 
+  public class JavaLangReflectArrayMembers {
+
+    public final DexMethod newInstanceMethodWithDimensions =
+        createMethod(
+            javaLangReflectArrayType,
+            createProto(objectType, classType, intArrayType),
+            "newInstance");
+
+    private JavaLangReflectArrayMembers() {}
+  }
+
   public class JavaLangSystemMethods {
     public final DexMethod identityHashCode;
 
@@ -2568,12 +2583,15 @@
               if (type.isClassType()) {
                 if (!appView.enableWholeProgramOptimizations()) {
                   // Don't reason at the level of interfaces in D8.
-                  return ClassTypeElement.create(type, nullability, InterfaceCollection.empty());
+                  return ClassTypeElement.createForD8(type, nullability);
                 }
                 assert appView.appInfo().hasClassHierarchy();
                 if (appView.isInterface(type).isTrue()) {
                   return ClassTypeElement.create(
-                      objectType, nullability, InterfaceCollection.singleton(type));
+                      objectType,
+                      nullability,
+                      appView.withClassHierarchy(),
+                      InterfaceCollection.singleton(type));
                 }
                 // In theory, `interfaces` is the least upper bound of implemented interfaces.
                 // It is expensive to walk through type hierarchy; collect implemented interfaces;
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 881b0e9..31a24ff 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -595,10 +595,6 @@
     methodCollection.addVirtualMethod(virtualMethod);
   }
 
-  public void addDirectMethod(DexEncodedMethod directMethod) {
-    methodCollection.addDirectMethod(directMethod);
-  }
-
   public void replaceVirtualMethod(
       DexMethod virtualMethod, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
     methodCollection.replaceVirtualMethod(virtualMethod, replacement);
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
index 1af8be2..f56cc34 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
@@ -4,8 +4,9 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
+import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.utils.ConsumerUtils;
-import com.android.tools.r8.utils.MapUtils;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
 import java.util.IdentityHashMap;
@@ -89,24 +90,31 @@
 
   public MethodAccessInfoCollection rewrittenWithLens(
       DexDefinitionSupplier definitions, GraphLens lens) {
-    return new MethodAccessInfoCollection(
-        rewriteInvokesWithLens(directInvokes, definitions, lens),
-        rewriteInvokesWithLens(interfaceInvokes, definitions, lens),
-        rewriteInvokesWithLens(staticInvokes, definitions, lens),
-        rewriteInvokesWithLens(superInvokes, definitions, lens),
-        rewriteInvokesWithLens(virtualInvokes, definitions, lens));
+    MethodAccessInfoCollection.Builder<?> builder = identityBuilder();
+    rewriteInvokesWithLens(builder, directInvokes, definitions, lens, Type.DIRECT);
+    rewriteInvokesWithLens(builder, interfaceInvokes, definitions, lens, Type.INTERFACE);
+    rewriteInvokesWithLens(builder, staticInvokes, definitions, lens, Type.STATIC);
+    rewriteInvokesWithLens(builder, superInvokes, definitions, lens, Type.SUPER);
+    rewriteInvokesWithLens(builder, virtualInvokes, definitions, lens, Type.VIRTUAL);
+    return builder.build();
   }
 
-  private static Map<DexMethod, ProgramMethodSet> rewriteInvokesWithLens(
-      Map<DexMethod, ProgramMethodSet> invokes, DexDefinitionSupplier definitions, GraphLens lens) {
-    return MapUtils.map(
-        invokes,
-        IdentityHashMap::new,
-        lens::getRenamedMethodSignature,
-        methods -> methods.rewrittenWithLens(definitions, lens),
-        (methods, other) -> {
-          methods.addAll(other);
-          return methods;
+  private static void rewriteInvokesWithLens(
+      MethodAccessInfoCollection.Builder<?> builder,
+      Map<DexMethod, ProgramMethodSet> invokes,
+      DexDefinitionSupplier definitions,
+      GraphLens lens,
+      Type type) {
+    invokes.forEach(
+        (reference, contexts) -> {
+          ProgramMethodSet newContexts = contexts.rewrittenWithLens(definitions, lens);
+          for (ProgramMethod newContext : newContexts) {
+            MethodLookupResult methodLookupResult =
+                lens.lookupMethod(reference, newContext.getReference(), type);
+            DexMethod newReference = methodLookupResult.getReference();
+            Type newType = methodLookupResult.getType();
+            builder.registerInvokeInContext(newReference, newContext, newType);
+          }
         });
   }
 
@@ -151,6 +159,25 @@
       return virtualInvokes;
     }
 
+    public boolean registerInvokeInContext(
+        DexMethod invokedMethod, ProgramMethod context, Type type) {
+      switch (type) {
+        case DIRECT:
+          return registerInvokeDirectInContext(invokedMethod, context);
+        case INTERFACE:
+          return registerInvokeInterfaceInContext(invokedMethod, context);
+        case STATIC:
+          return registerInvokeStaticInContext(invokedMethod, context);
+        case SUPER:
+          return registerInvokeSuperInContext(invokedMethod, context);
+        case VIRTUAL:
+          return registerInvokeVirtualInContext(invokedMethod, context);
+        default:
+          assert false;
+          return false;
+      }
+    }
+
     public boolean registerInvokeDirectInContext(DexMethod invokedMethod, ProgramMethod context) {
       return registerInvokeMethodInContext(invokedMethod, context, directInvokes);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
index f33a1d4..78332d5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import java.util.Objects;
+import java.util.Set;
 import java.util.function.Function;
 
 public class ArrayTypeElement extends ReferenceTypeElement {
@@ -134,10 +135,12 @@
 
   @Override
   public ArrayTypeElement fixupClassTypeReferences(
-      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithClassHierarchy> appView) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      Function<DexType, DexType> mapping,
+      Set<DexType> prunedTypes) {
     if (memberTypeLattice.isReferenceType()) {
       TypeElement substitutedMemberType =
-          memberTypeLattice.fixupClassTypeReferences(mapping, appView);
+          memberTypeLattice.fixupClassTypeReferences(appView, mapping, prunedTypes);
       if (substitutedMemberType != memberTypeLattice) {
         return ArrayTypeElement.create(substitutedMemberType, nullability);
       }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
index 144f465..083c27f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
@@ -23,6 +23,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Queue;
+import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
@@ -37,11 +38,16 @@
   private final DexType type;
 
   public static ClassTypeElement create(
-      DexType classType, Nullability nullability, InterfaceCollection interfaces) {
+      DexType classType,
+      Nullability nullability,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      InterfaceCollection interfaces) {
+    assert appView != null;
+    assert appView.enableWholeProgramOptimizations();
     assert interfaces != null;
     return NullabilityVariants.create(
         nullability,
-        (variants) -> new ClassTypeElement(classType, nullability, interfaces, variants, null));
+        variants -> new ClassTypeElement(classType, nullability, interfaces, variants, appView));
   }
 
   public static ClassTypeElement create(
@@ -49,9 +55,18 @@
       Nullability nullability,
       AppView<? extends AppInfoWithClassHierarchy> appView) {
     assert appView != null;
+    assert appView.enableWholeProgramOptimizations();
     return NullabilityVariants.create(
         nullability,
-        (variants) -> new ClassTypeElement(classType, nullability, null, variants, appView));
+        variants -> new ClassTypeElement(classType, nullability, null, variants, appView));
+  }
+
+  public static ClassTypeElement createForD8(DexType classType, Nullability nullability) {
+    return NullabilityVariants.create(
+        nullability,
+        variants ->
+            new ClassTypeElement(
+                classType, nullability, InterfaceCollection.empty(), variants, null));
   }
 
   private ClassTypeElement(
@@ -61,11 +76,13 @@
       NullabilityVariants<ClassTypeElement> variants,
       AppView<? extends AppInfoWithClassHierarchy> appView) {
     super(nullability);
+    assert appView != null
+        ? appView.enableWholeProgramOptimizations()
+        : (interfaces != null && interfaces.isEmpty());
     assert classType.isClassType();
-    assert interfaces != null || appView != null;
-    type = classType;
+    this.type = classType;
     this.appView = appView;
-    lazyInterfaces = interfaces;
+    this.lazyInterfaces = interfaces;
     this.variants = variants;
   }
 
@@ -75,12 +92,13 @@
 
   public InterfaceCollection getInterfaces() {
     if (lazyInterfaces == null) {
+      // Class type interfaces are cached on DexItemFactory.
       assert appView != null;
-      lazyInterfaces =
-          appView.dexItemFactory()
-              .getOrComputeLeastUpperBoundOfImplementedInterfaces(type, appView);
+      assert appView.enableWholeProgramOptimizations();
+      return appView
+          .dexItemFactory()
+          .getOrComputeLeastUpperBoundOfImplementedInterfaces(type, appView);
     }
-    assert lazyInterfaces != null;
     return lazyInterfaces;
   }
 
@@ -90,11 +108,6 @@
     return new ClassTypeElement(type, nullability, lazyInterfaces, variants, appView);
   }
 
-  public boolean isRelatedTo(ClassTypeElement other, AppView<?> appView) {
-    return lessThanOrEqualUpToNullability(other, appView)
-        || other.lessThanOrEqualUpToNullability(this, appView);
-  }
-
   @Override
   public ClassTypeElement getOrCreateVariant(Nullability nullability) {
     ClassTypeElement variant = variants.get(nullability);
@@ -156,17 +169,21 @@
 
   @Override
   public TypeElement fixupClassTypeReferences(
-      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithClassHierarchy> appView) {
+      AppView<? extends AppInfoWithClassHierarchy> ignore,
+      Function<DexType, DexType> mapping,
+      Set<DexType> prunedTypes) {
+    assert appView != null;
+    assert appView.enableWholeProgramOptimizations();
+
     DexType mappedType = mapping.apply(type);
     if (mappedType.isPrimitiveType()) {
       return PrimitiveTypeElement.fromDexType(mappedType, false);
     }
-    if (mappedType != type) {
-      return create(mappedType, nullability, appView);
-    }
-    // If the mapped type is not object and no computation of interfaces, we can return early.
-    if (mappedType != appView.dexItemFactory().objectType && lazyInterfaces == null) {
-      return this;
+
+    // If there are no interfaces, then just return the mapped type element. Otherwise, the list of
+    // interfaces require rewriting.
+    if (lazyInterfaces == null || lazyInterfaces.isEmpty()) {
+      return mappedType == type ? this : create(mappedType, nullability, appView);
     }
 
     // For most types there will not have been a change thus we iterate without allocating a new
@@ -176,6 +193,9 @@
     getInterfaces()
         .forEach(
             (iface, isKnown) -> {
+              if (prunedTypes.contains(iface)) {
+                return;
+              }
               DexType substitutedType = mapping.apply(iface);
               if (iface != substitutedType) {
                 hasChangedInterfaces.set();
@@ -196,7 +216,7 @@
     if (hasChangedInterfaces.get()) {
       if (interfaceToClassChange.isSet()) {
         assert !interfaceToClassChange.get().isInterface();
-        assert type == appView.dexItemFactory().objectType;
+        assert mappedType == appView.dexItemFactory().objectType;
         return create(interfaceToClassChange.get().type, nullability, appView);
       } else {
         Builder builder = InterfaceCollection.builder();
@@ -206,38 +226,53 @@
               assert iface == rewritten || isKnown : "Rewritten implies program types thus known.";
               builder.addInterface(rewritten, isKnown);
             });
-        return create(mappedType, nullability, builder.build());
+        return create(mappedType, nullability, appView, builder.build());
       }
     }
-    return this;
+    return mappedType == type ? this : create(mappedType, nullability, appView, getInterfaces());
   }
 
   ClassTypeElement join(ClassTypeElement other, AppView<?> appView) {
-    Nullability nullability = nullability().join(other.nullability());
     if (!appView.enableWholeProgramOptimizations()) {
-      assert lazyInterfaces != null && lazyInterfaces.isEmpty();
-      assert other.lazyInterfaces != null && other.lazyInterfaces.isEmpty();
-      return ClassTypeElement.create(
+      assert lazyInterfaces != null;
+      assert lazyInterfaces.isEmpty();
+      assert other.lazyInterfaces != null;
+      assert other.lazyInterfaces.isEmpty();
+      return ClassTypeElement.createForD8(
           getClassType() == other.getClassType()
               ? getClassType()
               : appView.dexItemFactory().objectType,
-          nullability,
-          InterfaceCollection.empty());
+          nullability().join(other.nullability()));
     }
+    return joinWithClassHierarchy(other);
+  }
+
+  private ClassTypeElement joinWithClassHierarchy(ClassTypeElement other) {
+    assert appView != null;
+    assert appView.enableWholeProgramOptimizations();
     DexType lubType =
-        computeLeastUpperBoundOfClasses(
-            appView.appInfo().withClassHierarchy(), getClassType(), other.getClassType());
+        computeLeastUpperBoundOfClasses(appView.appInfo(), getClassType(), other.getClassType());
     InterfaceCollection c1lubItfs = getInterfaces();
     InterfaceCollection c2lubItfs = other.getInterfaces();
-    InterfaceCollection lubItfs = null;
-    if (c1lubItfs.equals(c2lubItfs)) {
-      lubItfs = c1lubItfs;
-    }
-    if (lubItfs == null) {
-      lubItfs =
-          computeLeastUpperBoundOfInterfaces(appView.withClassHierarchy(), c1lubItfs, c2lubItfs);
-    }
-    return ClassTypeElement.create(lubType, nullability, lubItfs);
+    InterfaceCollection lubItfs =
+        c1lubItfs.equals(c2lubItfs)
+            ? c1lubItfs
+            : computeLeastUpperBoundOfInterfaces(appView, c1lubItfs, c2lubItfs);
+    InterfaceCollection lubItfsDefault =
+        appView
+            .dexItemFactory()
+            .getOrComputeLeastUpperBoundOfImplementedInterfaces(lubType, appView);
+    Nullability lubNullability = nullability().join(other.nullability());
+
+    // If the computed interfaces are identical to the interfaces of `lubType`, then do not include
+    // the interfaces in the ClassTypeElement. This canonicalization of interfaces reduces memory,
+    // but also reduces the amount of work involved in lens rewriting class type elements (the null
+    // element does not require any rewriting).
+    //
+    // From a correctness point of view, both solutions should work.
+    return lubItfs.equals(lubItfsDefault)
+        ? create(lubType, lubNullability, appView)
+        : create(lubType, lubNullability, appView, lubItfs);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
index a89c486..88d8b66 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
@@ -85,7 +85,7 @@
               || operandType.isPrimitiveType()
               || operandType.isNullType()
               || (operandType.isReferenceType()
-                  && operandType.fixupClassTypeReferences(mapping, appView) == operandType);
+                  && operandType.fixupClassTypeReferences(appView, mapping) == operandType);
         }
       }
     }
@@ -98,7 +98,7 @@
       BasicBlock block = blocks.next();
       for (Phi phi : block.getPhis()) {
         TypeElement phiType = phi.getType();
-        TypeElement substituted = phiType.fixupClassTypeReferences(mapping, appView);
+        TypeElement substituted = phiType.fixupClassTypeReferences(appView, mapping);
         assert substituted == phiType || affectedPhis.contains(phi);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
index 9736b13..25cb05d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
@@ -3,13 +3,17 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
+import static java.util.Collections.emptySet;
+
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.ir.code.Value;
+import java.util.Set;
 import java.util.function.Function;
 
 /** The base abstraction of lattice elements for local type analysis. */
@@ -67,11 +71,30 @@
     return ReferenceTypeElement.getNullType();
   }
 
+  public final TypeElement fixupClassTypeReferences(
+      AppView<? extends AppInfoWithClassHierarchy> appView, Function<DexType, DexType> mapping) {
+    return fixupClassTypeReferences(appView, mapping, emptySet());
+  }
+
   public TypeElement fixupClassTypeReferences(
-      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithClassHierarchy> appView) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      Function<DexType, DexType> mapping,
+      Set<DexType> prunedTypes) {
     return this;
   }
 
+  public final TypeElement rewrittenWithLens(
+      AppView<? extends AppInfoWithClassHierarchy> appView, GraphLens graphLens) {
+    return rewrittenWithLens(appView, graphLens, emptySet());
+  }
+
+  public final TypeElement rewrittenWithLens(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      GraphLens graphLens,
+      Set<DexType> prunedTypes) {
+    return fixupClassTypeReferences(appView, graphLens::lookupType, prunedTypes);
+  }
+
   public boolean isNullable() {
     return nullability().isNullable();
   }
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 4c1d942..eb5a275 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
@@ -1578,7 +1578,11 @@
     }
 
     public B setFreshOutValue(ValueFactory factory, TypeElement type) {
-      return setOutValue(factory.createValue(type));
+      return setFreshOutValue(factory, type, null);
+    }
+
+    public B setFreshOutValue(ValueFactory factory, TypeElement type, DebugLocalInfo localInfo) {
+      return setOutValue(factory.createValue(type, localInfo));
     }
 
     public B setPosition(Position position) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index 5793ec1..6fe8c6f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.utils.BooleanUtils;
 import java.util.List;
 import java.util.Set;
 import org.objectweb.asm.Opcodes;
@@ -160,6 +161,11 @@
     return arguments().get(index);
   }
 
+  public Value getArgumentForParameter(int index) {
+    int offset = BooleanUtils.intValue(!isInvokeStatic());
+    return getArgument(index + offset);
+  }
+
   public Value getFirstArgument() {
     return getArgument(0);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index 1ef9cf5..508b115 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -93,7 +93,8 @@
 
     assert verifyLambdaInterfaces(returnType, lambdaInterfaceSet, objectType);
 
-    return ClassTypeElement.create(objectType, Nullability.maybeNull(), lambdaInterfaceSet);
+    return ClassTypeElement.create(
+        objectType, Nullability.maybeNull(), appView.withClassHierarchy(), lambdaInterfaceSet);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index 70f0502..b4caedd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -37,6 +38,10 @@
     super(field, dest, (Value) null);
   }
 
+  public static Builder builder() {
+    return new Builder();
+  }
+
   public static StaticGet copyOf(IRCode code, StaticGet original) {
     Value newValue =
         new Value(code.valueNumberGenerator.next(), original.getOutType(), original.getLocalInfo());
@@ -243,4 +248,28 @@
       return holder != context.getHolderType();
     }
   }
+
+  public static class Builder extends BuilderBase<Builder, StaticGet> {
+
+    private DexField field;
+
+    public Builder setField(DexClassAndField field) {
+      return setField(field.getReference());
+    }
+
+    public Builder setField(DexField field) {
+      this.field = field;
+      return this;
+    }
+
+    @Override
+    public StaticGet build() {
+      return amend(new StaticGet(outValue, field));
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+  }
 }
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 3fb79c6..0437f8d 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
@@ -1523,20 +1523,6 @@
             null /* sourceString */);
       }
     }
-    if (type == Type.VIRTUAL) {
-      // If an invoke-virtual targets a private method in the current class overriding will
-      // not apply (see jvm spec on method resolution 5.4.3.3 and overriding 5.4.5) and
-      // therefore we use an invoke-direct instead. We need to do this as the Android Runtime
-      // will not allow invoke-virtual of a private method.
-      DexMethod invocationMethod = (DexMethod) item;
-      if (invocationMethod.holder == method.getHolderType()) {
-        DexEncodedMethod directTarget = method.getHolder().lookupDirectMethod(invocationMethod);
-        if (directTarget != null && !directTarget.isStatic()) {
-          assert invocationMethod.holder == directTarget.getHolderType();
-          type = Type.DIRECT;
-        }
-      }
-    }
     add(Invoke.create(type, item, callSiteProto, null, arguments, itf));
   }
 
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 5a9ee16..a241a05 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
@@ -1545,10 +1545,6 @@
       timing.end();
     }
 
-    if (enumUnboxer != null && methodProcessor.isPrimaryMethodProcessor()) {
-      enumUnboxer.analyzeEnums(code);
-    }
-
     assert code.verifyTypes(appView);
 
     deadCodeRemover.run(code, timing);
@@ -1597,6 +1593,10 @@
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
       Timing timing) {
+    if (enumUnboxer != null && methodProcessor.isPrimaryMethodProcessor()) {
+      enumUnboxer.analyzeEnums(code);
+    }
+
     if (libraryMethodOverrideAnalysis != null) {
       timing.begin("Analyze library method overrides");
       libraryMethodOverrideAnalysis.analyze(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 66e7ff7..b02d5e7 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
@@ -122,8 +122,7 @@
   private Value makeOutValue(Instruction insn, IRCode code) {
     if (insn.outValue() != null) {
       TypeElement oldType = insn.getOutType();
-      TypeElement newType =
-          oldType.fixupClassTypeReferences(appView.graphLens()::lookupType, appView);
+      TypeElement newType = oldType.rewrittenWithLens(appView, appView.graphLens());
       return code.createValue(newType, insn.getLocalInfo());
     }
     return null;
@@ -622,8 +621,7 @@
               Assume assume = current.asAssume();
               if (assume.hasOutValue()) {
                 TypeElement type = assume.getOutType();
-                TypeElement substituted =
-                    type.fixupClassTypeReferences(graphLens::lookupType, appView);
+                TypeElement substituted = type.rewrittenWithLens(appView, graphLens);
                 if (substituted != type) {
                   assert type.isArrayType() || type.isClassType();
                   if (substituted.isPrimitiveType()) {
@@ -659,8 +657,7 @@
             if (current.hasOutValue()) {
               // For all other instructions, substitute any changed type.
               TypeElement type = current.getOutType();
-              TypeElement substituted =
-                  type.fixupClassTypeReferences(graphLens::lookupType, appView);
+              TypeElement substituted = type.rewrittenWithLens(appView, graphLens);
               if (substituted != type) {
                 current.outValue().setType(substituted);
                 affectedPhis.addAll(current.outValue().uniquePhiUsers());
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 8c7528c..7bb5741 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
@@ -70,7 +70,8 @@
       LocalStackAllocator localStackAllocator,
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
-      MethodProcessingContext methodProcessingContext) {
+      MethodProcessingContext methodProcessingContext,
+      DexItemFactory dexItemFactory) {
     if (!instruction.isInvoke()) {
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BufferCovariantReturnTypeRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BufferCovariantReturnTypeRewriter.java
index 0f264b0..ea1cd46 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BufferCovariantReturnTypeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BufferCovariantReturnTypeRewriter.java
@@ -37,7 +37,8 @@
       LocalStackAllocator localStackAllocator,
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
-      MethodProcessingContext methodProcessingContext) {
+      MethodProcessingContext methodProcessingContext,
+      DexItemFactory dexItemFactory) {
     if (!isInvokeCandidate(instruction)) {
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaring.java
index 864a7b4..11c18d0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaring.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.ProgramMethod;
 import java.util.Collection;
 
@@ -26,7 +27,8 @@
       LocalStackAllocator localStackAllocator,
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
-      MethodProcessingContext methodProcessingContext);
+      MethodProcessingContext methodProcessingContext,
+      DexItemFactory dexItemFactory);
 
   /**
    * Returns true if the given instruction needs desugaring.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InvokeToPrivateRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InvokeToPrivateRewriter.java
new file mode 100644
index 0000000..7fd31a8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InvokeToPrivateRewriter.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * If an invoke-virtual targets a private method in the current class overriding will not apply (see
+ * JVM 11 spec on method selection 5.4.6. In previous jvm specs this was not explicitly stated, but
+ * derived from method resolution 5.4.3.3 and overriding 5.4.5).
+ *
+ * <p>An invoke-interface can in the same way target a private method.
+ *
+ * <p>For desugaring we use invoke-direct instead. We need to do this as the Android Runtime will
+ * not allow invoke-virtual of a private method.
+ */
+public class InvokeToPrivateRewriter implements CfInstructionDesugaring {
+
+  @Override
+  public Collection<CfInstruction> desugarInstruction(
+      CfInstruction instruction,
+      FreshLocalProvider freshLocalProvider,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext,
+      DexItemFactory dexItemFactory) {
+    if (!instruction.isInvokeVirtual() && !instruction.isInvokeInterface()) {
+      return null;
+    }
+    CfInvoke invoke = instruction.asInvoke();
+    DexMethod method = invoke.getMethod();
+    DexEncodedMethod privateMethod = privateMethodInvokedOnSelf(invoke, context);
+    if (privateMethod == null) {
+      return null;
+    }
+    return ImmutableList.of(new CfInvoke(Opcodes.INVOKESPECIAL, method, invoke.isInterface()));
+  }
+
+  @Override
+  public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+    if (!instruction.isInvokeVirtual() && !instruction.isInvokeInterface()) {
+      return false;
+    }
+    return isInvokingPrivateMethodOnSelf(instruction.asInvoke(), context);
+  }
+
+  private DexEncodedMethod privateMethodInvokedOnSelf(CfInvoke invoke, ProgramMethod context) {
+    DexMethod method = invoke.getMethod();
+    if (method.getHolderType() != context.getHolderType()) {
+      return null;
+    }
+    DexEncodedMethod directTarget = context.getHolder().lookupDirectMethod(method);
+    if (directTarget != null && !directTarget.isStatic()) {
+      assert method.holder == directTarget.getHolderType();
+      return directTarget;
+    }
+    return null;
+  }
+
+  private boolean isInvokingPrivateMethodOnSelf(CfInvoke invoke, ProgramMethod context) {
+    return privateMethodInvokedOnSelf(invoke, context) != null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index 04b40b3..26a2c5c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -45,6 +45,7 @@
     this.nestBasedAccessDesugaring = NestBasedAccessDesugaring.create(appView);
     desugarings.add(new LambdaInstructionDesugaring(appView));
     desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
+    desugarings.add(new InvokeToPrivateRewriter());
     desugarings.add(new StringConcatInstructionDesugaring(appView));
     desugarings.add(new BufferCovariantReturnTypeRewriter(appView));
     if (appView.options().enableBackportedMethodRewriting()) {
@@ -181,7 +182,8 @@
               localStackAllocator,
               eventConsumer,
               context,
-              methodProcessingContext);
+              methodProcessingContext,
+              appView.dexItemFactory());
       if (replacement != null) {
         assert verifyNoOtherDesugaringNeeded(
             instruction, context, methodProcessingContext, iterator);
@@ -215,7 +217,7 @@
         desugarings, desugaring -> desugaring.needsDesugaring(instruction, context));
   }
 
-  private static boolean verifyNoOtherDesugaringNeeded(
+  private boolean verifyNoOtherDesugaringNeeded(
       CfInstruction instruction,
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext,
@@ -234,7 +236,8 @@
                         },
                         CfInstructionDesugaringEventConsumer.createForDesugaredCode(),
                         context,
-                        methodProcessingContext)
+                        methodProcessingContext,
+                        appView.dexItemFactory())
                     != null)
         == null;
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
index d8d47e8..5770e22 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
@@ -125,7 +125,8 @@
       LocalStackAllocator localStackAllocator,
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
-      MethodProcessingContext methodProcessingContext) {
+      MethodProcessingContext methodProcessingContext,
+      DexItemFactory dexItemFactory) {
     assert !instruction.isInitClass();
     if (instruction.isInvokeDynamic() && needsDesugaring(instruction.asInvokeDynamic(), context)) {
       return desugarInvokeDynamicOnRecord(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/invokespecial/InvokeSpecialToSelfDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/invokespecial/InvokeSpecialToSelfDesugaring.java
index 0b9c80e..dfb797c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/invokespecial/InvokeSpecialToSelfDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/invokespecial/InvokeSpecialToSelfDesugaring.java
@@ -72,7 +72,8 @@
       LocalStackAllocator localStackAllocator,
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
-      MethodProcessingContext methodProcessingContext) {
+      MethodProcessingContext methodProcessingContext,
+      DexItemFactory dexItemFactory) {
     if (instruction.isInvokeSpecial()) {
       return desugarInvokeInstruction(instruction.asInvoke(), eventConsumer, context);
     }
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 0661c1d..0899b2d 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
@@ -25,12 +25,10 @@
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -39,10 +37,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.graph.MethodCollection;
-import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -918,45 +913,20 @@
       return;
     }
 
-    // If the companion class does not exist, then synthesize it on the classpath.
-    DexClass companionClass = appView.definitionFor(rewritten.getHolderType());
-    if (companionClass == null) {
-      companionClass =
-          synthesizeEmptyCompanionClass(appView, rewritten.getHolderType(), method.getHolder());
-    }
-
-    // If the companion class is a classpath class, then synthesize the companion class method if it
-    // does not exist.
-    if (companionClass.isClasspathClass()) {
-      synthetizeCompanionClassMethodIfNotPresent(companionClass.asClasspathClass(), rewritten);
-    }
-  }
-
-  private static DexClasspathClass synthesizeEmptyCompanionClass(
-      AppView<?> appView, DexType type, DexClass context) {
-    return appView
+    appView
         .getSyntheticItems()
-        .createClasspathClass(
-            SyntheticKind.COMPANION_CLASS, type, context, appView.dexItemFactory());
-  }
-
-  private static void synthetizeCompanionClassMethodIfNotPresent(
-      DexClasspathClass companionClass, DexMethod method) {
-    MethodCollection methodCollection = companionClass.getMethodCollection();
-    synchronized (methodCollection) {
-      if (methodCollection.getMethod(method) == null) {
-        boolean d8R8Synthesized = true;
-        methodCollection.addDirectMethod(
-            new DexEncodedMethod(
-                method,
-                MethodAccessFlags.createPublicStaticSynthetic(),
-                MethodTypeSignature.noSignature(),
-                DexAnnotationSet.empty(),
-                ParameterAnnotationsList.empty(),
-                DexEncodedMethod.buildEmptyThrowingCfCode(method),
-                d8R8Synthesized));
-      }
-    }
+        .ensureDirectMethodOnSyntheticClasspathClassWhileMigrating(
+            SyntheticKind.COMPANION_CLASS,
+            rewritten.getHolderType(),
+            method.getHolder(),
+            appView,
+            rewritten,
+            builder ->
+                builder
+                    .setName(rewritten.name)
+                    .setProto(rewritten.proto)
+                    .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                    .setCode(DexEncodedMethod::buildEmptyThrowingCfCode));
   }
 
   /**
@@ -1029,14 +999,6 @@
     }
   }
 
-  private void process(InterfaceDesugaringProcessor processor, Builder<?> builder, Flavor flavour) {
-    for (DexProgramClass clazz : builder.getProgramClasses()) {
-      if (shouldProcess(clazz, flavour)) {
-        processor.process(clazz, synthesizedMethods);
-      }
-    }
-  }
-
   final boolean isDefaultMethod(DexEncodedMethod method) {
     assert !method.accessFlags.isConstructor();
     assert !method.accessFlags.isStatic();
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 83f051d..206a3ed 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
@@ -89,7 +89,43 @@
     if (!iface.isInterface()) {
       return;
     }
+    analyzeBridges(iface);
+    if (needsCompanionClass(iface)) {
+      synthesizeCompanionClass(iface, synthesizedMethods);
+    }
+  }
 
+  private void analyzeBridges(DexProgramClass iface) {
+    for (ProgramMethod method : iface.virtualProgramMethods()) {
+      DexEncodedMethod virtual = method.getDefinition();
+      if (!interfaceMethodRemovalChangesApi(virtual, iface)) {
+        getPostProcessingInterfaceInfo(iface).setHasBridgesToRemove();
+        return;
+      }
+    }
+  }
+
+  private boolean needsCompanionClass(DexProgramClass iface) {
+    if (hasStaticMethodThatTriggersNonTrivialClassInitializer(iface)) {
+      return true;
+    }
+    for (ProgramMethod method : iface.virtualProgramMethods()) {
+      DexEncodedMethod virtual = method.getDefinition();
+      if (rewriter.isDefaultMethod(virtual)) {
+        return true;
+      }
+    }
+    for (ProgramMethod method : iface.directProgramMethods()) {
+      DexEncodedMethod definition = method.getDefinition();
+      if (!definition.isInitializer()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private void synthesizeCompanionClass(
+      DexProgramClass iface, ProgramMethodSet synthesizedMethods) {
     // The list of methods to be created in companion class.
     List<DexEncodedMethod> companionMethods = new ArrayList<>();
 
@@ -102,9 +138,7 @@
     // make private instance methods public static.
     processDirectInterfaceMethods(iface, companionMethods);
 
-    if (companionMethods.isEmpty()) {
-      return; // No methods to create, companion class not needed.
-    }
+    assert !companionMethods.isEmpty();
 
     ClassAccessFlags companionClassFlags = iface.accessFlags.copy();
     companionClassFlags.unsetAbstract();
@@ -260,20 +294,14 @@
         getPostProcessingInterfaceInfo(iface)
             .mapDefaultMethodToCompanionMethod(virtual, implMethod);
       }
-
-      if (!interfaceMethodRemovalChangesApi(virtual, iface)) {
-        getPostProcessingInterfaceInfo(iface).setHasBridgesToRemove();
-      }
     }
   }
 
   private void processDirectInterfaceMethods(
       DexProgramClass iface, List<DexEncodedMethod> companionMethods) {
-    DexEncodedMethod clinit = null;
     for (ProgramMethod method : iface.directProgramMethods()) {
       DexEncodedMethod definition = method.getDefinition();
       if (definition.isClassInitializer()) {
-        clinit = definition;
         continue;
       }
       if (definition.isInstanceInitializer()) {
@@ -283,6 +311,8 @@
         continue;
       }
 
+      getPostProcessingInterfaceInfo(iface).setHasNonClinitDirectMethods();
+
       MethodAccessFlags originalFlags = method.getAccessFlags();
       MethodAccessFlags newFlags = originalFlags.copy();
       if (originalFlags.isPrivate()) {
@@ -343,12 +373,6 @@
       companionMethods.add(implMethod);
       getPostProcessingInterfaceInfo(iface).moveMethod(oldMethod, companionMethod);
     }
-
-    boolean hasNonClinitDirectMethods =
-        iface.getMethodCollection().size() != (clinit == null ? 0 : 1);
-    if (hasNonClinitDirectMethods) {
-      getPostProcessingInterfaceInfo(iface).setHasNonClinitDirectMethods();
-    }
   }
 
   private void clearDirectMethods(DexProgramClass iface) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
index 59411a7..667397b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -73,7 +74,8 @@
       LocalStackAllocator localStackAllocator,
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
-      MethodProcessingContext methodProcessingContext) {
+      MethodProcessingContext methodProcessingContext,
+      DexItemFactory dexItemFactory) {
     if (instruction.isInvokeDynamic()) {
       return desugarInvokeDynamicInstruction(
           instruction.asInvokeDynamic(),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
index cec4cb1..a3bdc56 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
@@ -150,7 +150,8 @@
       LocalStackAllocator localStackAllocator,
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
-      MethodProcessingContext methodProcessingContext) {
+      MethodProcessingContext methodProcessingContext,
+      DexItemFactory dexItemFactory) {
     if (instruction.isFieldInstruction()) {
       return desugarFieldInstruction(instruction.asFieldInstruction(), context, eventConsumer);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/stringconcat/StringConcatInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/stringconcat/StringConcatInstructionDesugaring.java
index cae3e54..4f4d963 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/stringconcat/StringConcatInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/stringconcat/StringConcatInstructionDesugaring.java
@@ -75,7 +75,8 @@
       LocalStackAllocator localStackAllocator,
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
-      MethodProcessingContext methodProcessingContext) {
+      MethodProcessingContext methodProcessingContext,
+      DexItemFactory dexItemFactory) {
     if (instruction.isInvokeDynamic()) {
       // We are interested in bootstrap methods StringConcatFactory::makeConcat
       // and StringConcatFactory::makeConcatWthConstants, both are static.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrCloseResourceInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrCloseResourceInstructionDesugaring.java
index 076c9b3..30e7702 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrCloseResourceInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrCloseResourceInstructionDesugaring.java
@@ -44,7 +44,8 @@
       LocalStackAllocator localStackAllocator,
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
-      MethodProcessingContext methodProcessingContext) {
+      MethodProcessingContext methodProcessingContext,
+      DexItemFactory dexItemFactory) {
     if (!instruction.isInvokeStatic()) {
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 2d36f99..ae48979 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 import static com.google.common.base.Predicates.alwaysTrue;
 
+import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexClassAndMethod;
@@ -39,6 +40,7 @@
 import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfoLookup;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardMemberRuleReturnValue;
+import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Sets;
@@ -105,7 +107,7 @@
       DexField field = returnValueRule.getField();
       assert instruction.getOutType() == TypeElement.fromDexType(field.type, maybeNull(), appView);
 
-      DexEncodedField staticField = appView.appInfo().lookupStaticTarget(field);
+      DexClassAndField staticField = appView.appInfo().lookupStaticTarget(field);
       if (staticField == null) {
         if (warnedFields.add(field)) {
           reporter.warning(
@@ -118,8 +120,19 @@
         return null;
       }
 
+      if (AccessControl.isMemberAccessible(
+              staticField, staticField.getHolder(), code.context(), appView)
+          .isTrue()) {
+        return StaticGet.builder()
+            .setField(field)
+            .setFreshOutValue(code, field.getTypeElement(appView), instruction.getLocalInfo())
+            .build();
+      }
+
       Instruction replacement =
-          staticField.valueAsConstInstruction(code, instruction.getLocalInfo(), appView);
+          staticField
+              .getDefinition()
+              .valueAsConstInstruction(code, instruction.getLocalInfo(), appView);
       if (replacement == null) {
         reporter.warning(
             new StringDiagnostic(
@@ -179,11 +192,24 @@
       }
       replacement.setPosition(position);
       if (block.hasCatchHandlers()) {
-        iterator.split(code, blocks).listIterator(code).add(replacement);
+        BasicBlock splitBlock = iterator.split(code, blocks);
+        splitBlock.listIterator(code).add(replacement);
+
+        // Process the materialized value.
+        blocks.previous();
+        assert !iterator.hasNext();
+        assert IteratorUtils.peekNext(blocks) == splitBlock;
+
+        return true;
       } else {
         iterator.add(replacement);
       }
     }
+
+    // Process the materialized value.
+    iterator.previous();
+    assert iterator.peekNext() == replacement;
+
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index 802fdb3..683cf92 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InitClass;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -42,12 +43,9 @@
     }
     Set<Value> affectedValues = Sets.newIdentityHashSet();
     ProgramMethod context = code.context();
-    for (BasicBlock block : code.blocks) {
-      // Conservatively bail out if the containing block has catch handlers.
-      // TODO(b/118509730): unless join of all catch types is ClassNotFoundException ?
-      if (block.hasCatchHandlers()) {
-        continue;
-      }
+    BasicBlockIterator blockIterator = code.listIterator();
+    while (blockIterator.hasNext()) {
+      BasicBlock block = blockIterator.next();
       InstructionListIterator it = block.listIterator(code);
       while (it.hasNext()) {
         InvokeMethod invoke = it.nextUntil(x -> x.isInvokeStatic() || x.isInvokeVirtual());
@@ -61,14 +59,14 @@
               context,
               invoke.asInvokeStatic(),
               rewriteSingleGetClassOrForNameToConstClass(
-                  appView, code, it, invoke, affectedValues));
+                  appView, code, blockIterator, it, invoke, affectedValues));
         } else {
           applyTypeForGetClassTo(
               appView,
               context,
               invoke.asInvokeVirtual(),
               rewriteSingleGetClassOrForNameToConstClass(
-                  appView, code, it, invoke, affectedValues));
+                  appView, code, blockIterator, it, invoke, affectedValues));
         }
       }
     }
@@ -82,10 +80,12 @@
   private static BiConsumer<DexType, DexClass> rewriteSingleGetClassOrForNameToConstClass(
       AppView<AppInfoWithLiveness> appView,
       IRCode code,
+      BasicBlockIterator blockIterator,
       InstructionListIterator instructionIterator,
       InvokeMethod invoke,
       Set<Value> affectedValues) {
     return (type, baseClass) -> {
+      InitClass initClass = null;
       if (invoke.getInvokedMethod().match(appView.dexItemFactory().classMethods.forName)) {
         // Bail-out if the optimization could increase the size of the main dex.
         if (baseClass.isProgramClass()
@@ -106,25 +106,42 @@
             return;
           }
 
-          instructionIterator.addBefore(
+          initClass =
               InitClass.builder()
                   .setFreshOutValue(code, TypeElement.getInt())
                   .setType(type)
                   .setPosition(invoke)
-                  .build());
+                  .build();
         }
       }
 
       // If there are no users of the const-class then simply remove the instruction.
       if (!invoke.hasOutValue() || !invoke.outValue().hasAnyUsers()) {
-        instructionIterator.removeOrReplaceByDebugLocalRead();
+        if (initClass != null) {
+          instructionIterator.replaceCurrentInstruction(initClass);
+        } else {
+          instructionIterator.removeOrReplaceByDebugLocalRead();
+        }
         return;
       }
 
       // Otherwise insert a const-class instruction.
+      BasicBlock block = invoke.getBlock();
       affectedValues.addAll(invoke.outValue().affectedValues());
       instructionIterator.replaceCurrentInstructionWithConstClass(
           appView, code, type, invoke.getLocalInfo());
+
+      if (initClass != null) {
+        if (block.hasCatchHandlers()) {
+          instructionIterator
+              .splitCopyCatchHandlers(code, blockIterator, appView.options())
+              .listIterator(code)
+              .add(initClass);
+        } else {
+          instructionIterator.add(initClass);
+        }
+      }
+
       if (appView.options().isGeneratingClassFiles()) {
         code.method()
             .upgradeClassFileVersion(
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 f4c8028..bbbb543 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
@@ -77,31 +77,47 @@
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldOrdinalData;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldUnknownData;
 import com.android.tools.r8.ir.optimize.enums.eligibility.Reason;
-import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.IllegalInvokeWithImpreciseParameterTypeReason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.MissingContentsForEnumValuesArrayReason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.MissingEnumStaticFieldValuesReason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.MissingInstanceFieldValueForEnumInstanceReason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.MissingObjectStateForEnumInstanceReason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.UnsupportedInstanceFieldValueForEnumInstanceReason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.UnsupportedLibraryInvokeReason;
+import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.ir.optimize.info.UpdatableMethodOptimizationInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.shaking.KeepInfoCollection;
-import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.google.common.collect.HashMultiset;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.OptionalInt;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 public class EnumUnboxer {
@@ -121,7 +137,7 @@
   private EnumUnboxingRewriter enumUnboxerRewriter;
 
   private final boolean debugLogEnabled;
-  private final Map<DexType, Reason> debugLogs;
+  private final Map<DexType, List<Reason>> debugLogs;
 
   public EnumUnboxer(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
@@ -149,10 +165,20 @@
     return ordinal + 1;
   }
 
-  private void markEnumAsUnboxable(Reason reason, DexProgramClass enumClass) {
+  /**
+   * Returns true if {@param enumClass} was marked as being unboxable.
+   *
+   * <p>Note that, if debug logging is enabled, {@param enumClass} is not marked unboxable until the
+   * enum unboxing analysis has finished. This is to ensure completeness of the reason reporting.
+   */
+  private boolean markEnumAsUnboxable(Reason reason, DexProgramClass enumClass) {
     assert enumClass.isEnum();
-    reportFailure(enumClass.type, reason);
-    enumUnboxingCandidatesInfo.removeCandidate(enumClass.type);
+    if (!reportFailure(enumClass, reason)) {
+      // The failure was not reported, meaning debug logging is disabled.
+      enumUnboxingCandidatesInfo.removeCandidate(enumClass);
+      return true;
+    }
+    return false;
   }
 
   private DexProgramClass getEnumUnboxingCandidateOrNull(TypeElement lattice) {
@@ -265,6 +291,19 @@
   }
 
   private void analyzeCheckCast(CheckCast checkCast, Set<DexType> eligibleEnums) {
+    // Casts to enum array types are fine as long all enum array creations are valid and have valid
+    // usages. Since creations of enum arrays are rewritten to primitive int arrays, enum array
+    // casts will continue to work after rewriting to int[] casts. Casts that failed with
+    // ClassCastException: "T[] cannot be cast to MyEnum[]" will continue to fail, but with "T[]
+    // cannot be cast to int[]".
+    //
+    // Note that strictly speaking, the rewriting from MyEnum[] to int[] could change the semantics
+    // of code that would fail with "int[] cannot be cast to MyEnum[]" in the input. However, javac
+    // does not allow such code ("incompatible types"), so we should generally not see such code.
+    if (checkCast.getType().isArrayType()) {
+      return;
+    }
+
     // We are doing a type check, which typically means the in-value is of an upper
     // type and cannot be dealt with.
     // If the cast is on a dynamically typed object, the checkCast can be simply removed.
@@ -291,6 +330,9 @@
       ConstClass constClass, Set<DexType> eligibleEnums, ProgramMethod context) {
     // We are using the ConstClass of an enum, which typically means the enum cannot be unboxed.
     // We however allow unboxing if the ConstClass is used only:
+    // - as an argument to java.lang.reflect.Array#newInstance(java.lang.Class, int[]), to allow
+    //   unboxing of:
+    //    MyEnum[][] a = new MyEnum[x][y];
     // - as an argument to Enum#valueOf, to allow unboxing of:
     //    MyEnum a = Enum.valueOf(MyEnum.class, "A");
     // - as a receiver for a name method, to allow unboxing of:
@@ -315,11 +357,17 @@
       }
       if (user.isInvokeStatic()) {
         DexClassAndMethod singleTarget = user.asInvokeStatic().lookupSingleTarget(appView, context);
-        if (singleTarget != null && singleTarget.getReference() == factory.enumMembers.valueOf) {
-          // The name data is required for the correct mapping from the enum name to the ordinal in
-          // the valueOf utility method.
-          addRequiredNameData(enumType);
-          continue;
+        if (singleTarget != null) {
+          if (singleTarget.getReference() == factory.enumMembers.valueOf) {
+            // The name data is required for the correct mapping from the enum name to the ordinal
+            // in the valueOf utility method.
+            addRequiredNameData(enumClass);
+            continue;
+          }
+          if (singleTarget.getReference()
+              == factory.javaLangReflectArrayMembers.newInstanceMethodWithDimensions) {
+            continue;
+          }
         }
       }
       markEnumAsUnboxable(Reason.CONST_CLASS, enumClass);
@@ -328,9 +376,9 @@
     eligibleEnums.add(enumType);
   }
 
-  private void addRequiredNameData(DexType enumType) {
+  private void addRequiredNameData(DexProgramClass enumClass) {
     enumUnboxingCandidatesInfo.addRequiredEnumInstanceFieldData(
-        enumType, factory.enumMembers.nameField);
+        enumClass, factory.enumMembers.nameField);
   }
 
   private boolean isUnboxableNameMethod(DexMethod method) {
@@ -370,23 +418,30 @@
   }
 
   private Reason validateEnumUsages(IRCode code, Value value, DexProgramClass enumClass) {
+    Reason result = Reason.ELIGIBLE;
     for (Instruction user : value.uniqueUsers()) {
       Reason reason = instructionAllowEnumUnboxing(user, code, enumClass, value);
       if (reason != Reason.ELIGIBLE) {
-        markEnumAsUnboxable(reason, enumClass);
-        return reason;
+        if (markEnumAsUnboxable(reason, enumClass)) {
+          return reason;
+        }
+        // Record that the enum is ineligible, and continue analysis to collect all reasons for
+        // debugging.
+        result = reason;
       }
     }
     for (Phi phi : value.uniquePhiUsers()) {
       for (Value operand : phi.getOperands()) {
         if (!operand.getType().isNullType()
             && getEnumUnboxingCandidateOrNull(operand.getType()) != enumClass) {
+          // All reported reasons from here will be the same (INVALID_PHI), so just return
+          // immediately.
           markEnumAsUnboxable(Reason.INVALID_PHI, enumClass);
           return Reason.INVALID_PHI;
         }
       }
     }
-    return Reason.ELIGIBLE;
+    return result;
   }
 
   public void unboxEnums(
@@ -441,31 +496,19 @@
         executorService,
         new OptimizationInfoFixer() {
           @Override
-          public void fixup(DexEncodedField field) {
-            FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo();
-            if (optimizationInfo.isMutableFieldOptimizationInfo()) {
-              optimizationInfo
-                  .asMutableFieldOptimizationInfo()
-                  .fixupClassTypeReferences(appView.graphLens()::lookupType, appView)
-                  .fixupAbstractValue(appView, appView.graphLens());
-            } else {
-              assert optimizationInfo.isDefaultFieldOptimizationInfo();
-            }
+          public void fixup(DexEncodedField field, MutableFieldOptimizationInfo optimizationInfo) {
+            optimizationInfo
+                .fixupClassTypeReferences(appView, appView.graphLens())
+                .fixupAbstractValue(appView, appView.graphLens());
           }
 
           @Override
-          public void fixup(DexEncodedMethod method) {
-            MethodOptimizationInfo optimizationInfo = method.getOptimizationInfo();
-            if (optimizationInfo.isUpdatableMethodOptimizationInfo()) {
-              UpdatableMethodOptimizationInfo updatableOptimizationInfo =
-                  optimizationInfo.asUpdatableMethodOptimizationInfo();
-              updatableOptimizationInfo
-                  .fixupClassTypeReferences(appView.graphLens()::lookupType, appView)
-                  .fixupAbstractReturnValue(appView, appView.graphLens())
-                  .fixupInstanceInitializerInfo(appView, appView.graphLens());
-            } else {
-              assert optimizationInfo.isDefaultMethodOptimizationInfo();
-            }
+          public void fixup(
+              DexEncodedMethod method, UpdatableMethodOptimizationInfo optimizationInfo) {
+            optimizationInfo
+                .fixupClassTypeReferences(appView, appView.graphLens())
+                .fixupAbstractReturnValue(appView, appView.graphLens())
+                .fixupInstanceInitializerInfo(appView, appView.graphLens());
           }
         });
   }
@@ -479,29 +522,38 @@
     analyzeInitializers();
     analyzeAccessibility();
     EnumDataMap enumDataMap = analyzeEnumInstances();
-    assert enumDataMap.getUnboxedEnums().size() == enumUnboxingCandidatesInfo.candidates().size();
     if (debugLogEnabled) {
+      // Remove all enums that have been reported as being unboxable.
+      debugLogs.keySet().forEach(enumUnboxingCandidatesInfo::removeCandidate);
       reportEnumsAnalysis();
     }
+    assert enumDataMap.getUnboxedEnums().size() == enumUnboxingCandidatesInfo.candidates().size();
     return enumDataMap;
   }
 
   private EnumDataMap analyzeEnumInstances() {
     ImmutableMap.Builder<DexType, EnumData> builder = ImmutableMap.builder();
     enumUnboxingCandidatesInfo.forEachCandidateAndRequiredInstanceFieldData(
-        (enumClass, fields) -> {
-          EnumData data = buildData(enumClass, fields);
+        (enumClass, instanceFields) -> {
+          EnumData data = buildData(enumClass, instanceFields);
           if (data == null) {
-            markEnumAsUnboxable(Reason.MISSING_INSTANCE_FIELD_DATA, enumClass);
+            // Reason is already reported at this point.
+            enumUnboxingCandidatesInfo.removeCandidate(enumClass);
             return;
           }
-          builder.put(enumClass.type, data);
+          if (!debugLogEnabled || !debugLogs.containsKey(enumClass.getType())) {
+            builder.put(enumClass.type, data);
+          }
         });
     staticFieldValuesMap.clear();
     return new EnumDataMap(builder.build());
   }
 
-  private EnumData buildData(DexProgramClass enumClass, Set<DexField> fields) {
+  private EnumData buildData(DexProgramClass enumClass, Set<DexField> instanceFields) {
+    if (!enumClass.hasStaticFields()) {
+      return new EnumData(ImmutableMap.of(), ImmutableMap.of(), ImmutableSet.of(), -1);
+    }
+
     // This map holds all the accessible fields to their unboxed value, so we can remap the field
     // read to the unboxed value.
     ImmutableMap.Builder<DexField, Integer> unboxedValues = ImmutableMap.builder();
@@ -514,6 +566,10 @@
     EnumValuesObjectState valuesContents = null;
 
     EnumStaticFieldValues enumStaticFieldValues = staticFieldValuesMap.get(enumClass.type);
+    if (enumStaticFieldValues == null) {
+      reportFailure(enumClass, new MissingEnumStaticFieldValuesReason());
+      return null;
+    }
 
     // Step 1: We iterate over the field to find direct enum instance information and the values
     // fields.
@@ -527,10 +583,16 @@
             continue;
           }
           // We could not track the content of that field. We bail out.
+          reportFailure(
+              enumClass, new MissingObjectStateForEnumInstanceReason(staticField.getReference()));
           return null;
         }
         OptionalInt optionalOrdinal = getOrdinal(enumState);
         if (!optionalOrdinal.isPresent()) {
+          reportFailure(
+              enumClass,
+              new MissingInstanceFieldValueForEnumInstanceReason(
+                  staticField.getReference(), factory.enumMembers.ordinalField));
           return null;
         }
         int ordinal = optionalOrdinal.getAsInt();
@@ -547,6 +609,8 @@
           // We could not track the content of that field. We bail out.
           // We could not track the content of that field, and the field could be a values field.
           // We conservatively bail out.
+          reportFailure(
+              enumClass, new MissingContentsForEnumValuesArrayReason(staticField.getReference()));
           return null;
         }
         assert valuesState.isEnumValuesObjectState();
@@ -577,32 +641,41 @@
     // The ordinalToObjectState map may have holes at this point, if some enum instances are never
     // used ($VALUES unused or removed, and enum instance field unused or removed), it contains
     // only data for reachable enum instance, that is what we're interested in.
-    ImmutableMap.Builder<DexField, EnumInstanceFieldKnownData> instanceFieldBuilder =
-        ImmutableMap.builder();
-    for (DexField instanceField : fields) {
-      EnumInstanceFieldData fieldData =
-          computeEnumFieldData(instanceField, enumClass, ordinalToObjectState);
-      if (fieldData.isUnknown()) {
-        return null;
-      }
-      instanceFieldBuilder.put(instanceField, fieldData.asEnumFieldKnownData());
+    ImmutableMap<DexField, EnumInstanceFieldKnownData> instanceFieldsData =
+        computeRequiredEnumInstanceFieldsData(enumClass, instanceFields, ordinalToObjectState);
+    if (instanceFieldsData == null) {
+      return null;
     }
 
     return new EnumData(
-        instanceFieldBuilder.build(),
+        instanceFieldsData,
         unboxedValues.build(),
         valuesField.build(),
         valuesContents == null ? EnumData.INVALID_VALUES_SIZE : valuesContents.getEnumValuesSize());
   }
 
-  private boolean isFinalFieldInitialized(DexEncodedField staticField, DexProgramClass enumClass) {
-    assert staticField.isFinal();
-    return appView
-        .appInfo()
-        .isFieldOnlyWrittenInMethodIgnoringPinning(staticField, enumClass.getClassInitializer());
+  private ImmutableMap<DexField, EnumInstanceFieldKnownData> computeRequiredEnumInstanceFieldsData(
+      DexProgramClass enumClass,
+      Set<DexField> instanceFields,
+      Int2ReferenceMap<ObjectState> ordinalToObjectState) {
+    ImmutableMap.Builder<DexField, EnumInstanceFieldKnownData> builder = ImmutableMap.builder();
+    for (DexField instanceField : instanceFields) {
+      EnumInstanceFieldData fieldData =
+          computeRequiredEnumInstanceFieldData(instanceField, enumClass, ordinalToObjectState);
+      if (fieldData.isUnknown()) {
+        if (!debugLogEnabled) {
+          return null;
+        }
+        builder = null;
+      }
+      if (builder != null) {
+        builder.put(instanceField, fieldData.asEnumFieldKnownData());
+      }
+    }
+    return builder != null ? builder.build() : null;
   }
 
-  private EnumInstanceFieldData computeEnumFieldData(
+  private EnumInstanceFieldData computeRequiredEnumInstanceFieldData(
       DexField instanceField,
       DexProgramClass enumClass,
       Int2ReferenceMap<ObjectState> ordinalToObjectState) {
@@ -615,7 +688,15 @@
     for (Integer ordinal : ordinalToObjectState.keySet()) {
       ObjectState state = ordinalToObjectState.get(ordinal);
       AbstractValue fieldValue = state.getAbstractFieldValue(encodedInstanceField);
+      if (!fieldValue.isSingleValue()) {
+        reportFailure(
+            enumClass, new MissingInstanceFieldValueForEnumInstanceReason(ordinal, instanceField));
+        return EnumInstanceFieldUnknownData.getInstance();
+      }
       if (!(fieldValue.isSingleNumberValue() || fieldValue.isSingleStringValue())) {
+        reportFailure(
+            enumClass,
+            new UnsupportedInstanceFieldValueForEnumInstanceReason(ordinal, instanceField));
         return EnumInstanceFieldUnknownData.getInstance();
       }
       data.put(ordinalToUnboxedInt(ordinal), fieldValue);
@@ -888,29 +969,19 @@
   private void analyzeInitializers() {
     enumUnboxingCandidatesInfo.forEachCandidate(
         enumClass -> {
-          boolean hasInstanceInitializer = false;
           for (DexEncodedMethod directMethod : enumClass.directMethods()) {
             if (directMethod.isInstanceInitializer()) {
-              hasInstanceInitializer = true;
               if (directMethod
                   .getOptimizationInfo()
                   .getContextInsensitiveInstanceInitializerInfo()
                   .mayHaveOtherSideEffectsThanInstanceFieldAssignments()) {
-                markEnumAsUnboxable(Reason.INVALID_INIT, enumClass);
-                break;
+                if (markEnumAsUnboxable(Reason.INVALID_INIT, enumClass)) {
+                  break;
+                }
               }
             }
           }
-          if (!hasInstanceInitializer) {
-            // This case typically happens when a programmer uses EnumSet/EnumMap without using the
-            // enum keep rules. The code is incorrect in this case (EnumSet/EnumMap won't work).
-            // We bail out.
-            markEnumAsUnboxable(Reason.NO_INIT, enumClass);
-            return;
-          }
-
           if (enumClass.classInitializationMayHaveSideEffects(appView)) {
-            enumClass.classInitializationMayHaveSideEffects(appView);
             markEnumAsUnboxable(Reason.INVALID_CLINIT, enumClass);
           }
         });
@@ -1079,7 +1150,7 @@
       Value enumValue) {
     assert instanceGet.getField().holder == enumClass.type;
     DexField field = instanceGet.getField();
-    enumUnboxingCandidatesInfo.addRequiredEnumInstanceFieldData(enumClass.type, field);
+    enumUnboxingCandidatesInfo.addRequiredEnumInstanceFieldData(enumClass, field);
     return Reason.ELIGIBLE;
   }
 
@@ -1101,10 +1172,11 @@
     if (singleTarget == null) {
       return Reason.INVALID_INVOKE;
     }
-    DexClass dexClass = singleTarget.getHolder();
-    if (dexClass.isProgramClass()) {
-      if (dexClass.isEnum() && singleTarget.getDefinition().isInstanceInitializer()) {
-        if (code.method().getHolderType() == dexClass.type && code.method().isClassInitializer()) {
+    DexMethod singleTargetReference = singleTarget.getReference();
+    DexClass targetHolder = singleTarget.getHolder();
+    if (targetHolder.isProgramClass()) {
+      if (targetHolder.isEnum() && singleTarget.getDefinition().isInstanceInitializer()) {
+        if (code.context().getHolder() == targetHolder && code.method().isClassInitializer()) {
           // The enum instance initializer is allowed to be called only from the enum clinit.
           return Reason.ELIGIBLE;
         } else {
@@ -1113,28 +1185,28 @@
       }
       // Check that the enum-value only flows into parameters whose type exactly matches the
       // enum's type.
-      int offset = BooleanUtils.intValue(!singleTarget.getDefinition().isStatic());
-      for (int i = 0; i < singleTarget.getReference().getParameters().size(); i++) {
-        if (invoke.getArgument(offset + i) == enumValue) {
-          if (singleTarget.getReference().getParameter(i).toBaseType(factory) != enumClass.type) {
-            return Reason.GENERIC_INVOKE;
-          }
+      for (int i = 0; i < singleTarget.getParameters().size(); i++) {
+        if (invoke.getArgumentForParameter(i) == enumValue
+            && singleTarget.getParameter(i).toBaseType(factory) != enumClass.getType()) {
+          return new IllegalInvokeWithImpreciseParameterTypeReason(singleTargetReference);
         }
       }
       if (invoke.isInvokeMethodWithReceiver()) {
         Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver();
-        if (receiver == enumValue && dexClass.isInterface()) {
+        if (receiver == enumValue && targetHolder.isInterface()) {
           return Reason.DEFAULT_METHOD_INVOKE;
         }
       }
       return Reason.ELIGIBLE;
     }
-    if (dexClass.isClasspathClass()) {
-      return Reason.INVALID_INVOKE;
+
+    if (targetHolder.isClasspathClass()) {
+      return Reason.INVALID_INVOKE_CLASSPATH;
     }
-    assert dexClass.isLibraryClass();
-    DexMethod singleTargetReference = singleTarget.getReference();
-    if (dexClass.type != factory.enumType) {
+
+    assert targetHolder.isLibraryClass();
+
+    if (targetHolder.getType() != factory.enumType) {
       // System.identityHashCode(Object) is supported for proto enums.
       // Object#getClass without outValue and Objects.requireNonNull are supported since R8
       // rewrites explicit null checks to such instructions.
@@ -1142,7 +1214,7 @@
         return Reason.ELIGIBLE;
       }
       if (singleTargetReference == factory.stringMembers.valueOf) {
-        addRequiredNameData(enumClass.type);
+        addRequiredNameData(enumClass);
         return Reason.ELIGIBLE;
       }
       if (singleTargetReference == factory.objectMembers.getClass
@@ -1154,7 +1226,7 @@
           || singleTargetReference == factory.objectsMethods.requireNonNullWithMessage) {
         return Reason.ELIGIBLE;
       }
-      return Reason.UNSUPPORTED_LIBRARY_CALL;
+      return new UnsupportedLibraryInvokeReason(singleTargetReference);
     }
     // TODO(b/147860220): EnumSet and EnumMap may be interesting to model.
     if (singleTargetReference == factory.enumMembers.compareTo) {
@@ -1164,7 +1236,7 @@
     } else if (singleTargetReference == factory.enumMembers.nameMethod
         || singleTargetReference == factory.enumMembers.toString) {
       assert invoke.asInvokeMethodWithReceiver().getReceiver() == enumValue;
-      addRequiredNameData(enumClass.type);
+      addRequiredNameData(enumClass);
       return Reason.ELIGIBLE;
     } else if (singleTargetReference == factory.enumMembers.ordinalMethod) {
       return Reason.ELIGIBLE;
@@ -1172,12 +1244,11 @@
       return Reason.ELIGIBLE;
     } else if (singleTargetReference == factory.enumMembers.constructor) {
       // Enum constructor call is allowed only if called from an enum initializer.
-      if (code.method().isInstanceInitializer()
-          && code.method().getHolderType() == enumClass.type) {
+      if (code.method().isInstanceInitializer() && code.context().getHolder() == enumClass) {
         return Reason.ELIGIBLE;
       }
     }
-    return Reason.UNSUPPORTED_LIBRARY_CALL;
+    return new UnsupportedLibraryInvokeReason(singleTargetReference);
   }
 
   // Return is used for valueOf methods.
@@ -1196,30 +1267,102 @@
 
   private void reportEnumsAnalysis() {
     assert debugLogEnabled;
-    Reporter reporter = appView.options().reporter;
+    Reporter reporter = appView.reporter();
     Set<DexType> candidates = enumUnboxingCandidatesInfo.candidates();
     reporter.info(
         new StringDiagnostic(
-            "Unboxed enums (Unboxing succeeded "
-                + candidates.size()
-                + "): "
-                + Arrays.toString(candidates.toArray())));
-    StringBuilder sb = new StringBuilder();
-    sb.append("Boxed enums (Unboxing failed ").append(debugLogs.size()).append("):\n");
-    for (DexType enumType : debugLogs.keySet()) {
-      sb.append("- ")
-          .append(enumType)
-          .append(": ")
-          .append(debugLogs.get(enumType).toString())
-          .append('\n');
+            "Unboxed " + candidates.size() + " enums: " + Arrays.toString(candidates.toArray())));
+
+    StringBuilder sb =
+        new StringBuilder("Unable to unbox ")
+            .append(debugLogs.size())
+            .append(" enums.")
+            .append(System.lineSeparator())
+            .append(System.lineSeparator());
+
+    // Sort by the number of reasons that prevent enum unboxing.
+    TreeMap<DexType, List<Reason>> sortedDebugLogs =
+        new TreeMap<>(
+            Comparator.<DexType>comparingInt(x -> debugLogs.get(x).size())
+                .thenComparing(Function.identity()));
+    sortedDebugLogs.putAll(debugLogs);
+
+    // Print the pinned enums and remove them from further reporting.
+    List<DexType> pinned = new ArrayList<>();
+    Iterator<Entry<DexType, List<Reason>>> sortedDebugLogIterator =
+        sortedDebugLogs.entrySet().iterator();
+    while (sortedDebugLogIterator.hasNext()) {
+      Entry<DexType, List<Reason>> entry = sortedDebugLogIterator.next();
+      List<Reason> reasons = entry.getValue();
+      if (reasons.size() > 1) {
+        break;
+      }
+      if (reasons.get(0) == Reason.PINNED) {
+        pinned.add(entry.getKey());
+        sortedDebugLogIterator.remove();
+      }
     }
+    if (!pinned.isEmpty()) {
+      sb.append("Pinned: ").append(Arrays.toString(pinned.toArray()));
+    }
+
+    // Print the reasons for each unboxable enum.
+    sortedDebugLogs.forEach(
+        (type, reasons) -> {
+          sb.append(type).append(" (").append(reasons.size()).append(" reasons):");
+          HashMultiset.create(reasons)
+              .forEachEntry(
+                  (reason, count) ->
+                      sb.append(System.lineSeparator())
+                          .append(" - ")
+                          .append(reason)
+                          .append(" (")
+                          .append(count)
+                          .append(")"));
+          sb.append(System.lineSeparator());
+        });
+
+    sb.append(System.lineSeparator());
+
+    // Print information about how often a given Reason kind prevents enum unboxing.
+    Object2IntMap<Object> reasonKindCount = new Object2IntOpenHashMap<>();
+    debugLogs.forEach(
+        (type, reasons) ->
+            reasons.forEach(
+                reason ->
+                    reasonKindCount.put(reason.getKind(), reasonKindCount.getInt(reason) + 1)));
+    List<Object> differentReasonKinds = new ArrayList<>(reasonKindCount.keySet());
+    differentReasonKinds.sort(
+        (reasonKind, other) -> {
+          int freq = reasonKindCount.getInt(reasonKind) - reasonKindCount.getInt(other);
+          return freq != 0
+              ? freq
+              : System.identityHashCode(reasonKind) - System.identityHashCode(other);
+        });
+    differentReasonKinds.forEach(
+        reasonKind ->
+            sb.append(reasonKind)
+                .append(" (")
+                .append(reasonKindCount.getInt(reasonKind))
+                .append(")")
+                .append(System.lineSeparator()));
+
     reporter.info(new StringDiagnostic(sb.toString()));
   }
 
-  void reportFailure(DexType enumType, Reason reason) {
+  boolean reportFailure(DexProgramClass enumClass, Reason reason) {
+    return reportFailure(enumClass.getType(), reason);
+  }
+
+  /** Returns true if the failure was reported. */
+  boolean reportFailure(DexType enumType, Reason reason) {
     if (debugLogEnabled) {
-      debugLogs.put(enumType, reason);
+      debugLogs
+          .computeIfAbsent(enumType, ignore -> Collections.synchronizedList(new ArrayList<>()))
+          .add(reason);
+      return true;
     }
+    return false;
   }
 
   public Set<Phi> rewriteCode(IRCode code) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index 9196ca3..62b05a9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.optimize.enums.eligibility.Reason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.UnsupportedStaticFieldReason;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 
@@ -53,50 +54,62 @@
     if (!clazz.isEnum()) {
       return false;
     }
+
+    boolean result = true;
     if (clazz.superType != factory.enumType || !clazz.isEffectivelyFinal(appView)) {
-      enumUnboxer.reportFailure(clazz.type, Reason.SUBTYPES);
-      return false;
+      if (!enumUnboxer.reportFailure(clazz, Reason.SUBTYPES)) {
+        return false;
+      }
+      // Record that `clazz` is ineligible, and continue analysis to ensure all reasons are reported
+      // for debugging.
+      result = false;
     }
     if (clazz.instanceFields().size() > MAX_INSTANCE_FIELDS_FOR_UNBOXING) {
-      enumUnboxer.reportFailure(clazz.type, Reason.MANY_INSTANCE_FIELDS);
-      return false;
+      if (!enumUnboxer.reportFailure(clazz, Reason.MANY_INSTANCE_FIELDS)) {
+        return false;
+      }
+      result = false;
     }
     if (!enumHasBasicStaticFields(clazz)) {
-      enumUnboxer.reportFailure(clazz.type, Reason.UNEXPECTED_STATIC_FIELD);
-      return false;
+      result = false;
     }
-    return true;
+    return result;
   }
 
   // The enum should have the $VALUES static field and only fields directly referencing the enum
   // instances.
   private boolean enumHasBasicStaticFields(DexProgramClass clazz) {
+    boolean result = true;
     for (DexEncodedField staticField : clazz.staticFields()) {
-      if (isEnumField(staticField, clazz.type)) {
+      if (isEnumField(staticField, clazz)) {
         // Enum field, valid, do nothing.
-      } else if (matchesValuesField(staticField, clazz.type, factory)) {
+      } else if (matchesValuesField(staticField, clazz, factory)) {
         // Field $VALUES, valid, do nothing.
       } else if (appView.appInfo().isFieldRead(staticField)) {
         // Only non read static fields are valid, and they are assumed unused.
-        return false;
+        if (!enumUnboxer.reportFailure(
+            clazz, new UnsupportedStaticFieldReason(staticField.getReference()))) {
+          return false;
+        }
+        result = false;
       }
     }
-    return true;
+    return result;
   }
 
-  static boolean isEnumField(DexEncodedField staticField, DexType enumType) {
-    return staticField.getReference().type == enumType
-        && staticField.accessFlags.isEnum()
-        && staticField.accessFlags.isFinal();
+  static boolean isEnumField(DexEncodedField staticField, DexProgramClass enumClass) {
+    return staticField.getType() == enumClass.getType()
+        && staticField.isEnum()
+        && staticField.isFinal();
   }
 
   static boolean matchesValuesField(
-      DexEncodedField staticField, DexType enumType, DexItemFactory factory) {
-    return staticField.getReference().type.isArrayType()
-        && staticField.getReference().type.toArrayElementType(factory) == enumType
-        && staticField.accessFlags.isSynthetic()
-        && staticField.accessFlags.isFinal()
-        && staticField.getReference().name == factory.enumValuesFieldName;
+      DexEncodedField staticField, DexProgramClass enumClass, DexItemFactory factory) {
+    return staticField.getType().isArrayType()
+        && staticField.getType().toArrayElementType(factory) == enumClass.getType()
+        && staticField.isSynthetic()
+        && staticField.isFinal()
+        && staticField.getName() == factory.enumValuesFieldName;
   }
 
   private void removeEnumsInAnnotations() {
@@ -116,8 +129,9 @@
           || appView.options().testing.allowInjectedAnnotationMethods;
       DexType valueType = method.returnType().toBaseType(appView.dexItemFactory());
       if (enumToUnboxCandidates.isCandidate(valueType)) {
-        enumUnboxer.reportFailure(valueType, Reason.ANNOTATION);
-        enumToUnboxCandidates.removeCandidate(valueType);
+        if (!enumUnboxer.reportFailure(valueType, Reason.ANNOTATION)) {
+          enumToUnboxCandidates.removeCandidate(valueType);
+        }
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java
index 947cc6d..7d85b8d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java
@@ -26,6 +26,10 @@
     enumTypeToInfo.put(enumClass.type, new EnumUnboxingCandidateInfo(enumClass));
   }
 
+  public void removeCandidate(DexProgramClass enumClass) {
+    removeCandidate(enumClass.getType());
+  }
+
   public void removeCandidate(DexType enumType) {
     enumTypeToInfo.remove(enumType);
   }
@@ -77,11 +81,11 @@
     info.addMethodDependency(programMethod);
   }
 
-  public void addRequiredEnumInstanceFieldData(DexType enumType, DexField field) {
+  public void addRequiredEnumInstanceFieldData(DexProgramClass enumClass, DexField field) {
     // The enumType may be removed concurrently map from enumTypeToInfo. It means in that
     // case the enum is no longer a candidate, and dependencies don't need to be recorded
     // anymore.
-    EnumUnboxingCandidateInfo info = enumTypeToInfo.get(enumType);
+    EnumUnboxingCandidateInfo info = enumTypeToInfo.get(enumClass.getType());
     if (info == null) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
index 2cd2098..429bdae 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
@@ -4,46 +4,236 @@
 
 package com.android.tools.r8.ir.optimize.enums.eligibility;
 
-public class Reason {
-  public static final Reason ELIGIBLE = new Reason("ELIGIBLE");
-  public static final Reason ACCESSIBILITY = new Reason("ACCESSIBILITY");
-  public static final Reason ANNOTATION = new Reason("ANNOTATION");
-  public static final Reason PINNED = new Reason("PINNED");
-  public static final Reason DOWN_CAST = new Reason("DOWN_CAST");
-  public static final Reason SUBTYPES = new Reason("SUBTYPES");
-  public static final Reason MANY_INSTANCE_FIELDS = new Reason("MANY_INSTANCE_FIELDS");
-  public static final Reason GENERIC_INVOKE = new Reason("GENERIC_INVOKE");
-  public static final Reason DEFAULT_METHOD_INVOKE = new Reason("DEFAULT_METHOD_INVOKE");
-  public static final Reason UNEXPECTED_STATIC_FIELD = new Reason("UNEXPECTED_STATIC_FIELD");
-  public static final Reason UNRESOLVABLE_FIELD = new Reason("UNRESOLVABLE_FIELD");
-  public static final Reason CONST_CLASS = new Reason("CONST_CLASS");
-  public static final Reason INVALID_PHI = new Reason("INVALID_PHI");
-  public static final Reason NO_INIT = new Reason("NO_INIT");
-  public static final Reason INVALID_INIT = new Reason("INVALID_INIT");
-  public static final Reason INVALID_CLINIT = new Reason("INVALID_CLINIT");
-  public static final Reason INVALID_INVOKE = new Reason("INVALID_INVOKE");
-  public static final Reason INVALID_INVOKE_ON_ARRAY = new Reason("INVALID_INVOKE_ON_ARRAY");
-  public static final Reason IMPLICIT_UP_CAST_IN_RETURN = new Reason("IMPLICIT_UP_CAST_IN_RETURN");
-  public static final Reason UNSUPPORTED_LIBRARY_CALL = new Reason("UNSUPPORTED_LIBRARY_CALL");
-  public static final Reason MISSING_INSTANCE_FIELD_DATA =
-      new Reason("MISSING_INSTANCE_FIELD_DATA");
-  public static final Reason INVALID_FIELD_PUT = new Reason("INVALID_FIELD_PUT");
-  public static final Reason INVALID_ARRAY_PUT = new Reason("INVALID_ARRAY_PUT");
-  public static final Reason TYPE_MISMATCH_FIELD_PUT = new Reason("TYPE_MISMATCH_FIELD_PUT");
-  public static final Reason INVALID_IF_TYPES = new Reason("INVALID_IF_TYPES");
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.google.common.collect.ImmutableList;
+
+public abstract class Reason {
+  public static final Reason ELIGIBLE = new StringReason("ELIGIBLE");
+  public static final Reason ACCESSIBILITY = new StringReason("ACCESSIBILITY");
+  public static final Reason ANNOTATION = new StringReason("ANNOTATION");
+  public static final Reason PINNED = new StringReason("PINNED");
+  public static final Reason DOWN_CAST = new StringReason("DOWN_CAST");
+  public static final Reason SUBTYPES = new StringReason("SUBTYPES");
+  public static final Reason MANY_INSTANCE_FIELDS = new StringReason("MANY_INSTANCE_FIELDS");
+  public static final Reason DEFAULT_METHOD_INVOKE = new StringReason("DEFAULT_METHOD_INVOKE");
+  public static final Reason UNRESOLVABLE_FIELD = new StringReason("UNRESOLVABLE_FIELD");
+  public static final Reason CONST_CLASS = new StringReason("CONST_CLASS");
+  public static final Reason INVALID_PHI = new StringReason("INVALID_PHI");
+  public static final Reason NO_INIT = new StringReason("NO_INIT");
+  public static final Reason INVALID_INIT = new StringReason("INVALID_INIT");
+  public static final Reason INVALID_CLINIT = new StringReason("INVALID_CLINIT");
+  public static final Reason INVALID_INVOKE = new StringReason("INVALID_INVOKE");
+  public static final Reason INVALID_INVOKE_CLASSPATH =
+      new StringReason("INVALID_INVOKE_CLASSPATH");
+  public static final Reason INVALID_INVOKE_ON_ARRAY = new StringReason("INVALID_INVOKE_ON_ARRAY");
+  public static final Reason IMPLICIT_UP_CAST_IN_RETURN =
+      new StringReason("IMPLICIT_UP_CAST_IN_RETURN");
+  public static final Reason INVALID_FIELD_PUT = new StringReason("INVALID_FIELD_PUT");
+  public static final Reason INVALID_ARRAY_PUT = new StringReason("INVALID_ARRAY_PUT");
+  public static final Reason TYPE_MISMATCH_FIELD_PUT = new StringReason("TYPE_MISMATCH_FIELD_PUT");
+  public static final Reason INVALID_IF_TYPES = new StringReason("INVALID_IF_TYPES");
   public static final Reason ENUM_METHOD_CALLED_WITH_NULL_RECEIVER =
-      new Reason("ENUM_METHOD_CALLED_WITH_NULL_RECEIVER");
+      new StringReason("ENUM_METHOD_CALLED_WITH_NULL_RECEIVER");
   public static final Reason OTHER_UNSUPPORTED_INSTRUCTION =
-      new Reason("OTHER_UNSUPPORTED_INSTRUCTION");
+      new StringReason("OTHER_UNSUPPORTED_INSTRUCTION");
 
-  private final String message;
-
-  public Reason(String message) {
-    this.message = message;
-  }
+  public abstract Object getKind();
 
   @Override
-  public String toString() {
-    return message;
+  public abstract String toString();
+
+  public static class StringReason extends Reason {
+
+    private final String message;
+
+    public StringReason(String message) {
+      this.message = message;
+    }
+
+    @Override
+    public Object getKind() {
+      return this;
+    }
+
+    @Override
+    public String toString() {
+      return message;
+    }
+  }
+
+  public static class IllegalInvokeWithImpreciseParameterTypeReason extends Reason {
+
+    private final DexMethod invokedMethod;
+
+    public IllegalInvokeWithImpreciseParameterTypeReason(DexMethod invokedMethod) {
+      this.invokedMethod = invokedMethod;
+    }
+
+    @Override
+    public Object getKind() {
+      return getClass();
+    }
+
+    @Override
+    public String toString() {
+      return "IllegalInvokeWithImpreciseParameterType(" + invokedMethod.toSourceString() + ")";
+    }
+  }
+
+  public static class MissingEnumStaticFieldValuesReason extends Reason {
+
+    @Override
+    public Object getKind() {
+      return getClass();
+    }
+
+    @Override
+    public String toString() {
+      return "MissingEnumStaticFieldValues";
+    }
+  }
+
+  public static class MissingContentsForEnumValuesArrayReason extends Reason {
+
+    private final DexField valuesField;
+
+    public MissingContentsForEnumValuesArrayReason(DexField valuesField) {
+      this.valuesField = valuesField;
+    }
+
+    @Override
+    public Object getKind() {
+      return getClass();
+    }
+
+    @Override
+    public String toString() {
+      return "MissingContentsForEnumValuesArray(" + valuesField.toSourceString() + ")";
+    }
+  }
+
+  public static class MissingInstanceFieldValueForEnumInstanceReason extends Reason {
+
+    private final DexField enumField;
+    private final int ordinal;
+    private final DexField instanceField;
+
+    public MissingInstanceFieldValueForEnumInstanceReason(
+        DexField enumField, DexField instanceField) {
+      this.enumField = enumField;
+      this.ordinal = -1;
+      this.instanceField = instanceField;
+    }
+
+    public MissingInstanceFieldValueForEnumInstanceReason(int ordinal, DexField instanceField) {
+      this.enumField = null;
+      this.ordinal = ordinal;
+      this.instanceField = instanceField;
+    }
+
+    @Override
+    public Object getKind() {
+      return getClass();
+    }
+
+    @Override
+    public String toString() {
+      if (enumField != null) {
+        return "MissingInstanceFieldValueForEnumInstance(enum field="
+            + enumField.toSourceString()
+            + ", instance field="
+            + instanceField.toSourceString()
+            + ")";
+      }
+      assert ordinal >= 0;
+      return "MissingInstanceFieldValueForEnumInstance(ordinal="
+          + ordinal
+          + ", instance field="
+          + instanceField.toSourceString()
+          + ")";
+    }
+  }
+
+  public static class MissingObjectStateForEnumInstanceReason extends Reason {
+
+    private final DexField enumField;
+
+    public MissingObjectStateForEnumInstanceReason(DexField enumField) {
+      this.enumField = enumField;
+    }
+
+    @Override
+    public Object getKind() {
+      return getClass();
+    }
+
+    @Override
+    public String toString() {
+      return "MissingObjectStateForEnumInstance(" + enumField + ")";
+    }
+  }
+
+  public static class UnsupportedInstanceFieldValueForEnumInstanceReason extends Reason {
+
+    private final int ordinal;
+    private final DexField instanceField;
+
+    public UnsupportedInstanceFieldValueForEnumInstanceReason(int ordinal, DexField instanceField) {
+      this.ordinal = ordinal;
+      this.instanceField = instanceField;
+    }
+
+    @Override
+    public Object getKind() {
+      return getClass();
+    }
+
+    @Override
+    public String toString() {
+      return "UnsupportedInstanceFieldValueForEnumInstance(ordinal="
+          + ordinal
+          + ", instance field="
+          + instanceField.toSourceString()
+          + ")";
+    }
+  }
+
+  public static class UnsupportedLibraryInvokeReason extends Reason {
+
+    private final DexMethod invokedMethod;
+
+    public UnsupportedLibraryInvokeReason(DexMethod invokedMethod) {
+      this.invokedMethod = invokedMethod;
+    }
+
+    @Override
+    public Object getKind() {
+      return ImmutableList.of(getClass(), invokedMethod);
+    }
+
+    @Override
+    public String toString() {
+      return "UnsupportedLibraryInvoke(" + invokedMethod.toSourceString() + ")";
+    }
+  }
+
+  public static class UnsupportedStaticFieldReason extends Reason {
+
+    private final DexField field;
+
+    public UnsupportedStaticFieldReason(DexField field) {
+      this.field = field;
+    }
+
+    @Override
+    public Object getKind() {
+      return getClass();
+    }
+
+    @Override
+    public String toString() {
+      return "UnsupportedStaticField(" + field.toSourceString() + ")";
+    }
   }
 }
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 41ee658..b4141c6 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
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.info;
 
+import static java.util.Collections.emptySet;
+
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
@@ -13,7 +15,7 @@
 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 java.util.function.Function;
+import java.util.Set;
 
 /**
  * Optimization info for fields.
@@ -35,13 +37,20 @@
   private TypeElement dynamicUpperBoundType = null;
 
   public MutableFieldOptimizationInfo fixupClassTypeReferences(
-      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithClassHierarchy> appView) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, GraphLens lens) {
+    return fixupClassTypeReferences(appView, lens, emptySet());
+  }
+
+  public MutableFieldOptimizationInfo fixupClassTypeReferences(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      GraphLens lens,
+      Set<DexType> prunedTypes) {
     if (dynamicUpperBoundType != null) {
-      dynamicUpperBoundType = dynamicUpperBoundType.fixupClassTypeReferences(mapping, appView);
+      dynamicUpperBoundType = dynamicUpperBoundType.rewrittenWithLens(appView, lens, prunedTypes);
     }
     if (dynamicLowerBoundType != null) {
       TypeElement dynamicLowerBoundType =
-          this.dynamicLowerBoundType.fixupClassTypeReferences(mapping, appView);
+          this.dynamicLowerBoundType.rewrittenWithLens(appView, lens);
       if (dynamicLowerBoundType.isClassType()) {
         this.dynamicLowerBoundType = dynamicLowerBoundType.asClassType();
       } else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
index 46cff7b..b384693 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.ir.conversion.FieldOptimizationFeedback;
 import com.android.tools.r8.ir.conversion.MethodOptimizationFeedback;
 import com.android.tools.r8.shaking.AppInfoWithLivenessModifier;
@@ -20,19 +21,41 @@
 
   public interface OptimizationInfoFixer {
 
-    void fixup(DexEncodedField field);
+    void fixup(DexEncodedField field, MutableFieldOptimizationInfo optimizationInfo);
 
-    void fixup(DexEncodedMethod method);
+    void fixup(DexEncodedMethod method, UpdatableMethodOptimizationInfo optimizationInfo);
   }
 
   public void fixupOptimizationInfos(
       AppView<?> appView, ExecutorService executorService, OptimizationInfoFixer fixer)
       throws ExecutionException {
+    fixupOptimizationInfos(appView.appInfo().classes(), executorService, fixer);
+  }
+
+  public void fixupOptimizationInfos(
+      Iterable<DexProgramClass> classes,
+      ExecutorService executorService,
+      OptimizationInfoFixer fixer)
+      throws ExecutionException {
     ThreadUtils.processItems(
-        appView.appInfo().classes(),
+        classes,
         clazz -> {
-          clazz.fields().forEach(fixer::fixup);
-          clazz.methods().forEach(fixer::fixup);
+          for (DexEncodedField field : clazz.fields()) {
+            FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo();
+            if (optimizationInfo.isMutableFieldOptimizationInfo()) {
+              fixer.fixup(field, optimizationInfo.asMutableFieldOptimizationInfo());
+            } else {
+              assert optimizationInfo.isDefaultFieldOptimizationInfo();
+            }
+          }
+          for (DexEncodedMethod method : clazz.methods()) {
+            MethodOptimizationInfo optimizationInfo = method.getOptimizationInfo();
+            if (optimizationInfo.isUpdatableMethodOptimizationInfo()) {
+              fixer.fixup(method, optimizationInfo.asUpdatableMethodOptimizationInfo());
+            } else {
+              assert optimizationInfo.isDefaultMethodOptimizationInfo();
+            }
+          }
         },
         executorService);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index d6bdd6b..03eee35 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.info;
 
+import static java.util.Collections.emptySet;
+
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
@@ -23,7 +25,6 @@
 import com.android.tools.r8.utils.BooleanUtils;
 import java.util.BitSet;
 import java.util.Set;
-import java.util.function.Function;
 
 public class UpdatableMethodOptimizationInfo extends MethodOptimizationInfo {
 
@@ -147,14 +148,21 @@
   }
 
   public UpdatableMethodOptimizationInfo fixupClassTypeReferences(
-      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithClassHierarchy> appView) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, GraphLens lens) {
+    return fixupClassTypeReferences(appView, lens, emptySet());
+  }
+
+  public UpdatableMethodOptimizationInfo fixupClassTypeReferences(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      GraphLens lens,
+      Set<DexType> prunedTypes) {
     if (returnsObjectWithUpperBoundType != null) {
       returnsObjectWithUpperBoundType =
-          returnsObjectWithUpperBoundType.fixupClassTypeReferences(mapping, appView);
+          returnsObjectWithUpperBoundType.rewrittenWithLens(appView, lens, prunedTypes);
     }
     if (returnsObjectWithLowerBoundType != null) {
       TypeElement returnsObjectWithLowerBoundType =
-          this.returnsObjectWithLowerBoundType.fixupClassTypeReferences(mapping, appView);
+          this.returnsObjectWithLowerBoundType.rewrittenWithLens(appView, lens, prunedTypes);
       if (returnsObjectWithLowerBoundType.isClassType()) {
         this.returnsObjectWithLowerBoundType = returnsObjectWithLowerBoundType.asClassType();
       } else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
index 262671f..708c84a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
@@ -62,12 +62,9 @@
     }
     return new InstanceFieldTypeInitializationInfo(
         dynamicLowerBoundType != null
-            ? dynamicLowerBoundType
-                .fixupClassTypeReferences(lens::lookupType, appView.withClassHierarchy())
-                .asClassType()
+            ? dynamicLowerBoundType.rewrittenWithLens(appView, lens).asClassType()
             : null,
-        dynamicUpperBoundType.fixupClassTypeReferences(
-            lens::lookupType, appView.withClassHierarchy()));
+        dynamicUpperBoundType.rewrittenWithLens(appView, lens));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index 7761bfa..f600a41 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -519,8 +520,8 @@
     }
     // Allow single assignment to a singleton field.
     StaticPut staticPut = instruction.asStaticPut();
-    DexEncodedField fieldAccessed = appView.appInfo().lookupStaticTarget(staticPut.getField());
-    return fieldAccessed == info.singletonField;
+    DexClassAndField fieldAccessed = appView.appInfo().lookupStaticTarget(staticPut.getField());
+    return fieldAccessed != null && fieldAccessed.getDefinition() == info.singletonField;
   }
 
   // Only allow a very trivial pattern: load the singleton field and return it, which looks like:
@@ -536,8 +537,8 @@
     for (Instruction instr : code.instructions()) {
       if (instr.isStaticGet()) {
         staticGet = instr.asStaticGet();
-        DexEncodedField fieldAccessed = appView.appInfo().lookupStaticTarget(staticGet.getField());
-        if (fieldAccessed != info.singletonField) {
+        DexClassAndField fieldAccessed = appView.appInfo().lookupStaticTarget(staticGet.getField());
+        if (fieldAccessed == null || fieldAccessed.getDefinition() != info.singletonField) {
           return null;
         }
         instructions.add(instr);
@@ -568,7 +569,8 @@
       return null;
     }
 
-    assert candidateInfo.singletonField == appView.appInfo().lookupStaticTarget(field)
+    assert candidateInfo.singletonField
+            == appView.appInfo().lookupStaticTarget(field).getDefinition()
         : "Added reference after collectCandidates(...)?";
 
     Value singletonValue = staticGet.dest();
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
index 8c6c2b8..256ee1e 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
@@ -15,6 +15,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Predicate;
 
 // This is a parser for the Kotlin-generated source debug extensions, which is a stratified map.
 // The kotlin-parser for this data is can be found here in the kotlin compiler:
@@ -80,13 +81,21 @@
         int linesInBlock,
         ThrowingConsumer<List<String>, KotlinSourceDebugExtensionParserException> callback)
         throws IOException, KotlinSourceDebugExtensionParserException {
-      if (terminator.equals(readLine)) {
+      readUntil(terminator::equals, linesInBlock, callback);
+    }
+
+    void readUntil(
+        Predicate<String> terminator,
+        int linesInBlock,
+        ThrowingConsumer<List<String>, KotlinSourceDebugExtensionParserException> callback)
+        throws IOException, KotlinSourceDebugExtensionParserException {
+      if (terminator.test(readLine)) {
         return;
       }
       List<String> readStrings = new ArrayList<>();
       readStrings.add(readNextLine());
       int linesLeft = linesInBlock;
-      while (!terminator.equals(readLine) && !isEOF()) {
+      while (!terminator.test(readLine) && !isEOF()) {
         if (linesLeft == 1) {
           assert readStrings.size() == linesInBlock;
           callback.accept(readStrings);
@@ -97,7 +106,7 @@
         }
         readStrings.add(readNextLine());
       }
-      if (readStrings.size() > 0 && !readStrings.get(0).equals(terminator)) {
+      if (readStrings.size() > 0 && !terminator.test(readStrings.get(0))) {
         throw new KotlinSourceDebugExtensionParserException(
             "Block size does not match linesInBlock = " + linesInBlock);
       }
@@ -125,7 +134,7 @@
     // *L
     // <from>#<file>,<to>:<debug-line-position>
     // <from>#<file>:<debug-line-position>
-    // *E
+    // *E <-- This is an error in versions prior to kotlin 1.5 and is not present in kotlin 1.5.
     // *S KotlinDebug
     // ***
     // *E
@@ -157,10 +166,12 @@
       // or
       // <from>#<file>:<debug-line-position>
       reader.readUntil(
-          SMAP_END_IDENTIFIER, 1, block -> addDebugEntryToBuilder(block.get(0), builder));
+          line -> line.equals(SMAP_END_IDENTIFIER) || line.startsWith(SMAP_SECTION_START),
+          1,
+          block -> addDebugEntryToBuilder(block.get(0), builder));
 
-      // Ensure that we read the end section *E.
-      if (reader.isEOF()) {
+      // Ensure that we read the end section and it is terminated.
+      if (reader.isEOF() && !reader.readLine.equals(SMAP_END_IDENTIFIER)) {
         throw new KotlinSourceDebugExtensionParserException(
             "Unexpected EOF when parsing SMAP debug entries");
       }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 0583a2f..26742bd 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -5,7 +5,6 @@
 
 import static com.android.tools.r8.naming.ClassNameMapper.MissingFileAction.MISSING_FILE_IS_ERROR;
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
-import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.graph.DexField;
@@ -15,16 +14,12 @@
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
-import com.android.tools.r8.naming.mappinginformation.MappingInformation;
-import com.android.tools.r8.naming.mappinginformation.ScopeReference;
 import com.android.tools.r8.position.Position;
-import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.BiMapContainer;
 import com.android.tools.r8.utils.ChainableStringConsumer;
 import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableBiMap;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.io.CharSource;
 import java.io.BufferedReader;
@@ -32,14 +27,11 @@
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.function.Consumer;
 
 public class ClassNameMapper implements ProguardMap {
 
@@ -50,7 +42,6 @@
 
   public static class Builder extends ProguardMap.Builder {
     private final Map<String, ClassNamingForNameMapper.Builder> mapping = new HashMap<>();
-    private final Map<ScopeReference, List<MappingInformation>> scopedMappingInfo = new HashMap<>();
 
     private Builder() {
 
@@ -66,42 +57,11 @@
     }
 
     @Override
-    public void addMappingInformation(
-        ScopeReference reference,
-        MappingInformation info,
-        Consumer<MappingInformation> onProhibitedAddition) {
-      List<MappingInformation> additionalMappings =
-          scopedMappingInfo.computeIfAbsent(reference, ignoreArgument(ArrayList::new));
-      for (MappingInformation existing : additionalMappings) {
-        if (!existing.allowOther(info)) {
-          onProhibitedAddition.accept(existing);
-        }
-      }
-      additionalMappings.add(info);
-    }
-
-    @Override
     public ClassNameMapper build() {
-      return new ClassNameMapper(buildClassNameMappings(), buildScopedMappingInfo());
-    }
-
-    private ImmutableMap<ScopeReference, ImmutableList<MappingInformation>>
-        buildScopedMappingInfo() {
-      ImmutableMap.Builder<ScopeReference, ImmutableList<MappingInformation>> builder =
-          ImmutableMap.builder();
-      scopedMappingInfo.forEach((ref, infos) -> builder.put(ref, ImmutableList.copyOf(infos)));
-      return builder.build();
+      return new ClassNameMapper(buildClassNameMappings());
     }
 
     private ImmutableMap<String, ClassNamingForNameMapper> buildClassNameMappings() {
-      // Ensure that all scoped references have at least the identity in the final mapping.
-      for (ScopeReference reference : scopedMappingInfo.keySet()) {
-        if (!reference.isGlobalScope()) {
-          mapping.computeIfAbsent(
-              reference.getHolderReference().getTypeName(),
-              t -> ClassNamingForNameMapper.builder(t, t));
-        }
-      }
       ImmutableMap.Builder<String, ClassNamingForNameMapper> builder = ImmutableMap.builder();
       builder.orderEntriesByValue(Comparator.comparing(x -> x.originalName));
       mapping.forEach(
@@ -166,26 +126,17 @@
   }
 
   private final ImmutableMap<String, ClassNamingForNameMapper> classNameMappings;
-  private final ImmutableMap<ScopeReference, ImmutableList<MappingInformation>>
-      additionalMappingInfo;
   private BiMapContainer<String, String> nameMapping;
   private final Map<Signature, Signature> signatureMap = new HashMap<>();
 
-  private ClassNameMapper(
-      ImmutableMap<String, ClassNamingForNameMapper> classNameMappings,
-      ImmutableMap<ScopeReference, ImmutableList<MappingInformation>> additionalMappingInfo) {
+  private ClassNameMapper(ImmutableMap<String, ClassNamingForNameMapper> classNameMappings) {
     this.classNameMappings = classNameMappings;
-    this.additionalMappingInfo = additionalMappingInfo;
   }
 
   public Map<String, ClassNamingForNameMapper> getClassNameMappings() {
     return classNameMappings;
   }
 
-  public List<MappingInformation> getAdditionalMappingInfo(ScopeReference reference) {
-    return additionalMappingInfo.getOrDefault(reference, ImmutableList.of());
-  }
-
   private Signature canonicalizeSignature(Signature signature) {
     Signature result = signatureMap.get(signature);
     if (result != null) {
@@ -255,7 +206,7 @@
     ImmutableMap.Builder<String, ClassNamingForNameMapper> builder = ImmutableMap.builder();
     builder.orderEntriesByValue(Comparator.comparing(x -> x.originalName));
     classNameMappings.forEach(builder::put);
-    return new ClassNameMapper(builder.build(), additionalMappingInfo);
+    return new ClassNameMapper(builder.build());
   }
 
   public boolean verifyIsSorted() {
@@ -277,10 +228,7 @@
     // deterministic (and easy to navigate manually).
     assert verifyIsSorted();
     for (ClassNamingForNameMapper naming : getClassNameMappings().values()) {
-      List<MappingInformation> additionalMappingInfo =
-          getAdditionalMappingInfo(
-              ScopeReference.fromClassReference(Reference.classFromTypeName(naming.renamedName)));
-      naming.write(consumer, additionalMappingInfo);
+      naming.write(consumer);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNaming.java b/src/main/java/com/android/tools/r8/naming/ClassNaming.java
index 429ed45..c9de8ab 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNaming.java
@@ -3,8 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
 import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.utils.ThrowingConsumer;
+import java.util.function.Consumer;
 
 /**
  * Stores name information for a class.
@@ -20,11 +23,14 @@
     public abstract ClassNaming build();
 
     /** This is an optional method, may be implemented as no-op */
-    public abstract void addMappedRange(
+    public abstract MappedRange addMappedRange(
         Range obfuscatedRange,
         MemberNaming.MethodSignature originalSignature,
         Object originalRange,
         String obfuscatedName);
+
+    public abstract void addMappingInformation(
+        MappingInformation info, Consumer<MappingInformation> onProhibitedAddition);
   }
 
   MemberNaming lookup(Signature renamedSignature);
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
index c8e2f2b..a222acb 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
@@ -6,10 +6,12 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind;
+import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ThrowingConsumer;
@@ -20,6 +22,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
 
 /**
  * Stores name information for a class.
@@ -82,12 +85,19 @@
     }
 
     @Override
-    /** No-op */
-    public void addMappedRange(
+    public MappedRange addMappedRange(
         Range obfuscatedRange,
         MemberNaming.MethodSignature originalSignature,
         Object originalRange,
-        String obfuscatedName) {}
+        String obfuscatedName) {
+      return null;
+    }
+
+    @Override
+    public void addMappingInformation(
+        MappingInformation info, Consumer<MappingInformation> onProhibitedAddition) {
+      // Intentionally empty.
+    }
   }
 
   static Builder builder(
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 523184b..1d837b3 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -20,6 +20,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.function.Consumer;
 
 /**
  * Stores name information for a class.
@@ -36,6 +37,7 @@
     private final Map<FieldSignature, MemberNaming> fieldMembers = Maps.newHashMap();
     private final Map<String, List<MappedRange>> mappedRangesByName = Maps.newHashMap();
     private final Map<String, List<MemberNaming>> mappedFieldNamingsByName = Maps.newHashMap();
+    private final List<MappingInformation> additionalMappingInfo = new ArrayList<>();
 
     private Builder(String renamedName, String originalName) {
       this.originalName = originalName;
@@ -69,19 +71,38 @@
       }
 
       return new ClassNamingForNameMapper(
-          renamedName, originalName, methodMembers, fieldMembers, map, mappedFieldNamingsByName);
+          renamedName,
+          originalName,
+          methodMembers,
+          fieldMembers,
+          map,
+          mappedFieldNamingsByName,
+          additionalMappingInfo);
     }
 
     /** The parameters are forwarded to MappedRange constructor, see explanation there. */
     @Override
-    public void addMappedRange(
+    public MappedRange addMappedRange(
         Range minifiedRange,
         MemberNaming.MethodSignature originalSignature,
         Object originalRange,
         String renamedName) {
-      mappedRangesByName
-          .computeIfAbsent(renamedName, k -> new ArrayList<>())
-          .add(new MappedRange(minifiedRange, originalSignature, originalRange, renamedName));
+      MappedRange range =
+          new MappedRange(minifiedRange, originalSignature, originalRange, renamedName);
+      mappedRangesByName.computeIfAbsent(renamedName, k -> new ArrayList<>()).add(range);
+      return range;
+    }
+
+    @Override
+    public void addMappingInformation(
+        MappingInformation info, Consumer<MappingInformation> onProhibitedAddition) {
+      for (MappingInformation existing : additionalMappingInfo) {
+        if (!existing.allowOther(info)) {
+          onProhibitedAddition.accept(existing);
+          return;
+        }
+      }
+      additionalMappingInfo.add(info);
     }
   }
 
@@ -200,19 +221,27 @@
 
   public final Map<String, List<MemberNaming>> mappedFieldNamingsByName;
 
+  private final List<MappingInformation> additionalMappingInfo;
+
   private ClassNamingForNameMapper(
       String renamedName,
       String originalName,
       Map<MethodSignature, MemberNaming> methodMembers,
       Map<FieldSignature, MemberNaming> fieldMembers,
       Map<String, MappedRangesOfName> mappedRangesByRenamedName,
-      Map<String, List<MemberNaming>> mappedFieldNamingsByName) {
+      Map<String, List<MemberNaming>> mappedFieldNamingsByName,
+      List<MappingInformation> additionalMappingInfo) {
     this.renamedName = renamedName;
     this.originalName = originalName;
     this.methodMembers = ImmutableMap.copyOf(methodMembers);
     this.fieldMembers = ImmutableMap.copyOf(fieldMembers);
     this.mappedRangesByRenamedName = mappedRangesByRenamedName;
     this.mappedFieldNamingsByName = mappedFieldNamingsByName;
+    this.additionalMappingInfo = additionalMappingInfo;
+  }
+
+  public List<MappingInformation> getAdditionalMappingInfo() {
+    return Collections.unmodifiableList(additionalMappingInfo);
   }
 
   public MappedRangesOfName getMappedRangesForRenamedName(String renamedName) {
@@ -297,7 +326,7 @@
     return methodMembers.values();
   }
 
-  void write(ChainableStringConsumer consumer, List<MappingInformation> additionalMappingInfo) {
+  void write(ChainableStringConsumer consumer) {
     consumer.accept(originalName).accept(" -> ").accept(renamedName).accept(":\n");
 
     // Print all additional mapping information.
@@ -315,13 +344,16 @@
     mappedRangesSorted.sort(Comparator.comparingInt(range -> range.sequenceNumber));
     for (MappedRange range : mappedRangesSorted) {
       consumer.accept("    ").accept(range.toString()).accept("\n");
+      for (MappingInformation info : range.additionalMappingInfo) {
+        consumer.accept("      # ").accept(info.serialize()).accept("\n");
+      }
     }
   }
 
   @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
-    write(ChainableStringConsumer.wrap(builder::append), Collections.emptyList());
+    write(ChainableStringConsumer.wrap(builder::append));
     return builder.toString();
   }
 
@@ -390,6 +422,8 @@
      */
     private final int sequenceNumber = getNextSequenceNumber();
 
+    private List<MappingInformation> additionalMappingInfo = new ArrayList<>();
+
     private MappedRange(
         Range minifiedRange, MethodSignature signature, Object originalRange, String renamedName) {
 
@@ -403,6 +437,26 @@
       this.renamedName = renamedName;
     }
 
+    public void addMappingInformation(
+        MappingInformation info, Consumer<MappingInformation> onProhibitedAddition) {
+      for (MappingInformation existing : additionalMappingInfo) {
+        if (!existing.allowOther(info)) {
+          onProhibitedAddition.accept(existing);
+          return;
+        }
+      }
+      additionalMappingInfo.add(info);
+    }
+
+    public boolean isCompilerSynthesized() {
+      for (MappingInformation info : additionalMappingInfo) {
+        if (info.isCompilerSynthesizedMappingInformation()) {
+          return true;
+        }
+      }
+      return false;
+    }
+
     public int getOriginalLineNumber(int lineNumberAfterMinification) {
       if (minifiedRange == null) {
         // General mapping without concrete line numbers: "a() -> b"
@@ -479,6 +533,10 @@
       result = 31 * result + renamedName.hashCode();
       return result;
     }
+
+    public List<MappingInformation> getAdditionalMappingInfo() {
+      return additionalMappingInfo;
+    }
   }
 }
 
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 0f50ecb..6ca2736 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -9,16 +9,24 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodAccessInfoCollection;
+import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.function.Function;
 
 /**
@@ -87,8 +95,10 @@
   // from the method name minifier to the interface method name minifier.
   class State {
 
-    void putRenaming(DexEncodedMethod key, DexString value) {
-      renaming.put(key.getReference(), value);
+    void putRenaming(DexEncodedMethod key, DexString newName) {
+      if (newName != key.getName()) {
+        renaming.put(key.getReference(), newName);
+      }
     }
 
     MethodReservationState<?> getReservationState(DexType type) {
@@ -173,7 +183,9 @@
     }
   }
 
-  MethodRenaming computeRenaming(Iterable<DexClass> interfaces, Timing timing) {
+  MethodRenaming computeRenaming(
+      Iterable<DexClass> interfaces, ExecutorService executorService, Timing timing)
+      throws ExecutionException {
     // Phase 1: Reserve all the names that need to be kept and allocate linked state in the
     //          library part.
     timing.begin("Phase 1");
@@ -193,6 +205,9 @@
     timing.begin("Phase 4");
     assignNamesToClassesMethods(appView.dexItemFactory().objectType, rootNamingState);
     timing.end();
+    timing.begin("Phase 5: non-rebound references");
+    renameNonReboundReferences(executorService);
+    timing.end();
 
     return new MethodRenaming(renaming);
   }
@@ -322,6 +337,60 @@
     return reservationState;
   }
 
+  private void renameNonReboundReferences(ExecutorService executorService)
+      throws ExecutionException {
+    Map<DexMethod, DexString> nonReboundRenamings = new ConcurrentHashMap<>();
+    MethodAccessInfoCollection methodAccessInfoCollection =
+        appView.appInfo().getMethodAccessInfoCollection();
+    ThreadUtils.processItems(
+        methodAccessInfoCollection::forEachMethodReference,
+        method -> renameNonReboundMethodReference(method, nonReboundRenamings),
+        executorService);
+    renaming.putAll(nonReboundRenamings);
+  }
+
+  private void renameNonReboundMethodReference(
+      DexMethod method, Map<DexMethod, DexString> nonReboundRenamings) {
+    if (method.getHolderType().isArrayType()) {
+      return;
+    }
+
+    DexClass holder = appView.contextIndependentDefinitionFor(method.getHolderType());
+    if (holder == null) {
+      return;
+    }
+
+    ResolutionResult resolutionResult = appView.appInfo().resolveMethodOn(holder, method);
+    if (resolutionResult.isSingleResolution()) {
+      DexEncodedMethod resolvedMethod = resolutionResult.getSingleTarget();
+      if (resolvedMethod.getReference() == method) {
+        return;
+      }
+
+      DexString newName = renaming.get(resolvedMethod.getReference());
+      if (newName != null) {
+        assert newName != resolvedMethod.getName();
+        nonReboundRenamings.put(method, newName);
+      }
+      return;
+    }
+
+    // If resolution fails, the method must be renamed consistently with the targets that give rise
+    // to the failure.
+    assert resolutionResult.isFailedResolution();
+
+    List<DexEncodedMethod> targets = new ArrayList<>();
+    resolutionResult.asFailedResolution().forEachFailureDependency(targets::add);
+    if (!targets.isEmpty()) {
+      DexString newName = renaming.get(targets.get(0).getReference());
+      assert targets.stream().allMatch(target -> renaming.get(target.getReference()) == newName);
+      if (newName != null) {
+        assert newName != targets.get(0).getName();
+        nonReboundRenamings.put(method, newName);
+      }
+    }
+  }
+
   // Shuffles the given methods if assertions are enabled and deterministic debugging is disabled.
   // Used to ensure that the generated output is deterministic.
   private static Iterable<DexEncodedMethod> shuffleMethods(
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index a51edde..c44d311 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -20,9 +20,7 @@
 import com.android.tools.r8.naming.NamingLens.NonIdentityNamingLens;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import java.util.ArrayList;
 import java.util.IdentityHashMap;
-import java.util.List;
 import java.util.Map;
 
 class MinifiedRenaming extends NonIdentityNamingLens {
@@ -89,36 +87,12 @@
 
   @Override
   public DexString lookupName(DexMethod method) {
-    DexString renamed = renaming.get(method);
-    if (renamed != null) {
-      return renamed;
-    }
-    // If the method does not have a direct renaming, return the resolutions mapping.
-    ResolutionResult resolutionResult = appView.appInfo().unsafeResolveMethodDueToDexFormat(method);
-    if (resolutionResult.isSingleResolution()) {
-      return renaming.getOrDefault(resolutionResult.getSingleTarget().getReference(), method.name);
-    }
-    // If resolution fails, the method must be renamed consistently with the targets that give rise
-    // to the failure.
-    if (resolutionResult.isFailedResolution()) {
-      List<DexEncodedMethod> targets = new ArrayList<>();
-      resolutionResult.asFailedResolution().forEachFailureDependency(targets::add);
-      if (!targets.isEmpty()) {
-        DexString firstRename = renaming.get(targets.get(0).getReference());
-        assert targets.stream()
-            .allMatch(target -> renaming.get(target.getReference()) == firstRename);
-        if (firstRename != null) {
-          return firstRename;
-        }
-      }
-    }
-    // If no renaming can be found the default is the methods name.
-    return method.name;
+    return renaming.getOrDefault(method, method.getName());
   }
 
   @Override
   public DexString lookupName(DexField field) {
-    return renaming.getOrDefault(field, field.name);
+    return renaming.getOrDefault(field, field.getName());
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index c5b8ced..e7106a6 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -67,7 +67,7 @@
     timing.begin("MinifyMethods");
     MethodRenaming methodRenaming =
         new MethodNameMinifier(appView, subtypingInfo, minifyMembers)
-            .computeRenaming(interfaces, timing);
+            .computeRenaming(interfaces, executorService, timing);
     timing.end();
 
     assert new MinifiedRenaming(appView, classRenaming, methodRenaming, FieldRenaming.empty())
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMap.java b/src/main/java/com/android/tools/r8/naming/ProguardMap.java
index 94615a0..221f064 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMap.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMap.java
@@ -4,10 +4,7 @@
 package com.android.tools.r8.naming;
 
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.naming.mappinginformation.MappingInformation;
-import com.android.tools.r8.naming.mappinginformation.ScopeReference;
 import com.android.tools.r8.position.Position;
-import java.util.function.Consumer;
 
 public interface ProguardMap {
 
@@ -16,11 +13,6 @@
         String renamedName, String originalName, Position position);
 
     abstract ProguardMap build();
-
-    abstract void addMappingInformation(
-        ScopeReference scope,
-        MappingInformation MappingInformation,
-        Consumer<MappingInformation> onProhibitedAddition);
   }
 
   boolean hasMapping(DexType type);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 92a507b..74737eb 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -145,7 +145,7 @@
     timing.begin("MinifyMethods");
     MethodRenaming methodRenaming =
         new MethodNameMinifier(appView, subtypingInfo, nameStrategy)
-            .computeRenaming(interfaces, timing);
+            .computeRenaming(interfaces, executorService, timing);
     // Amend the method renamings with the default interface methods.
     methodRenaming.renaming.putAll(defaultInterfaceMethodImplementationNames);
     methodRenaming.renaming.putAll(additionalMethodNamings);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 0efcb53..ae65b10 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.naming;
 
 import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
@@ -11,10 +12,7 @@
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics;
 import com.android.tools.r8.naming.mappinginformation.MetaInfMappingInformation;
-import com.android.tools.r8.naming.mappinginformation.ScopeReference;
-import com.android.tools.r8.naming.mappinginformation.ScopeReference.ClassScopeReference;
 import com.android.tools.r8.position.TextPosition;
-import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.IdentifierUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.gson.JsonObject;
@@ -25,7 +23,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
-import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
 /**
  * Parses a Proguard mapping file and produces mappings from obfuscated class names to the original
@@ -90,7 +88,6 @@
   private int lineOffset = 0;
   private String line;
   private MapVersion version = MapVersion.MapVersionNone;
-  private ScopeReference implicitSingletonScope = ScopeReference.globalScope();
 
   private int peekCodePoint() {
     return lineOffset < line.length() ? line.codePointAt(lineOffset) : '\n';
@@ -226,16 +223,12 @@
   // Parsing of entries
 
   private void parseClassMappings(ProguardMap.Builder mapBuilder) throws IOException {
-    assert implicitSingletonScope == ScopeReference.globalScope();
     while (hasLine()) {
       skipWhitespace();
       if (isCommentLineWithJsonBrace()) {
         parseMappingInformation(
-            (reference, info) -> {
-              if (!reference.isGlobalScope()) {
-                diagnosticsHandler.error(
-                    MappingInformationDiagnostics.invalidScopeFor(lineNo, reference, info));
-              }
+            info -> {
+              assert info.isMetaInfMappingInformation();
             });
         // Skip reading the rest of the line.
         lineOffset = line.length();
@@ -259,7 +252,6 @@
       expect(':');
       ClassNaming.Builder currentClassBuilder =
           mapBuilder.classNamingBuilder(after, before, getPosition());
-      implicitSingletonScope = new ClassScopeReference(Reference.classFromTypeName(after));
       skipWhitespace();
       if (nextLine()) {
         parseMemberMappings(mapBuilder, currentClassBuilder);
@@ -267,20 +259,18 @@
     }
   }
 
-  private void parseMappingInformation(
-      BiConsumer<ScopeReference, MappingInformation> onMappingInfo) {
+  private void parseMappingInformation(Consumer<MappingInformation> onMappingInfo) {
     MappingInformation.fromJsonObject(
         version,
         parseJsonInComment(),
         diagnosticsHandler,
         lineNo,
-        implicitSingletonScope,
-        (reference, info) -> {
+        info -> {
           MetaInfMappingInformation generatorInfo = info.asMetaInfMappingInformation();
           if (generatorInfo != null) {
             version = generatorInfo.getMapVersion();
           }
-          onMappingInfo.accept(reference, info);
+          onMappingInfo.accept(info);
         });
   }
 
@@ -288,21 +278,33 @@
       throws IOException {
     MemberNaming lastAddedNaming = null;
     MemberNaming activeMemberNaming = null;
+    MappedRange activeMappedRange = null;
     Range previousMappedRange = null;
     do {
       Object originalRange = null;
       Range mappedRange = null;
       // Try to parse any information added in comments above member namings
       if (isCommentLineWithJsonBrace()) {
+        final MemberNaming currentMember = activeMemberNaming;
+        final MappedRange currentRange = activeMappedRange;
         parseMappingInformation(
-            (reference, mappingInfo) ->
-                mapBuilder.addMappingInformation(
-                    reference,
-                    mappingInfo,
+            info -> {
+              if (currentMember == null) {
+                classNamingBuilder.addMappingInformation(
+                    info,
                     conflictingInfo ->
                         diagnosticsHandler.warning(
                             MappingInformationDiagnostics.notAllowedCombination(
-                                reference, mappingInfo, conflictingInfo, lineNo))));
+                                info, conflictingInfo, lineNo)));
+              } else if (currentRange != null) {
+                currentRange.addMappingInformation(
+                    info,
+                    conflictingInfo ->
+                        diagnosticsHandler.warning(
+                            MappingInformationDiagnostics.notAllowedCombination(
+                                info, conflictingInfo, lineNo)));
+              }
+            });
         // Skip reading the rest of the line.
         lineOffset = line.length();
         continue;
@@ -344,8 +346,9 @@
       String renamedName = parseMethodName();
 
       if (signature instanceof MethodSignature) {
-        classNamingBuilder.addMappedRange(
-            mappedRange, (MethodSignature) signature, originalRange, renamedName);
+        activeMappedRange =
+            classNamingBuilder.addMappedRange(
+                mappedRange, (MethodSignature) signature, originalRange, renamedName);
       }
 
       assert mappedRange == null || signature instanceof MethodSignature;
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
index 6516e22..73ae362 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
@@ -103,10 +103,7 @@
     // Turn off linting of the mapping file in some build systems.
     builder.append("# common_typos_disable" + "\n");
     // Emit the R8 specific map-file version.
-    MapVersion mapVersion =
-        options.testing.enableExperimentalMapFileVersion
-            ? MapVersion.MapVersionExperimental
-            : MapVersion.STABLE;
+    MapVersion mapVersion = options.getMapFileVersion();
     if (mapVersion.isGreaterThan(MapVersion.MapVersionNone)) {
       builder
           .append("# ")
diff --git a/src/main/java/com/android/tools/r8/naming/SeedMapper.java b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
index e595d42..927cf6e 100644
--- a/src/main/java/com/android/tools/r8/naming/SeedMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
@@ -9,8 +9,6 @@
 
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.MemberNaming.Signature;
-import com.android.tools.r8.naming.mappinginformation.MappingInformation;
-import com.android.tools.r8.naming.mappinginformation.ScopeReference;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.ImmutableMap;
@@ -25,7 +23,6 @@
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Consumer;
 
 /**
  * Mappings read from the given ProGuard map.
@@ -64,14 +61,6 @@
     }
 
     @Override
-    void addMappingInformation(
-        ScopeReference scope,
-        MappingInformation MappingInformation,
-        Consumer<MappingInformation> onProhibitedAddition) {
-      // Not needed.
-    }
-
-    @Override
     SeedMapper build() {
       reporter.failIfPendingErrors();
       return new SeedMapper(ImmutableMap.copyOf(map), mappedToDescriptorNames, reporter);
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/CompilerSynthesizedMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/CompilerSynthesizedMappingInformation.java
index eb675c3..0c49e28 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/CompilerSynthesizedMappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/CompilerSynthesizedMappingInformation.java
@@ -8,10 +8,11 @@
 import com.android.tools.r8.naming.MapVersion;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonPrimitive;
-import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
 public class CompilerSynthesizedMappingInformation extends MappingInformation {
 
+  public static final MapVersion SUPPORTED_VERSION = MapVersion.MapVersionExperimental;
   public static final String ID = "com.android.tools.r8.synthesized";
 
   public static class Builder {
@@ -21,6 +22,10 @@
     }
   }
 
+  public static boolean isSupported(MapVersion version) {
+    return version.isGreaterThanOrEqualTo(SUPPORTED_VERSION);
+  }
+
   private CompilerSynthesizedMappingInformation() {}
 
   public static Builder builder() {
@@ -59,16 +64,9 @@
       JsonObject object,
       DiagnosticsHandler diagnosticsHandler,
       int lineNumber,
-      ScopeReference implicitSingletonScope,
-      BiConsumer<ScopeReference, MappingInformation> onMappingInfo) {
-    if (version.isLessThan(MapVersion.MapVersionExperimental)) {
-      return;
-    }
-    CompilerSynthesizedMappingInformation info = builder().build();
-    for (ScopeReference reference :
-        ScopedMappingInformation.deserializeScope(
-            object, implicitSingletonScope, diagnosticsHandler, lineNumber, version)) {
-      onMappingInfo.accept(reference, info);
+      Consumer<MappingInformation> onMappingInfo) {
+    if (isSupported(version)) {
+      onMappingInfo.accept(builder().build());
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/FileNameInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/FileNameInformation.java
index a9c7811..e4f3490 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/FileNameInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/FileNameInformation.java
@@ -6,11 +6,10 @@
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.MapVersion;
-import com.android.tools.r8.naming.mappinginformation.ScopeReference.ClassScopeReference;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonPrimitive;
-import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
 public class FileNameInformation extends MappingInformation {
 
@@ -64,15 +63,12 @@
       JsonObject object,
       DiagnosticsHandler diagnosticsHandler,
       int lineNumber,
-      ScopeReference implicitSingletonScope,
-      BiConsumer<ScopeReference, MappingInformation> onMappingInfo) {
-    assert implicitSingletonScope instanceof ClassScopeReference;
+      Consumer<MappingInformation> onMappingInfo) {
     try {
       JsonElement fileName =
           getJsonElementFromObject(object, diagnosticsHandler, lineNumber, FILE_NAME_KEY, ID);
       if (fileName != null) {
-        onMappingInfo.accept(
-            implicitSingletonScope, new FileNameInformation(fileName.getAsString()));
+        onMappingInfo.accept(new FileNameInformation(fileName.getAsString()));
       }
     } catch (UnsupportedOperationException | IllegalStateException ignored) {
       diagnosticsHandler.info(
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
index 034b537..145080f 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.naming.MapVersion;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
-import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
 public abstract class MappingInformation {
 
@@ -49,8 +49,7 @@
       JsonObject object,
       DiagnosticsHandler diagnosticsHandler,
       int lineNumber,
-      ScopeReference implicitSingletonScope,
-      BiConsumer<ScopeReference, MappingInformation> onMappingInfo) {
+      Consumer<MappingInformation> onMappingInfo) {
     if (object == null) {
       diagnosticsHandler.info(MappingInformationDiagnostics.notValidJson(lineNumber));
       return;
@@ -73,7 +72,6 @@
         object,
         diagnosticsHandler,
         lineNumber,
-        implicitSingletonScope,
         onMappingInfo);
   }
 
@@ -83,8 +81,7 @@
       JsonObject object,
       DiagnosticsHandler diagnosticsHandler,
       int lineNumber,
-      ScopeReference implicitSingletonScope,
-      BiConsumer<ScopeReference, MappingInformation> onMappingInfo) {
+      Consumer<MappingInformation> onMappingInfo) {
     switch (id) {
       case MetaInfMappingInformation.ID:
         MetaInfMappingInformation.deserialize(
@@ -92,11 +89,11 @@
         return;
       case FileNameInformation.ID:
         FileNameInformation.deserialize(
-            version, object, diagnosticsHandler, lineNumber, implicitSingletonScope, onMappingInfo);
+            version, object, diagnosticsHandler, lineNumber, onMappingInfo);
         return;
       case CompilerSynthesizedMappingInformation.ID:
         CompilerSynthesizedMappingInformation.deserialize(
-            version, object, diagnosticsHandler, lineNumber, implicitSingletonScope, onMappingInfo);
+            version, object, diagnosticsHandler, lineNumber, onMappingInfo);
         return;
       default:
         diagnosticsHandler.info(MappingInformationDiagnostics.noHandlerFor(lineNumber, id));
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformationDiagnostics.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformationDiagnostics.java
index 261a544..1c4242a 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformationDiagnostics.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformationDiagnostics.java
@@ -36,14 +36,6 @@
     this.position = position;
   }
 
-  public static MappingInformationDiagnostics invalidScopeFor(
-      int lineNumber, ScopeReference reference, MappingInformation info) {
-    return new MappingInformationDiagnostics(
-        String.format(
-            "Cannot use scope %s for mapping information %s", reference.toString(), info.getId()),
-        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
-  }
-
   static MappingInformationDiagnostics noHandlerFor(int lineNumber, String value) {
     return new MappingInformationDiagnostics(
         String.format("Could not find a handler for %s", value),
@@ -102,14 +94,9 @@
   }
 
   public static MappingInformationDiagnostics notAllowedCombination(
-      ScopeReference reference, MappingInformation one, MappingInformation other, int lineNumber) {
+      MappingInformation one, MappingInformation other, int lineNumber) {
     return new MappingInformationDiagnostics(
-        "The mapping '"
-            + one.serialize()
-            + "' is not allowed in combination with '"
-            + other.serialize()
-            + "' in the mapping for "
-            + reference.toString(),
+        "The mapping '" + one + "' is not allowed in combination with '" + other + "'",
         new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MetaInfMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MetaInfMappingInformation.java
index f3becb8..ea3824a 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/MetaInfMappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MetaInfMappingInformation.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.naming.MapVersion;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonPrimitive;
-import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
 public class MetaInfMappingInformation extends MappingInformation {
 
@@ -57,22 +57,22 @@
   }
 
   public static void deserialize(
-      MapVersion version,
+      MapVersion ignoredCurrentMapVersion,
       JsonObject object,
       DiagnosticsHandler diagnosticsHandler,
       int lineNumber,
-      BiConsumer<ScopeReference, MappingInformation> onMappingInfo) {
+      Consumer<MappingInformation> onMappingInfo) {
     // Parsing the generator information must support parsing at all map versions as it itself is
     // what establishes the version.
-    String mapVersion = object.get(MAP_VERSION_KEY).getAsString();
-    if (mapVersion == null) {
+    String mapVersionString = object.get(MAP_VERSION_KEY).getAsString();
+    if (mapVersionString == null) {
       noKeyForObjectWithId(lineNumber, MAP_VERSION_KEY, MAPPING_ID_KEY, ID);
       return;
     }
-    MapVersion mapVersion1 = MapVersion.fromName(mapVersion);
-    if (mapVersion1 == null) {
+    MapVersion mapVersion = MapVersion.fromName(mapVersionString);
+    if (mapVersion == null) {
       return;
     }
-    onMappingInfo.accept(ScopeReference.globalScope(), new MetaInfMappingInformation(mapVersion1));
+    onMappingInfo.accept(new MetaInfMappingInformation(mapVersion));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/ScopeReference.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/ScopeReference.java
deleted file mode 100644
index 1ce5efa..0000000
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/ScopeReference.java
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.naming.mappinginformation;
-
-import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.utils.DescriptorUtils;
-
-/** Abstraction for the items referenced in a scope. */
-public abstract class ScopeReference {
-
-  public static ScopeReference globalScope() {
-    return GlobalScopeReference.INSTANCE;
-  }
-
-  public static ScopeReference fromClassReference(ClassReference reference) {
-    return new ClassScopeReference(reference);
-  }
-
-  // Method for reading in the serialized reference format.
-  public static ScopeReference fromReferenceString(String referenceString) {
-    if (DescriptorUtils.isClassDescriptor(referenceString)) {
-      return fromClassReference(Reference.classFromDescriptor(referenceString));
-    }
-    throw new Unimplemented("No support for reference: " + referenceString);
-  }
-
-  public boolean isGlobalScope() {
-    return equals(ScopeReference.globalScope());
-  }
-
-  public abstract String toReferenceString();
-
-  public abstract ClassReference getHolderReference();
-
-  @Override
-  public abstract boolean equals(Object other);
-
-  @Override
-  public abstract int hashCode();
-
-  @Override
-  public String toString() {
-    return toReferenceString();
-  }
-
-  public static class GlobalScopeReference extends ScopeReference {
-    private static final GlobalScopeReference INSTANCE = new GlobalScopeReference();
-
-    @Override
-    public String toReferenceString() {
-      throw new Unreachable();
-    }
-
-    @Override
-    public String toString() {
-      return "<global-scope>";
-    }
-
-    @Override
-    public ClassReference getHolderReference() {
-      throw new Unreachable();
-    }
-
-    @Override
-    public boolean equals(Object other) {
-      return this == other;
-    }
-
-    @Override
-    public int hashCode() {
-      return System.identityHashCode(this);
-    }
-  }
-
-  public static class ClassScopeReference extends ScopeReference {
-    private final ClassReference reference;
-
-    public ClassScopeReference(ClassReference reference) {
-      assert reference != null;
-      this.reference = reference;
-    }
-
-    @Override
-    public String toReferenceString() {
-      return reference.getDescriptor();
-    }
-
-    @Override
-    public ClassReference getHolderReference() {
-      return reference;
-    }
-
-    @Override
-    public boolean equals(Object other) {
-      return other instanceof ClassScopeReference
-          && reference.equals(((ClassScopeReference) other).reference);
-    }
-
-    @Override
-    public int hashCode() {
-      return reference.hashCode();
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/ScopedMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/ScopedMappingInformation.java
deleted file mode 100644
index 4a5b3d3..0000000
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/ScopedMappingInformation.java
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.naming.mappinginformation;
-
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.naming.MapVersion;
-import com.google.common.collect.ImmutableList;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import java.util.Collections;
-import java.util.List;
-
-public abstract class ScopedMappingInformation {
-
-  private static final MapVersion SCOPE_SUPPORTED = MapVersion.MapVersionExperimental;
-  public static final String SCOPE_KEY = "scope";
-
-  public static List<ScopeReference> deserializeScope(
-      JsonObject object,
-      ScopeReference implicitSingletonScope,
-      DiagnosticsHandler diagnosticsHandler,
-      int lineNumber,
-      MapVersion version) {
-    // Prior to support, the scope is always the implicit scope.
-    if (version.isLessThan(SCOPE_SUPPORTED)) {
-      return Collections.singletonList(implicitSingletonScope);
-    }
-    // If the scope key is absent, the implicit scope is assumed.
-    JsonArray scopeArray = object.getAsJsonArray(SCOPE_KEY);
-    if (scopeArray == null) {
-      return Collections.singletonList(implicitSingletonScope);
-    }
-    ImmutableList.Builder<ScopeReference> builder = ImmutableList.builder();
-    for (JsonElement element : scopeArray) {
-      builder.add(ScopeReference.fromReferenceString(element.getAsString()));
-    }
-    return builder.build();
-  }
-
-  public static void serializeScope(
-      JsonObject object,
-      ScopeReference currentImplicitScope,
-      List<ScopeReference> scopeReferences,
-      MapVersion version) {
-    assert !scopeReferences.isEmpty();
-    // If emitting a non-experimental version the scope is always implicit.
-    if (version.isLessThan(SCOPE_SUPPORTED)) {
-      assert currentImplicitScope.equals(scopeReferences.get(0));
-      return;
-    }
-    // If the scope matches the implicit scope don't add it explicitly.
-    if (scopeReferences.size() == 1 && scopeReferences.get(0).equals(currentImplicitScope)) {
-      return;
-    }
-    JsonArray scopeArray = new JsonArray();
-    scopeReferences.forEach(ref -> scopeArray.add(ref.toReferenceString()));
-    object.add(SCOPE_KEY, scopeArray);
-  }
-}
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 3a30ec4..795c3bb 100644
--- a/src/main/java/com/android/tools/r8/references/Reference.java
+++ b/src/main/java/com/android/tools/r8/references/Reference.java
@@ -41,6 +41,10 @@
     return descriptor.equals("V") ? null : typeFromDescriptor(descriptor);
   }
 
+  public static TypeReference returnTypeFromTypeName(String typename) {
+    return typename.equals("void") ? null : typeFromTypeName(typename);
+  }
+
   public static TypeReference typeFromDescriptor(String descriptor) {
     switch (descriptor.charAt(0)) {
       case 'L':
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceElement.java b/src/main/java/com/android/tools/r8/retrace/RetraceElement.java
index 6070723..cb2ac8c 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceElement.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceElement.java
@@ -11,4 +11,6 @@
 public interface RetraceElement<R extends RetraceResult<?>> {
 
   R getRetraceResultContext();
+
+  boolean isCompilerSynthesized();
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceFrameElement.java b/src/main/java/com/android/tools/r8/retrace/RetraceFrameElement.java
index de298f0..5d8332e 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceFrameElement.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceFrameElement.java
@@ -16,7 +16,9 @@
 
   RetraceClassElement getClassElement();
 
-  void visitFrames(BiConsumer<RetracedMethodReference, Integer> consumer);
+  void visitAllFrames(BiConsumer<RetracedMethodReference, Integer> consumer);
+
+  void visitNonCompilerSynthesizedFrames(BiConsumer<RetracedMethodReference, Integer> consumer);
 
   RetraceSourceFileResult retraceSourceFile(RetracedClassMemberReference frame, String sourceFile);
 
diff --git a/src/main/java/com/android/tools/r8/retrace/StringRetrace.java b/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
index 825ac52..4502782 100644
--- a/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
@@ -73,7 +73,10 @@
 
   private void joinAmbiguousLines(
       List<List<String>> retracedResult, Consumer<String> joinedConsumer) {
-    assert !retracedResult.isEmpty();
+    if (retracedResult.isEmpty()) {
+      // The result is empty, likely it maps to compiler synthesized items.
+      return;
+    }
     List<String> initialResult = retracedResult.get(0);
     initialResult.forEach(joinedConsumer);
     if (retracedResult.size() <= 1) {
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
index 613e869..fd8ebb6 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
-import com.android.tools.r8.naming.mappinginformation.ScopeReference;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
@@ -225,14 +224,25 @@
     }
 
     @Override
+    public boolean isCompilerSynthesized() {
+      if (classResult.mapper != null) {
+        for (MappingInformation info : classResult.mapper.getAdditionalMappingInfo()) {
+          if (info.isCompilerSynthesizedMappingInformation()) {
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+
+    @Override
     public RetraceSourceFileResultImpl retraceSourceFile(String sourceFile) {
-      for (MappingInformation info :
-          classResult
-              .getRetracerImpl()
-              .getAdditionalMappingInfo(
-                  ScopeReference.fromClassReference(classResult.obfuscatedReference))) {
-        if (info.isFileNameInformation()) {
-          return new RetraceSourceFileResultImpl(info.asFileNameInformation().getFileName(), false);
+      if (classResult.mapper != null) {
+        for (MappingInformation info : classResult.mapper.getAdditionalMappingInfo()) {
+          if (info.isFileNameInformation()) {
+            return new RetraceSourceFileResultImpl(
+                info.asFileNameInformation().getFileName(), false);
+          }
         }
       }
       return new RetraceSourceFileResultImpl(
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java
index 34c5206..331298e 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.retrace.internal;
 
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.references.Reference;
@@ -107,6 +108,11 @@
     }
 
     @Override
+    public boolean isCompilerSynthesized() {
+      throw new Unimplemented("b/172014416");
+    }
+
+    @Override
     public boolean isUnknown() {
       return fieldReference.isUnknown();
     }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
index 178bf9e..b5dae19 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.retrace.RetracedMethodReference;
 import com.android.tools.r8.retrace.Retracer;
 import com.android.tools.r8.retrace.internal.RetraceClassResultImpl.RetraceClassElementImpl;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Pair;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
@@ -152,6 +153,23 @@
       this.obfuscatedPosition = obfuscatedPosition;
     }
 
+    private boolean isOuterMostFrameCompilerSynthesized() {
+      if (mappedRanges == null || mappedRanges.isEmpty()) {
+        return false;
+      }
+      return ListUtils.last(mappedRanges).isCompilerSynthesized();
+    }
+
+    /**
+     * Predicate determines if the *entire* frame is to be considered synthetic.
+     *
+     * <p>That is only true for a frame that has just one entry and that entry is synthetic.
+     */
+    @Override
+    public boolean isCompilerSynthesized() {
+      return getOuterFrames().isEmpty() && isOuterMostFrameCompilerSynthesized();
+    }
+
     @Override
     public RetraceFrameResult getRetraceResultContext() {
       return retraceFrameResult;
@@ -173,7 +191,7 @@
     }
 
     @Override
-    public void visitFrames(BiConsumer<RetracedMethodReference, Integer> consumer) {
+    public void visitAllFrames(BiConsumer<RetracedMethodReference, Integer> consumer) {
       int counter = 0;
       consumer.accept(getTopFrame(), counter++);
       for (RetracedMethodReferenceImpl outerFrame : getOuterFrames()) {
@@ -182,6 +200,22 @@
     }
 
     @Override
+    public void visitNonCompilerSynthesizedFrames(
+        BiConsumer<RetracedMethodReference, Integer> consumer) {
+      int index = 0;
+      RetracedMethodReferenceImpl prev = getTopFrame();
+      for (RetracedMethodReferenceImpl next : getOuterFrames()) {
+        consumer.accept(prev, index++);
+        prev = next;
+      }
+      // We expect only the last frame, i.e., the outer-most caller to potentially be synthesized.
+      // If not include it too.
+      if (!isOuterMostFrameCompilerSynthesized()) {
+        consumer.accept(prev, index);
+      }
+    }
+
+    @Override
     public RetraceSourceFileResult retraceSourceFile(
         RetracedClassMemberReference frame, String sourceFile) {
       return RetraceUtils.getSourceFile(
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
index 167390d..7e66ec1 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.retrace.internal;
 
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
 import com.android.tools.r8.references.MethodReference;
@@ -140,6 +141,11 @@
     }
 
     @Override
+    public boolean isCompilerSynthesized() {
+      throw new Unimplemented("b/172014416");
+    }
+
+    @Override
     public boolean isUnknown() {
       return methodReference.isUnknown();
     }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
index d14df13..33a7d80 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
@@ -6,8 +6,6 @@
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.naming.mappinginformation.MappingInformation;
-import com.android.tools.r8.naming.mappinginformation.ScopeReference;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
@@ -16,7 +14,6 @@
 import com.android.tools.r8.retrace.ProguardMapProducer;
 import com.android.tools.r8.retrace.Retracer;
 import java.io.BufferedReader;
-import java.util.Collection;
 
 /** A default implementation for the retrace api using the ClassNameMapper defined in R8. */
 public class RetracerImpl implements Retracer {
@@ -72,8 +69,4 @@
   public RetraceTypeResultImpl retraceType(TypeReference typeReference) {
     return RetraceTypeResultImpl.create(typeReference, this);
   }
-
-  public Collection<MappingInformation> getAdditionalMappingInfo(ScopeReference reference) {
-    return classNameMapper.getAdditionalMappingInfo(reference);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
index 937bf8e..793282a 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
@@ -99,15 +99,16 @@
                                   frameElement -> {
                                     List<RetraceStackTraceProxy<T, ST>> retracedProxies =
                                         new ArrayList<>();
-                                    frameElement.visitFrames(
+                                    frameElement.visitNonCompilerSynthesizedFrames(
                                         (frame, index) -> {
+                                          boolean isTopFrame = retracedProxies.isEmpty();
                                           RetraceStackTraceProxyImpl.Builder<T, ST> proxy =
                                               RetraceStackTraceProxyImpl.builder(element)
                                                   .setRetracedClass(frame.getHolderClass())
                                                   .setRetracedMethod(frame)
                                                   .setAmbiguous(
-                                                      frameResult.isAmbiguous() && index == 0)
-                                                  .setTopFrame(index == 0);
+                                                      frameResult.isAmbiguous() && isTopFrame)
+                                                  .setTopFrame(isTopFrame);
                                           if (element.hasLineNumber()) {
                                             proxy.setLineNumber(
                                                 frame.getOriginalPositionOrDefault(
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 6974a61..ef2f149 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -17,6 +17,11 @@
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.ir.optimize.info.UpdatableMethodOptimizationInfo;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -29,6 +34,8 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.function.Predicate;
 
 public class TreePruner {
@@ -56,15 +63,18 @@
             : UnusedItemsPrinter.DONT_PRINT;
   }
 
-  public DirectMappedDexApplication run() {
+  public DirectMappedDexApplication run(ExecutorService executorService) throws ExecutionException {
     DirectMappedDexApplication application = appView.appInfo().app().asDirect();
     Timing timing = application.timing;
     timing.begin("Pruning application...");
     try {
       DirectMappedDexApplication.Builder builder = removeUnused(application);
-      return prunedTypes.isEmpty() && !appView.options().configurationDebugging
-          ? application
-          : builder.build();
+      DirectMappedDexApplication newApplication =
+          prunedTypes.isEmpty() && !appView.options().configurationDebugging
+              ? application
+              : builder.build();
+      fixupOptimizationInfo(newApplication, executorService);
+      return newApplication;
     } finally {
       timing.end();
     }
@@ -92,7 +102,7 @@
             && !options.forceProguardCompatibility) {
           // The class is only needed as a type but never instantiated. Make it abstract to reflect
           // this.
-          if (clazz.accessFlags.isFinal()) {
+          if (clazz.isFinal()) {
             // We cannot mark this class abstract, as it is final (not supported on Android).
             // However, this might extend an abstract class and we might have removed the
             // corresponding methods in this class. This might happen if we only keep this
@@ -120,7 +130,7 @@
 
   private void pruneUnusedInterfaces(DexProgramClass clazz) {
     Set<DexType> reachableInterfaces = new LinkedHashSet<>();
-    for (DexType type : clazz.interfaces.values) {
+    for (DexType type : clazz.getInterfaces()) {
       retainReachableInterfacesFrom(type, reachableInterfaces);
     }
     if (!reachableInterfaces.isEmpty()) {
@@ -374,6 +384,33 @@
     return Collections.unmodifiableCollection(methodsToKeepForConfigurationDebugging);
   }
 
+  private void fixupOptimizationInfo(
+      DirectMappedDexApplication application, ExecutorService executorService)
+      throws ExecutionException {
+    // Clear the type elements cache due to redundant interface removal.
+    appView.dexItemFactory().clearTypeElementsCache();
+
+    OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
+    feedback.fixupOptimizationInfos(
+        application.classes(),
+        executorService,
+        new OptimizationInfoFixer() {
+          @Override
+          public void fixup(DexEncodedField field, MutableFieldOptimizationInfo optimizationInfo) {
+            optimizationInfo.fixupClassTypeReferences(appView, appView.graphLens(), prunedTypes);
+          }
+
+          @Override
+          public void fixup(
+              DexEncodedMethod method, UpdatableMethodOptimizationInfo optimizationInfo) {
+            optimizationInfo.fixupClassTypeReferences(appView, appView.graphLens(), prunedTypes);
+          }
+        });
+
+    // Verify that the fixup did not lead to the caching of any elements.
+    assert appView.dexItemFactory().verifyNoCachedTypeElements();
+  }
+
   private boolean verifyNoDeadFields(DexProgramClass clazz) {
     for (DexEncodedField field : clazz.fields()) {
       // Pinned field which type is never instantiated are always null, they are marked as dead
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index ed9d843..8f883ea 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -453,10 +453,10 @@
 
   private static boolean shouldAnnotateSynthetics(InternalOptions options) {
     // Only intermediate builds have annotated synthetics to allow later sharing.
-    // This is currently also disabled on CF to CF desugaring to avoid missing class references to
-    // the annotated classes.
+    // This is currently also disabled on non-L8 CF to CF desugaring to avoid missing class
+    // references to the annotated classes.
     // TODO(b/147485959): Find an alternative encoding for synthetics to avoid missing-class refs.
-    return options.intermediate && !options.cfToCfDesugar;
+    return options.intermediate && (!options.cfToCfDesugar || options.forceAnnotateSynthetics);
   }
 
   private <T extends SyntheticDefinition<?, T, ?>>
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 65c53d5..d6c93aa 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
@@ -360,18 +361,6 @@
     return clazz;
   }
 
-  public DexClasspathClass createClasspathClass(
-      SyntheticKind kind, DexType type, DexClass context, DexItemFactory factory) {
-    // Obtain the outer synthesizing context in the case the context itself is synthetic.
-    // This is to ensure a flat input-type -> synthetic-item mapping.
-    SynthesizingContext outerContext = SynthesizingContext.fromNonSyntheticInputContext(context);
-    SyntheticClasspathClassBuilder classBuilder =
-        new SyntheticClasspathClassBuilder(type, outerContext, factory);
-    DexClasspathClass clazz = classBuilder.build();
-    addPendingDefinition(new SyntheticClasspathClassDefinition(kind, outerContext, clazz));
-    return clazz;
-  }
-
   // TODO(b/172194101): Make this take a unique context.
   public DexProgramClass createFixedClass(
       SyntheticKind kind,
@@ -390,6 +379,91 @@
     return clazz;
   }
 
+  // This is a temporary API for migration to the hygienic synthetic, the classes created behave
+  // like a hygienic synthetic, but use the legacyType passed as parameter instead of the
+  // hygienic type.
+  public DexProgramClass ensureFixedClassWhileMigrating(
+      SyntheticKind kind,
+      DexType legacyType,
+      DexProgramClass context,
+      AppView<?> appView,
+      Consumer<SyntheticProgramClassBuilder> fn) {
+    synchronized (context) {
+      DexClass dexClass = appView.definitionFor(legacyType);
+      if (dexClass != null) {
+        assert dexClass.isProgramClass();
+        return dexClass.asProgramClass();
+      }
+      // Obtain the outer synthesizing context in the case the context itself is synthetic.
+      // This is to ensure a flat input-type -> synthetic-item mapping.
+      SynthesizingContext outerContext = getSynthesizingContext(context);
+      SyntheticProgramClassBuilder classBuilder =
+          new SyntheticProgramClassBuilder(legacyType, outerContext, appView.dexItemFactory());
+      fn.accept(classBuilder);
+      DexProgramClass clazz = classBuilder.build();
+      addPendingDefinition(new SyntheticProgramClassDefinition(kind, outerContext, clazz));
+      return clazz;
+    }
+  }
+
+  public DexClasspathClass createFixedClasspathClass(
+      SyntheticKind kind, DexClasspathClass context, DexItemFactory factory) {
+    // Obtain the outer synthesizing context in the case the context itself is synthetic.
+    // This is to ensure a flat input-type -> synthetic-item mapping.
+    SynthesizingContext outerContext = SynthesizingContext.fromNonSyntheticInputContext(context);
+    DexType type = SyntheticNaming.createFixedType(kind, outerContext, factory);
+    SyntheticClasspathClassBuilder classBuilder =
+        new SyntheticClasspathClassBuilder(type, outerContext, factory);
+    DexClasspathClass clazz = classBuilder.build();
+    addPendingDefinition(new SyntheticClasspathClassDefinition(kind, outerContext, clazz));
+    return clazz;
+  }
+
+  // This is a temporary API for migration to the hygienic synthetic, the classes created behave
+  // like a hygienic synthetic, but use the legacyType passed as parameter instead of the
+  // hygienic type.
+  private DexClasspathClass ensureFixedClasspathClassWhileMigrating(
+      SyntheticKind kind, DexType legacyType, DexClass context, AppView<?> appView) {
+    synchronized (context) {
+      DexClass dexClass = appView.definitionFor(legacyType);
+      if (dexClass != null) {
+        assert dexClass.isClasspathClass();
+        return dexClass.asClasspathClass();
+      }
+      // Obtain the outer synthesizing context in the case the context itself is synthetic.
+      // This is to ensure a flat input-type -> synthetic-item mapping.
+      SynthesizingContext outerContext = SynthesizingContext.fromNonSyntheticInputContext(context);
+      SyntheticClasspathClassBuilder classBuilder =
+          new SyntheticClasspathClassBuilder(legacyType, outerContext, appView.dexItemFactory());
+      DexClasspathClass clazz = classBuilder.build();
+      addPendingDefinition(new SyntheticClasspathClassDefinition(kind, outerContext, clazz));
+      return clazz;
+    }
+  }
+
+  // This is a temporary API for migration to the hygienic synthetic, the classes created behave
+  // like a hygienic synthetic, but use the legacyType passed as parameter instead of the
+  // hygienic type.
+  public void ensureDirectMethodOnSyntheticClasspathClassWhileMigrating(
+      SyntheticKind kind,
+      DexType legacyType,
+      DexClass context,
+      AppView<?> appView,
+      DexMethod method,
+      Consumer<SyntheticMethodBuilder> builderConsumer) {
+    DexClasspathClass syntheticClass =
+        ensureFixedClasspathClassWhileMigrating(kind, legacyType, context, appView);
+    synchronized (syntheticClass) {
+      if (syntheticClass.lookupMethod(method) != null) {
+        return;
+      }
+      SyntheticMethodBuilder syntheticMethodBuilder =
+          new SyntheticMethodBuilder(appView.dexItemFactory(), syntheticClass.type);
+      builderConsumer.accept(syntheticMethodBuilder);
+      syntheticClass.addDirectMethod(syntheticMethodBuilder.build());
+    }
+  }
+
   public DexProgramClass createFixedClassFromType(
       SyntheticKind kind,
       DexType contextType,
@@ -407,19 +481,6 @@
     return clazz;
   }
 
-  public DexClasspathClass createFixedClasspathClass(
-      SyntheticKind kind, DexClasspathClass context, DexItemFactory factory) {
-    // Obtain the outer synthesizing context in the case the context itself is synthetic.
-    // This is to ensure a flat input-type -> synthetic-item mapping.
-    SynthesizingContext outerContext = SynthesizingContext.fromNonSyntheticInputContext(context);
-    DexType type = SyntheticNaming.createFixedType(kind, outerContext, factory);
-    SyntheticClasspathClassBuilder classBuilder =
-        new SyntheticClasspathClassBuilder(type, outerContext, factory);
-    DexClasspathClass clazz = classBuilder.build();
-    addPendingDefinition(new SyntheticClasspathClassDefinition(kind, outerContext, clazz));
-    return clazz;
-  }
-
   /** Create a single synthetic method item. */
   public ProgramMethod createMethod(
       SyntheticKind kind,
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
index ab4cd49..487cb53 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
@@ -7,9 +7,11 @@
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
@@ -20,7 +22,8 @@
     Code generate(DexMethod method);
   }
 
-  private final SyntheticClassBuilder<?, ?> parent;
+  private final DexItemFactory factory;
+  private final DexType holderType;
   private DexString name = null;
   private DexProto proto = null;
   private CfVersion classFileVersion;
@@ -28,11 +31,17 @@
   private MethodAccessFlags accessFlags = null;
 
   SyntheticMethodBuilder(SyntheticClassBuilder<?, ?> parent) {
-    this.parent = parent;
+    this.factory = parent.getFactory();
+    this.holderType = parent.getType();
+  }
+
+  SyntheticMethodBuilder(DexItemFactory factory, DexType holderType) {
+    this.factory = factory;
+    this.holderType = holderType;
   }
 
   public SyntheticMethodBuilder setName(String name) {
-    return setName(parent.getFactory().createString(name));
+    return setName(factory.createString(name));
   }
 
   public SyntheticMethodBuilder setName(DexString name) {
@@ -95,7 +104,7 @@
   }
 
   private DexMethod getMethodSignature() {
-    return parent.getFactory().createMethod(parent.getType(), proto, name);
+    return factory.createMethod(holderType, proto, name);
   }
 
   private MethodAccessFlags getAccessFlags() {
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 79e63c0..210ae9b 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -47,6 +47,7 @@
 import com.android.tools.r8.ir.desugar.nest.Nest;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
+import com.android.tools.r8.naming.MapVersion;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.references.Reference;
@@ -269,6 +270,7 @@
   public boolean encodeChecksums = false;
   public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
   public boolean cfToCfDesugar = false;
+  public boolean forceAnnotateSynthetics = false;
 
   public int callGraphLikelySpuriousCallEdgeThreshold = 50;
 
@@ -1398,6 +1400,12 @@
     public boolean enableExperimentalMapFileVersion = false;
   }
 
+  public MapVersion getMapFileVersion() {
+    return testing.enableExperimentalMapFileVersion
+        ? MapVersion.MapVersionExperimental
+        : MapVersion.STABLE;
+  }
+
   @VisibleForTesting
   public void disableNameReflectionOptimization() {
     // Use this util to disable get*Name() computation if the main intention of tests is checking
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 050519f..3663ec2 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -43,6 +43,7 @@
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.ClassNaming;
 import com.android.tools.r8.naming.ClassNaming.Builder;
+import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -50,8 +51,7 @@
 import com.android.tools.r8.naming.Range;
 import com.android.tools.r8.naming.mappinginformation.CompilerSynthesizedMappingInformation;
 import com.android.tools.r8.naming.mappinginformation.FileNameInformation;
-import com.android.tools.r8.naming.mappinginformation.ScopeReference;
-import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.retrace.internal.RetraceUtils;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
@@ -294,28 +294,22 @@
                       com.android.tools.r8.position.Position.UNKNOWN));
 
       // Check if source file should be added to the map
-      ScopeReference classScope =
-          ScopeReference.fromClassReference(
-              Reference.classFromDescriptor(renamedDescriptor.toString()));
       if (clazz.sourceFile != null) {
         String sourceFile = clazz.sourceFile.toString();
         if (!RetraceUtils.hasPredictableSourceFileName(clazz.toSourceString(), sourceFile)) {
-          classNameMapperBuilder.addMappingInformation(
-              classScope,
-              FileNameInformation.build(sourceFile),
-              conflictingInfo -> {
-                throw new Unreachable();
-              });
+          onDemandClassNamingBuilder
+              .get()
+              .addMappingInformation(FileNameInformation.build(sourceFile), Unreachable::raise);
         }
       }
 
-      if (isSyntheticClass && appView.options().testing.enableExperimentalMapFileVersion) {
-        classNameMapperBuilder.addMappingInformation(
-            classScope,
-            CompilerSynthesizedMappingInformation.builder().build(),
-            conflictingInfo -> {
-              throw new Unreachable();
-            });
+      if (isSyntheticClass
+          && CompilerSynthesizedMappingInformation.isSupported(
+              appView.options().getMapFileVersion())) {
+        onDemandClassNamingBuilder
+            .get()
+            .addMappingInformation(
+                CompilerSynthesizedMappingInformation.builder().build(), Unreachable::raise);
       }
 
       // If the class is renamed add it to the classNamingBuilder.
@@ -384,15 +378,32 @@
           DexString obfuscatedNameDexString = namingLens.lookupName(method.getReference());
           String obfuscatedName = obfuscatedNameDexString.toString();
 
+          List<MappingInformation> methodMappingInfo = new ArrayList<>();
+          if (method.isD8R8Synthesized()
+              && CompilerSynthesizedMappingInformation.isSupported(
+                  appView.options().getMapFileVersion())) {
+            methodMappingInfo.add(CompilerSynthesizedMappingInformation.builder().build());
+          }
+
+          // Don't emit pure identity mappings.
+          if (mappedPositions.isEmpty()
+              && methodMappingInfo.isEmpty()
+              && obfuscatedNameDexString == originalMethod.name
+              && originalMethod.holder == originalType) {
+            continue;
+          }
+
+          MemberNaming memberNaming = new MemberNaming(originalSignature, obfuscatedName);
+          onDemandClassNamingBuilder.get().addMemberEntry(memberNaming);
+
           // Add simple "a() -> b" mapping if we won't have any other with concrete line numbers
           if (mappedPositions.isEmpty()) {
-            // But only if it's been renamed.
-            if (obfuscatedNameDexString != originalMethod.name
-                || originalMethod.holder != originalType) {
-              onDemandClassNamingBuilder
-                  .get()
-                  .addMappedRange(null, originalSignature, null, obfuscatedName);
-            }
+            MappedRange range =
+                onDemandClassNamingBuilder
+                    .get()
+                    .addMappedRange(null, originalSignature, null, obfuscatedName);
+            methodMappingInfo.forEach(
+                info -> range.addMappingInformation(info, Unreachable::raise));
             continue;
           }
 
@@ -403,9 +414,6 @@
                   signatures.computeIfAbsent(
                       m, key -> MethodSignature.fromDexMethod(m, m.holder != clazz.getType()));
 
-          MemberNaming memberNaming = new MemberNaming(originalSignature, obfuscatedName);
-          onDemandClassNamingBuilder.get().addMemberEntry(memberNaming);
-
           // Update memberNaming with the collected positions, merging multiple positions into a
           // single region whenever possible.
           for (int i = 0; i < mappedPositions.size(); /* updated in body */ ) {
@@ -449,20 +457,25 @@
             Range originalRange = new Range(firstPosition.originalLine, lastPosition.originalLine);
 
             ClassNaming.Builder classNamingBuilder = onDemandClassNamingBuilder.get();
-            classNamingBuilder.addMappedRange(
-                obfuscatedRange,
-                getOriginalMethodSignature.apply(firstPosition.method),
-                originalRange,
-                obfuscatedName);
+            MappedRange lastMappedRange =
+                classNamingBuilder.addMappedRange(
+                    obfuscatedRange,
+                    getOriginalMethodSignature.apply(firstPosition.method),
+                    originalRange,
+                    obfuscatedName);
             Position caller = firstPosition.caller;
             while (caller != null) {
-              classNamingBuilder.addMappedRange(
-                  obfuscatedRange,
-                  getOriginalMethodSignature.apply(caller.method),
-                  Math.max(caller.line, 0), // Prevent against "no-position".
-                  obfuscatedName);
+              lastMappedRange =
+                  classNamingBuilder.addMappedRange(
+                      obfuscatedRange,
+                      getOriginalMethodSignature.apply(caller.method),
+                      Math.max(caller.line, 0), // Prevent against "no-position".
+                      obfuscatedName);
               caller = caller.callerPosition;
             }
+            for (MappingInformation info : methodMappingInfo) {
+              lastMappedRange.addMappingInformation(info, Unreachable::raise);
+            }
             i = j;
           }
         } // for each method of the group
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index d299224..822a5bd 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -82,9 +82,12 @@
 
   @Override
   public D8TestBuilder enableCoreLibraryDesugaring(
-      AndroidApiLevel minApiLevel, KeepRuleConsumer keepRuleConsumer) {
+      AndroidApiLevel minApiLevel,
+      KeepRuleConsumer keepRuleConsumer,
+      StringResource desugaredLibraryConfiguration) {
     if (minApiLevel.getLevel() < AndroidApiLevel.O.getLevel()) {
-      super.enableCoreLibraryDesugaring(minApiLevel, keepRuleConsumer);
+      super.enableCoreLibraryDesugaring(
+          minApiLevel, keepRuleConsumer, desugaredLibraryConfiguration);
       builder.setDesugaredLibraryKeepRuleConsumer(keepRuleConsumer);
     }
     return self();
diff --git a/src/test/java/com/android/tools/r8/D8TestRunResult.java b/src/test/java/com/android/tools/r8/D8TestRunResult.java
index d0fe6fb..c4427d1 100644
--- a/src/test/java/com/android/tools/r8/D8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/D8TestRunResult.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertNotNull;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
@@ -31,4 +32,12 @@
     assertNotNull(app);
     return proguardMap == null ? new CodeInspector(app) : new CodeInspector(app, proguardMap);
   }
+
+  @Override
+  public StackTrace getStackTrace() {
+    if (proguardMap == null) {
+      return super.getStackTrace();
+    }
+    return super.getStackTrace().retrace(proguardMap);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
index f19059b..ec2b927 100644
--- a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
+++ b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
@@ -30,7 +30,8 @@
 
   public enum KotlinCompilerVersion {
     KOTLINC_1_3_72("kotlin-compiler-1.3.72"),
-    KOTLINC_1_4_20("kotlin-compiler-1.4.20");
+    KOTLINC_1_4_20("kotlin-compiler-1.4.20"),
+    KOTLINC_1_5_0_M2("kotlin-compiler-1.5.0-M2");
 
     private final String folder;
 
@@ -77,6 +78,10 @@
       return compilerVersion == version;
     }
 
+    public boolean isNot(KotlinCompilerVersion version) {
+      return !is(version);
+    }
+
     public KotlinCompilerVersion getCompilerVersion() {
       return compilerVersion;
     }
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 4b095bb..95198ec 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -845,6 +845,11 @@
                       DexVm.Version.V4_4_4,
                       DexVm.Version.V5_1_1,
                       DexVm.Version.V6_0_1)))
+          // Class.forName() that fails due to verification error is removed.
+          .put(
+              "412-new-array",
+              TestCondition.match(
+                  TestCondition.compilers(CompilerUnderTest.R8, CompilerUnderTest.R8_AFTER_D8)))
           // Array index out of bounds exception.
           .put("449-checker-bce", TestCondition.any())
           // Fails: get_vreg_jni.cc:46] Check failed: value == 42u (value=314630384, 42u=42)
@@ -887,6 +892,11 @@
           .put("575-checker-string-init-alias", TestCondition.any())
           // Runtime exception.
           .put("577-profile-foreign-dex", TestCondition.any())
+          // Class.forName() that fails due to linkage error is removed.
+          .put(
+              "587-inline-class-error",
+              TestCondition.match(
+                  TestCondition.compilers(CompilerUnderTest.R8, CompilerUnderTest.R8_AFTER_D8)))
           // Array index out of bounds exception.
           .put("602-deoptimizeable", TestCondition.any())
           // Array index out of bounds exception.
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 0d90a34..19b414a 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -618,9 +618,12 @@
 
   @Override
   public T enableCoreLibraryDesugaring(
-      AndroidApiLevel minApiLevel, KeepRuleConsumer keepRuleConsumer) {
+      AndroidApiLevel minApiLevel,
+      KeepRuleConsumer keepRuleConsumer,
+      StringResource desugaredLibraryConfiguration) {
     if (minApiLevel.getLevel() < AndroidApiLevel.O.getLevel()) {
-      super.enableCoreLibraryDesugaring(minApiLevel, keepRuleConsumer);
+      super.enableCoreLibraryDesugaring(
+          minApiLevel, keepRuleConsumer, desugaredLibraryConfiguration);
       builder.setDesugaredLibraryKeepRuleConsumer(keepRuleConsumer);
     }
     return self();
diff --git a/src/test/java/com/android/tools/r8/SingleTestRunResult.java b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
index 07e0736..17546ee 100644
--- a/src/test/java/com/android/tools/r8/SingleTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
@@ -50,6 +50,11 @@
     return result.stdout;
   }
 
+  public <E extends Throwable> RR inspectStdOut(ThrowingConsumer<String, E> consumer) throws E {
+    consumer.accept(getStdOut());
+    return self();
+  }
+
   public String getStdErr() {
     return result.stderr;
   }
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 766fb0f..cd584e0 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -432,9 +432,18 @@
 
   public T enableCoreLibraryDesugaring(
       AndroidApiLevel minApiLevel, KeepRuleConsumer keepRuleConsumer) {
-    assert minApiLevel.getLevel() < AndroidApiLevel.O.getLevel();
-    builder.addDesugaredLibraryConfiguration(
+    return enableCoreLibraryDesugaring(
+        minApiLevel,
+        keepRuleConsumer,
         StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()));
+  }
+
+  public T enableCoreLibraryDesugaring(
+      AndroidApiLevel minApiLevel,
+      KeepRuleConsumer keepRuleConsumer,
+      StringResource desugaredLibraryConfiguration) {
+    assert minApiLevel.getLevel() < AndroidApiLevel.O.getLevel();
+    builder.addDesugaredLibraryConfiguration(desugaredLibraryConfiguration);
     // TODO(b/158543446): This should not be setting an implicit library file. Doing so causes
     //  inconsistent library setup depending on the api level and makes tests hard to read and
     //  reason about.
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index 34cc0b0..23e2e5c 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -53,8 +53,51 @@
   public <S extends Throwable, T extends Throwable> RR applyIf(
       boolean condition, ThrowingConsumer<RR, S> thenConsumer, ThrowingConsumer<RR, T> elseConsumer)
       throws S, T {
-    if (condition) {
-      thenConsumer.accept(self());
+    return applyIf(
+        condition,
+        thenConsumer,
+        true,
+        elseConsumer,
+        r -> {
+          assert false;
+        });
+  }
+
+  public <S extends Throwable, T extends Throwable, U extends Throwable> RR applyIf(
+      boolean condition1,
+      ThrowingConsumer<RR, S> thenConsumer1,
+      boolean condition2,
+      ThrowingConsumer<RR, T> thenConsumer2,
+      ThrowingConsumer<RR, U> elseConsumer)
+      throws S, T, U {
+    return applyIf(
+        condition1,
+        thenConsumer1,
+        condition2,
+        thenConsumer2,
+        true,
+        elseConsumer,
+        r -> {
+          assert false;
+        });
+  }
+
+  public <S extends Throwable, T extends Throwable, U extends Throwable, V extends Throwable>
+      RR applyIf(
+          boolean condition1,
+          ThrowingConsumer<RR, S> thenConsumer1,
+          boolean condition2,
+          ThrowingConsumer<RR, T> thenConsumer2,
+          boolean condition3,
+          ThrowingConsumer<RR, U> thenConsumer3,
+          ThrowingConsumer<RR, V> elseConsumer)
+          throws S, T, U, V {
+    if (condition1) {
+      thenConsumer1.accept(self());
+    } else if (condition2) {
+      thenConsumer2.accept(self());
+    } else if (condition3) {
+      thenConsumer3.accept(self());
     } else {
       elseConsumer.accept(self());
     }
diff --git a/src/test/java/com/android/tools/r8/TestRunResultCollection.java b/src/test/java/com/android/tools/r8/TestRunResultCollection.java
index 4912ef8..e166be4 100644
--- a/src/test/java/com/android/tools/r8/TestRunResultCollection.java
+++ b/src/test/java/com/android/tools/r8/TestRunResultCollection.java
@@ -57,10 +57,59 @@
     return inspectIf(Predicates.alwaysTrue(), consumer);
   }
 
-  public RR applyIf(Predicate<C> filter, Consumer<TestRunResult<?>> fn) {
+  public RR applyIf(Predicate<C> filter, Consumer<TestRunResult<?>> thenConsumer) {
+    return applyIf(filter, thenConsumer, r -> {});
+  }
+
+  public RR applyIf(
+      Predicate<C> filter,
+      Consumer<TestRunResult<?>> thenConsumer,
+      Consumer<TestRunResult<?>> elseConsumer) {
+    return applyIf(
+        filter,
+        thenConsumer,
+        c -> true,
+        elseConsumer,
+        r -> {
+          assert false;
+        });
+  }
+
+  public RR applyIf(
+      Predicate<C> filter1,
+      Consumer<TestRunResult<?>> thenConsumer1,
+      Predicate<C> filter2,
+      Consumer<TestRunResult<?>> thenConsumer2,
+      Consumer<TestRunResult<?>> elseConsumer) {
+    return applyIf(
+        filter1,
+        thenConsumer1,
+        filter2,
+        thenConsumer2,
+        c -> true,
+        elseConsumer,
+        r -> {
+          assert false;
+        });
+  }
+
+  public RR applyIf(
+      Predicate<C> filter1,
+      Consumer<TestRunResult<?>> thenConsumer1,
+      Predicate<C> filter2,
+      Consumer<TestRunResult<?>> thenConsumer2,
+      Predicate<C> filter3,
+      Consumer<TestRunResult<?>> thenConsumer3,
+      Consumer<TestRunResult<?>> elseConsumer) {
     for (Pair<C, TestRunResult<?>> run : runs) {
-      if (filter.test(run.getFirst())) {
-        fn.accept(run.getSecond());
+      if (filter1.test(run.getFirst())) {
+        thenConsumer1.accept(run.getSecond());
+      } else if (filter2.test(run.getFirst())) {
+        thenConsumer2.accept(run.getSecond());
+      } else if (filter3.test(run.getFirst())) {
+        thenConsumer3.accept(run.getSecond());
+      } else {
+        elseConsumer.accept(run.getSecond());
       }
     }
     return self();
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
index 20bf4b8..5aef2ad 100644
--- a/src/test/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -323,6 +323,10 @@
       return Objects.hash(vm, home);
     }
 
+    public boolean isOlderThan(CfVm version) {
+      return vm.lessThan(version);
+    }
+
     public boolean isNewerThan(CfVm version) {
       return !vm.lessThanOrEqual(version);
     }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index e9a47e3..c3949d9 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
 import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_4_20;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0_M2;
 import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.isDexFile;
 import static org.junit.Assert.assertEquals;
@@ -2152,8 +2153,12 @@
     return new KotlinCompiler(KOTLINC_1_4_20);
   }
 
+  public static KotlinCompiler getKotlinC_1_5_0_m2() {
+    return new KotlinCompiler(KOTLINC_1_5_0_M2);
+  }
+
   public static KotlinCompiler[] getKotlinCompilers() {
-    return new KotlinCompiler[] {getKotlinC_1_3_72(), getKotlinC_1_4_20()};
+    return new KotlinCompiler[] {getKotlinC_1_3_72(), getKotlinC_1_4_20(), getKotlinC_1_5_0_m2()};
   }
 
   public static void disassemble(AndroidApp app, PrintStream ps) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java b/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java
index 550e21f..8540d13 100644
--- a/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java
+++ b/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java
@@ -9,13 +9,11 @@
 import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.ToolHelper.getKotlinStdlibJar;
 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.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime;
@@ -77,15 +75,10 @@
   }
 
   private void inspectSourceDebugExtension(CodeInspector inspector) {
-    ClassSubject clazz = inspector.clazz("retrace.InlineFunctionKt");
+    ClassSubject clazz = inspector.clazz("retrace.MainKt");
     assertThat(clazz, isPresent());
     AnnotationSubject sourceDebugExtensions =
         clazz.annotation("dalvik.annotation.SourceDebugExtension");
-    // TODO(b/179866574): This is somehow not present
-    if (kotlinCompiler.is(KotlinCompilerVersion.KOTLINC_1_4_20)) {
-      assertThat(sourceDebugExtensions, not(isPresent()));
-    } else {
-      assertThat(sourceDebugExtensions, isPresent());
-    }
+    assertThat(sourceDebugExtensions, isPresent());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
index b385fd6..a8d57c0 100644
--- a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.graph.GenericSignatureIdentityTest.testParseSignaturesInJar;
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static com.google.common.io.ByteStreams.toByteArray;
-import static org.hamcrest.CoreMatchers.anyOf;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -106,14 +105,7 @@
           .setMode(mode)
           .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
           .addKeepRuleFiles(MAIN_KEEP)
-          .allowDiagnosticInfoMessages(mode == CompilationMode.DEBUG)
           .compile()
-          .assertAllInfoMessagesMatch(
-              anyOf(
-                  containsString("Stripped invalid locals information from 1 method."),
-                  containsString("Methods with invalid locals information:"),
-                  containsString(
-                      "Some warnings are typically a sign of using an outdated Java toolchain.")))
           .apply(c -> FileUtils.writeTextFile(map, c.getProguardMap()))
           .writeToZip(jar);
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerIndirectReflectiveNameTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerIndirectReflectiveNameTest.java
index f52ff7c..0dd71da 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerIndirectReflectiveNameTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerIndirectReflectiveNameTest.java
@@ -4,11 +4,6 @@
 
 package com.android.tools.r8.classmerging.vertical;
 
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertThrows;
-
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
@@ -48,26 +43,15 @@
 
   @Test
   public void testR8() throws Exception {
-    // TODO(b/173099479): This should not throw an assertion-error.
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            testForR8(parameters.getBackend())
-                .addProgramClasses(Main.class, A.class, B.class)
-                .addKeepMainRule(Main.class)
-                .setMinApi(parameters.getApiLevel())
-                .enableInliningAnnotations()
-                .enableNeverClassInliningAnnotations()
-                .compileWithExpectedDiagnostics(
-                    diagnostics -> {
-                      diagnostics.assertErrorsMatch(
-                          diagnosticMessage(
-                              containsString(
-                                  "Expected vertically merged class"
-                                      + " `com.android.tools.r8.classmerging.vertical."
-                                      + "VerticalClassMergerIndirectReflectiveNameTest$A`"
-                                      + " to be absent")));
-                    }));
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, A.class, B.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A::foo", "B::foo");
   }
 
   public static class A {
diff --git a/src/test/java/com/android/tools/r8/desugar/InterfaceInvokePrivateTest.java b/src/test/java/com/android/tools/r8/desugar/InterfaceInvokePrivateTest.java
new file mode 100644
index 0000000..86c5440
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/InterfaceInvokePrivateTest.java
@@ -0,0 +1,164 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar;
+
+import static com.android.tools.r8.utils.InternalOptions.EXPERIMENTAL_CF_VERSION;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class InterfaceInvokePrivateTest extends TestBase implements Opcodes {
+
+  private final TestParameters parameters;
+  private final CfVersion inputCfVersion;
+
+  @Parameterized.Parameters(name = "{0}, Input CfVersion = {1}")
+  public static Iterable<?> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().withDexRuntimes().withAllApiLevelsAlsoForCf().build(),
+        CfVersion.rangeInclusive(CfVersion.V1_8, CfVersion.V15));
+  }
+
+  public InterfaceInvokePrivateTest(TestParameters parameters, CfVersion inputCfVersion) {
+    this.parameters = parameters;
+    this.inputCfVersion = inputCfVersion;
+  }
+
+  private boolean isNotDesugaredAndCfRuntimeOlderThanJDK11(DesugarTestConfiguration configuration) {
+    return DesugarTestConfiguration.isNotDesugared(configuration)
+        && parameters.getRuntime().isCf()
+        && parameters.getRuntime().asCf().isOlderThan(CfVm.JDK11);
+  }
+
+  private boolean isInputCfVersionSupported() {
+    return inputCfVersion.isLessThanOrEqualTo(
+        CfVersion.fromRaw(parameters.getRuntime().asCf().getVm().getClassfileVersion()));
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    assumeTrue(parameters.getRuntime().isCf());
+    assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+
+    testForJvm()
+        .addProgramClassFileData(transformIToPrivate(inputCfVersion))
+        .addProgramClasses(TestRunner.class)
+        .run(parameters.getRuntime(), TestRunner.class)
+        .applyIf(
+            // Fails if input CF version is not supported.
+            !isInputCfVersionSupported(),
+            r ->
+                r.assertFailureWithErrorThatMatches(
+                    containsString(
+                        "more recent version of the Java Runtime (class file version "
+                            + inputCfVersion.toString())),
+            // Fails with ICCE on VMs prior to JDK 11.
+            parameters.getRuntime().asCf().isOlderThan(CfVm.JDK11),
+            r -> r.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class),
+            // Succeeds on VMs from JDK 11 regardless of the CF version of the input.
+            r -> r.assertSuccessWithOutputLines("Hello, world!"));
+  }
+
+  @Test
+  public void testDesugar() throws Exception {
+    testForDesugaring(parameters)
+        .addProgramClassFileData(transformIToPrivate(inputCfVersion))
+        .addProgramClasses(TestRunner.class)
+        .run(parameters.getRuntime(), TestRunner.class)
+        .applyIf(
+            // Running un-desugared on a JVM with too high a CF version fails.
+            c ->
+                parameters.getRuntime().isCf()
+                    && !isInputCfVersionSupported()
+                    && DesugarTestConfiguration.isNotDesugared(c),
+            r ->
+                r.assertFailureWithErrorThatMatches(
+                    containsString(
+                        "more recent version of the Java Runtime (class file version "
+                            + inputCfVersion.toString())),
+            // Running un-desugared on a JVM with a supported CF version fails on pre JVM 11. On
+            // JVM 11 and above this succeeds even of the input CF version is below 55.
+            this::isNotDesugaredAndCfRuntimeOlderThanJDK11,
+            r -> r.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class),
+            // All other conditions succeed.
+            r -> r.assertSuccessWithOutputLines("Hello, world!"));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(transformIToPrivate(inputCfVersion))
+        .addProgramClasses(TestRunner.class)
+        .addKeepMainRule(TestRunner.class)
+        // TODO(b/185463156): Not keeping I and its members will "fix" the ICCE for all runtimes.
+        .addKeepClassAndMembersRules(I.class)
+        .setMinApi(parameters.getApiLevel())
+        .allowDiagnosticWarningMessages(
+            inputCfVersion.isGreaterThanOrEqualTo(EXPERIMENTAL_CF_VERSION))
+        .compile()
+        .run(parameters.getRuntime(), TestRunner.class)
+        .applyIf(
+            // Running un-desugared on a JVM with too high a CF version fails.
+            parameters.getRuntime().isCf() && !isInputCfVersionSupported(),
+            r ->
+                r.assertFailureWithErrorThatMatches(
+                    containsString(
+                        "more recent version of the Java Runtime (class file version "
+                            + inputCfVersion.toString())),
+            // Running on a JVM with a supported CF version fails on pre JVM 11. On
+            // JVM 11 and above this succeeds even of the input CF version is below 55.
+            parameters.getRuntime().isCf()
+                && parameters.getRuntime().asCf().isOlderThan(CfVm.JDK11),
+            r -> r.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class),
+            // All other conditions succeed.
+            r -> r.assertSuccessWithOutputLines("Hello, world!"));
+  }
+
+  private byte[] transformIToPrivate(CfVersion version) throws NoSuchMethodException, IOException {
+    return transformer(I.class)
+        .setVersion(version)
+        .setPrivate(I.class.getDeclaredMethod("privateHello"))
+        .transformMethodInsnInMethod(
+            "hello",
+            ((opcode, owner, name, descriptor, isInterface, continuation) -> {
+              continuation.visitMethodInsn(
+                  name.equals("privateHello") ? Opcodes.INVOKEINTERFACE : opcode,
+                  owner,
+                  name,
+                  descriptor,
+                  isInterface);
+            }))
+        .transform();
+  }
+
+  interface I {
+    /* private */ default String privateHello() {
+      return "Hello, world!";
+    }
+
+    default String hello() {
+      // The private method "privateHello" is called with invokeinterface.
+      return privateHello();
+    }
+  }
+
+  public static class TestRunner implements I {
+
+    public static void main(String[] args) {
+      System.out.println(new TestRunner().hello());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/VirtualInvokePrivateTest.java b/src/test/java/com/android/tools/r8/desugar/VirtualInvokePrivateTest.java
new file mode 100644
index 0000000..55de73b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/VirtualInvokePrivateTest.java
@@ -0,0 +1,118 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class VirtualInvokePrivateTest extends TestBase implements Opcodes {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withCfRuntimes()
+        .withDexRuntimes()
+        .withAllApiLevelsAlsoForCf()
+        .build();
+  }
+
+  public VirtualInvokePrivateTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private boolean isNotDesugaredAndCfRuntimeNewerThanOrEqualToJDK11(
+      DesugarTestConfiguration configuration) {
+    return DesugarTestConfiguration.isNotDesugared(configuration)
+        && parameters.getRuntime().isCf()
+        && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11);
+  }
+
+  private void inspectNotDesugared(CodeInspector inspector) {
+    MethodSubject main = inspector.clazz(TestRunner.class).uniqueMethodWithName("main");
+    assertEquals(2, main.streamInstructions().filter(InstructionSubject::isInvokeVirtual).count());
+  }
+
+  private void inspectDesugared(CodeInspector inspector) {
+    MethodSubject main = inspector.clazz(TestRunner.class).uniqueMethodWithName("main");
+    assertEquals(1, main.streamInstructions().filter(InstructionSubject::isInvokeVirtual).count());
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    assumeTrue(parameters.getRuntime().isCf());
+    assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+
+    testForJvm()
+        .addProgramClassFileData(transformInvokeSpecialToInvokeVirtual())
+        .run(parameters.getRuntime(), TestRunner.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  @Test
+  public void testDesugar() throws Exception {
+    testForDesugaring(parameters)
+        .addProgramClassFileData(transformInvokeSpecialToInvokeVirtual())
+        .run(parameters.getRuntime(), TestRunner.class)
+        .inspectIf(
+            this::isNotDesugaredAndCfRuntimeNewerThanOrEqualToJDK11, this::inspectNotDesugared)
+        .inspectIf(DesugarTestConfiguration::isDesugared, this::inspectDesugared)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(transformInvokeSpecialToInvokeVirtual())
+        .addKeepMainRule(TestRunner.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestRunner.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  private byte[] transformInvokeSpecialToInvokeVirtual() throws IOException {
+    return transformer(TestRunner.class)
+        .setVersion(CfVersion.V1_8)
+        .transformMethodInsnInMethod(
+            "main",
+            ((opcode, owner, name, descriptor, isInterface, continuation) -> {
+              continuation.visitMethodInsn(
+                  name.equals("hello") ? Opcodes.INVOKEVIRTUAL : opcode,
+                  owner,
+                  name,
+                  descriptor,
+                  isInterface);
+            }))
+        .transform();
+  }
+
+  public static class TestRunner {
+    private String hello() {
+      return "Hello, world!";
+    }
+
+    public static void main(String[] args) {
+      // The private method "hello" is called with invokevirtual.
+      System.out.println(new TestRunner().hello());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index 8f1d451..3b9a63d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.errors.Unreachable;
@@ -27,21 +28,25 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.tracereferences.TraceReferences;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.base.Charsets;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
 import org.junit.BeforeClass;
+import org.junit.rules.TemporaryFolder;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.MethodVisitor;
@@ -94,6 +99,158 @@
     return parameters.getApiLevel().getLevel() < AndroidApiLevel.O.getLevel();
   }
 
+  public static class L8TestBuilder {
+
+    private final AndroidApiLevel apiLevel;
+    private final TemporaryFolder temp;
+
+    private CompilationMode mode = CompilationMode.RELEASE;
+    private List<String> keepRules = new ArrayList<>();
+    private List<Path> additionalProgramFiles = new ArrayList<>();
+    private Consumer<InternalOptions> optionsModifier = ConsumerUtils.emptyConsumer();
+    private Path desugarJDKLibs = ToolHelper.getDesugarJDKLibs();
+    private Path desugarJDKLibsConfiguration = ToolHelper.DESUGAR_LIB_CONVERSIONS;
+    private StringResource desugaredLibraryConfiguration =
+        StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting());
+
+    public static L8TestBuilder builder(AndroidApiLevel apiLevel, TemporaryFolder temp) {
+      return new L8TestBuilder(apiLevel, temp);
+    }
+
+    private L8TestBuilder(AndroidApiLevel apiLevel, TemporaryFolder temp) {
+      this.apiLevel = apiLevel;
+      this.temp = temp;
+    }
+
+    public L8TestBuilder addProgramFiles(Collection<Path> programFiles) {
+      this.additionalProgramFiles.addAll(programFiles);
+      return this;
+    }
+
+    public L8TestBuilder addKeepRules(String keepRules) {
+      if (!keepRules.trim().isEmpty()) {
+        this.keepRules.add(keepRules);
+      }
+      return this;
+    }
+
+    public L8TestBuilder addKeepRuleFiles(Collection<Path> keepRuleFiles) throws IOException {
+      for (Path keepRuleFile : keepRuleFiles) {
+        addKeepRules(FileUtils.readTextFile(keepRuleFile, StandardCharsets.UTF_8));
+      }
+      return this;
+    }
+
+    public L8TestBuilder addOptionsModifier(Consumer<InternalOptions> optionsModifier) {
+      this.optionsModifier = this.optionsModifier.andThen(optionsModifier);
+      return this;
+    }
+
+    public L8TestBuilder applyIf(boolean condition, ThrowableConsumer<L8TestBuilder> thenConsumer) {
+      return applyIf(condition, thenConsumer, ThrowableConsumer.empty());
+    }
+
+    public L8TestBuilder applyIf(
+        boolean condition,
+        ThrowableConsumer<L8TestBuilder> thenConsumer,
+        ThrowableConsumer<L8TestBuilder> elseConsumer) {
+      if (condition) {
+        thenConsumer.acceptWithRuntimeException(this);
+      } else {
+        elseConsumer.acceptWithRuntimeException(this);
+      }
+      return this;
+    }
+
+    public L8TestBuilder setDebug() {
+      this.mode = CompilationMode.DEBUG;
+      return this;
+    }
+
+    public L8TestBuilder setDesugarJDKLibs(Path desugarJDKLibs) {
+      this.desugarJDKLibs = desugarJDKLibs;
+      return this;
+    }
+
+    public L8TestBuilder setDesugarJDKLibsConfiguration(Path desugarJDKLibsConfiguration) {
+      this.desugarJDKLibsConfiguration = desugarJDKLibsConfiguration;
+      return this;
+    }
+
+    public L8TestBuilder setDesugaredLibraryConfiguration(Path path) {
+      this.desugaredLibraryConfiguration = StringResource.fromFile(path);
+      return this;
+    }
+
+    public Path compile() {
+      // We wrap exceptions in a RuntimeException to call this from a lambda.
+      try {
+        // If we compile extended library here, it means we use TestNG.
+        // TestNG requires annotations, hence we disable AnnotationRemoval.
+        // This implies that extra warning are generated if this is set.
+        TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
+        Path desugaredLib = temp.newFolder().toPath().resolve("desugar_jdk_libs_dex.zip");
+        L8Command.Builder l8Builder =
+            L8Command.builder(diagnosticsHandler)
+                .addProgramFiles(getProgramFiles())
+                .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+                .setMode(mode)
+                .addDesugaredLibraryConfiguration(desugaredLibraryConfiguration)
+                .setMinApiLevel(apiLevel.getLevel())
+                .setOutput(desugaredLib, OutputMode.DexIndexed);
+        Path mapping = null;
+        if (!keepRules.isEmpty()) {
+          mapping = temp.newFolder().toPath().resolve("mapping.txt");
+          l8Builder.addProguardConfiguration(
+              ImmutableList.<String>builder()
+                  .addAll(keepRules)
+                  .add("-printmapping " + mapping)
+                  .build(),
+              Origin.unknown());
+        }
+        ToolHelper.runL8(
+            l8Builder.build(),
+            options -> {
+              if (!additionalProgramFiles.isEmpty()) {
+                options.testing.disableL8AnnotationRemoval = true;
+              }
+              optionsModifier.accept(options);
+            });
+        if (additionalProgramFiles.isEmpty()) {
+          assertTrue(
+              diagnosticsHandler.getInfos().stream()
+                  .noneMatch(
+                      string ->
+                          string
+                              .getDiagnosticMessage()
+                              .startsWith(
+                                  "Invalid parameter counts in MethodParameter attributes.")));
+        }
+        new CodeInspector(desugaredLib, mapping)
+            .forAllClasses(clazz -> assertTrue(clazz.getFinalName().startsWith("j$.")));
+        return desugaredLib;
+      } catch (Exception e) {
+        // Don't wrap assumption violation so junit can catch it.
+        if (e instanceof RuntimeException) {
+          throw ((RuntimeException) e);
+        }
+        throw new RuntimeException(e);
+      }
+    }
+
+    private Collection<Path> getProgramFiles() {
+      return ImmutableList.<Path>builder()
+          .add(desugarJDKLibs)
+          .add(desugarJDKLibsConfiguration)
+          .addAll(additionalProgramFiles)
+          .build();
+    }
+  }
+
+  protected L8TestBuilder testForL8(AndroidApiLevel apiLevel) {
+    return new L8TestBuilder(apiLevel, temp);
+  }
+
   protected Path buildDesugaredLibrary(AndroidApiLevel apiLevel) {
     return buildDesugaredLibrary(apiLevel, "", false);
   }
@@ -126,62 +283,11 @@
       boolean shrink,
       List<Path> additionalProgramFiles,
       Consumer<InternalOptions> optionsModifier) {
-    // We wrap exceptions in a RuntimeException to call this from a lambda.
-    try {
-      // If we compile extended library here, it means we use TestNG.
-      // TestNG requires annotations, hence we disable AnnotationRemoval.
-      // This implies that extra warning are generated if this is set.
-      boolean extraFiles = !additionalProgramFiles.isEmpty();
-      ArrayList<Path> extraPaths = new ArrayList<>(additionalProgramFiles);
-      TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
-      Path desugaredLib = temp.newFolder().toPath().resolve("desugar_jdk_libs_dex.zip");
-      L8Command.Builder l8Builder =
-          L8Command.builder(diagnosticsHandler)
-              .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
-              .addProgramFiles(ToolHelper.getDesugarJDKLibs())
-              .addProgramFiles(ToolHelper.DESUGAR_LIB_CONVERSIONS)
-              .setMode(shrink ? CompilationMode.RELEASE : CompilationMode.DEBUG)
-              .addProgramFiles(extraPaths)
-              .addDesugaredLibraryConfiguration(
-                  StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()))
-              .setMinApiLevel(apiLevel.getLevel())
-              .setOutput(desugaredLib, OutputMode.DexIndexed);
-      Path mapping = null;
-      if (shrink) {
-        mapping = temp.newFolder().toPath().resolve("mapping.txt");
-        List<String> lines =
-            new ArrayList<>(Arrays.asList(keepRules.split(System.lineSeparator())));
-        lines.add("-printmapping " + mapping);
-        l8Builder.addProguardConfiguration(lines, Origin.unknown());
-      }
-      ToolHelper.runL8(
-          l8Builder.build(),
-          options -> {
-            if (extraFiles) {
-              options.testing.disableL8AnnotationRemoval = true;
-            }
-            optionsModifier.accept(options);
-          });
-      if (!extraFiles) {
-        assertTrue(
-            diagnosticsHandler.getInfos().stream()
-                .noneMatch(
-                    string ->
-                        string
-                            .getDiagnosticMessage()
-                            .startsWith(
-                                "Invalid parameter counts in MethodParameter attributes.")));
-      }
-      new CodeInspector(desugaredLib, mapping)
-          .forAllClasses(clazz -> assertTrue(clazz.getFinalName().startsWith("j$.")));
-      return desugaredLib;
-    } catch (Exception e) {
-      // Don't wrap assumption violation so junit can catch it.
-      if (e instanceof RuntimeException) {
-        throw ((RuntimeException) e);
-      }
-      throw new RuntimeException(e);
-    }
+    return testForL8(apiLevel)
+        .addProgramFiles(additionalProgramFiles)
+        .applyIf(shrink, builder -> builder.addKeepRules(keepRules), L8TestBuilder::setDebug)
+        .addOptionsModifier(optionsModifier)
+        .compile();
   }
 
   protected void assertLines2By2Correct(String stdOut) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
index 2d3c243..ef8f61a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.kotlin;
 
-import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_4_20;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
 import static com.android.tools.r8.KotlinTestBase.getCompileMemoizer;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.containsString;
@@ -132,7 +132,7 @@
             .setMinApi(parameters.getApiLevel())
             .allowDiagnosticWarningMessages()
             .allowUnusedDontWarnKotlinReflectJvmInternal(
-                kotlinParameters.getCompiler().is(KOTLINC_1_4_20));
+                kotlinParameters.getCompiler().isNot(KOTLINC_1_3_72));
     KeepRuleConsumer keepRuleConsumer = null;
     if (desugarLibrary) {
       keepRuleConsumer = createKeepRuleConsumer(parameters);
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 0bc6b67..6382436 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
@@ -49,26 +49,22 @@
         .addKeepRules(enumKeepRules.getKeepRules())
         .addKeepClassRules(ClassAnnotationDefault.class)
         .addKeepRuntimeVisibleAnnotations()
+        .addEnumUnboxingInspector(
+            inspector ->
+                inspector
+                    .assertUnboxed(MyEnumParamMethod2.class, MyEnumRetMethod2.class)
+                    .assertNotUnboxed(
+                        MyEnum.class,
+                        MyEnumDefault.class,
+                        MyEnumArray.class,
+                        MyEnumArrayDefault.class))
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .enableMemberValuePropagationAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-        .allowDiagnosticInfoMessages()
         .setMinApi(parameters.getApiLevel())
         .compile()
-        .inspectDiagnosticMessages(
-            m -> {
-              assertEnumIsBoxed(MyEnumDefault.class, MyEnumDefault.class.getSimpleName(), m);
-              assertEnumIsBoxed(MyEnum.class, MyEnum.class.getSimpleName(), m);
-              assertEnumIsBoxed(
-                  MyEnumArrayDefault.class, MyEnumArrayDefault.class.getSimpleName(), m);
-              assertEnumIsBoxed(MyEnumArray.class, MyEnumArray.class.getSimpleName(), m);
-              assertEnumIsUnboxed(
-                  MyEnumRetMethod2.class, MyEnumRetMethod2.class.getSimpleName(), m);
-              assertEnumIsUnboxed(
-                  MyEnumParamMethod2.class, MyEnumParamMethod2.class.getSimpleName(), m);
-            })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("print", "1", "1", "1", "1", "1", "0", "0", "0", "0");
   }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ClInitSideEffectEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ClInitSideEffectEnumUnboxingTest.java
index 67dfbc0..743a819 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ClInitSideEffectEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ClInitSideEffectEnumUnboxingTest.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import org.junit.Test;
@@ -37,23 +36,20 @@
   @Test
   public void testEnumUnboxing() throws Exception {
     Class<Switch> classToTest = Switch.class;
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(ClInitSideEffectEnumUnboxingTest.class)
-            .addKeepMainRule(classToTest)
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .enableInliningAnnotations()
-            .enableNoHorizontalClassMergingAnnotations()
-            .enableNeverClassInliningAnnotations()
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspectDiagnosticMessages(
-                m -> assertEnumIsUnboxed(MyEnum.class, classToTest.getSimpleName(), m))
-            .run(parameters.getRuntime(), classToTest)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClInitSideEffectEnumUnboxingTest.class)
+        .addKeepMainRule(classToTest)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+        .enableInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), classToTest)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ClassAccessEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ClassAccessEnumUnboxingTest.java
index a39e30f..a24cb93 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ClassAccessEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ClassAccessEnumUnboxingTest.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import org.junit.Assume;
@@ -37,27 +36,23 @@
   @Test
   public void testEnumUnboxing() throws Exception {
     Assume.assumeTrue("studio rules required to use valueOf", enumKeepRules.isStudio());
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(ClassAccessEnumUnboxingTest.class)
-            .addKeepMainRule(Main.class)
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .enableNeverClassInliningAnnotations()
-            .enableInliningAnnotations()
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspectDiagnosticMessages(
-                m -> {
-                  assertEnumIsUnboxed(ProtoEnumLike.class, ProtoEnumLike.class.getSimpleName(), m);
-                  assertEnumIsUnboxed(UnboxableEnum.class, UnboxableEnum.class.getSimpleName(), m);
-                  assertEnumIsBoxed(EscapingEnum1.class, EscapingEnum1.class.getSimpleName(), m);
-                  assertEnumIsBoxed(EscapingEnum2.class, EscapingEnum2.class.getSimpleName(), m);
-                })
-            .run(parameters.getRuntime(), Main.class)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassAccessEnumUnboxingTest.class)
+        .addKeepMainRule(Main.class)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(
+            inspector ->
+                inspector
+                    .assertUnboxed(ProtoEnumLike.class, UnboxableEnum.class)
+                    .assertNotUnboxed(EscapingEnum1.class, EscapingEnum2.class))
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingTest.java
index 9873711..e6fdc93 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingTest.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import org.junit.Test;
@@ -19,7 +18,7 @@
 @RunWith(Parameterized.class)
 public class ComparisonEnumUnboxingTest extends EnumUnboxingTestBase {
 
-  private static final Class<?>[] INPUTS = new Class<?>[] {NullCheck.class, EnumComparison.class};
+  private static final Class<?>[] TESTS = new Class<?>[] {NullCheck.class, EnumComparison.class};
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
@@ -42,12 +41,14 @@
     R8TestCompileResult compile =
         testForR8(parameters.getBackend())
             .addInnerClasses(ComparisonEnumUnboxingTest.class)
-            .addKeepMainRules(INPUTS)
+            .addKeepMainRules(TESTS)
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
             .addKeepRules(enumKeepRules.getKeepRules())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
+            .addEnumUnboxingInspector(
+                inspector ->
+                    inspector.assertUnboxed(NullCheck.MyEnum.class, EnumComparison.MyEnum.class))
             .setMinApi(parameters.getApiLevel())
             .compile()
             .inspect(
@@ -55,14 +56,11 @@
                   assertEquals(3, inspector.clazz(NullCheck.class).allMethods().size());
                   assertEquals(2, inspector.clazz(EnumComparison.class).allMethods().size());
                 });
-    for (Class<?> input : INPUTS) {
-      R8TestRunResult run =
-          compile
-              .inspectDiagnosticMessages(
-                  m -> assertEnumIsUnboxed(input.getDeclaredClasses()[0], input.getSimpleName(), m))
-              .run(parameters.getRuntime(), input)
-              .assertSuccess();
-      assertLines2By2Correct(run.getStdOut());
+    for (Class<?> main : TESTS) {
+      compile
+          .run(parameters.getRuntime(), main)
+          .assertSuccess()
+          .inspectStdOut(this::assertLines2By2Correct);
     }
   }
 
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 e9a7e5d..0326e0b 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
@@ -7,8 +7,8 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.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.references.Reference;
@@ -67,24 +67,21 @@
             .compile()
             .writeToZip();
     // Compile the app with the lib.
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addProgramClasses(App.class, App.AppEnum.class)
-            .addProgramFiles(javaLibShrunk)
-            .addKeepMainRule(App.class)
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .enableNeverClassInliningAnnotations()
-            .enableInliningAnnotations()
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspect(this::assertUtilityClassPresent)
-            .inspectDiagnosticMessages(
-                m -> assertEnumIsUnboxed(App.AppEnum.class, App.class.getSimpleName(), m))
-            .run(parameters.getRuntime(), App.class)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addProgramClasses(App.class, App.AppEnum.class)
+        .addProgramFiles(javaLibShrunk)
+        .addKeepMainRule(App.class)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(App.AppEnum.class))
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::assertUtilityClassPresent)
+        .run(parameters.getRuntime(), App.class)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   private void assertUtilityClassPresent(CodeInspector codeInspector) {
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 7cc81c3..44cdff0 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
@@ -7,7 +7,6 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.enumunboxing.examplelib1.JavaLibrary1;
 import com.android.tools.r8.enumunboxing.examplelib2.JavaLibrary2;
@@ -58,24 +57,21 @@
     Path javaLibShrunk1 = compileLibrary(JavaLibrary1.class, JavaLibrary1.LibEnum1.class);
     Path javaLibShrunk2 = compileLibrary(JavaLibrary2.class, JavaLibrary2.LibEnum2.class);
     // Compile the app with the lib.
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addProgramClasses(App.class, App.AppEnum.class)
-            .addProgramFiles(javaLibShrunk1, javaLibShrunk2)
-            .addKeepMainRule(App.class)
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .enableNeverClassInliningAnnotations()
-            .enableInliningAnnotations()
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspect(this::assertUtilityClassPresent)
-            .inspectDiagnosticMessages(
-                m -> assertEnumIsUnboxed(App.AppEnum.class, App.class.getSimpleName(), m))
-            .run(parameters.getRuntime(), App.class)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addProgramClasses(App.class, App.AppEnum.class)
+        .addProgramFiles(javaLibShrunk1, javaLibShrunk2)
+        .addKeepMainRule(App.class)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(App.AppEnum.class))
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::assertUtilityClassPresent)
+        .run(parameters.getRuntime(), App.class)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   private Path compileLibrary(Class<?> libClass, Class<?> enumLibClass) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EmptyEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EmptyEnumUnboxingTest.java
index ddc0806..34a9d32 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EmptyEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EmptyEnumUnboxingTest.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import org.junit.Test;
@@ -35,24 +34,20 @@
 
   @Test
   public void testEnumUnboxing() throws Exception {
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(EmptyEnumUnboxingTest.class)
-            .addKeepMainRule(Main.class)
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .enableNeverClassInliningAnnotations()
-            .enableInliningAnnotations()
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspectDiagnosticMessages(
-                m ->
-                    // TODO(b/166532373): Unbox enum with no cases.
-                    assertEnumIsBoxed(MyEnum.class, Main.class.getSimpleName(), m))
-            .run(parameters.getRuntime(), Main.class)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(EmptyEnumUnboxingTest.class)
+        .addKeepMainRule(Main.class)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        // TODO(b/166532373): Unbox enum with no cases.
+        .addEnumUnboxingInspector(inspector -> inspector.assertNotUnboxed(MyEnum.class))
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumMissingFieldsUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumMissingFieldsUnboxingTest.java
index f6b55cf..e46a48c 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumMissingFieldsUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumMissingFieldsUnboxingTest.java
@@ -7,7 +7,6 @@
 import static org.hamcrest.CoreMatchers.containsString;
 
 import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.io.IOException;
 import java.util.List;
@@ -37,22 +36,19 @@
 
   @Test
   public void testEnumUnboxing() throws Exception {
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addProgramClasses(TestClass.class)
-            .addProgramClassFileData(getEnumProgramData())
-            .addKeepMainRule(TestClass.class)
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .enableNeverClassInliningAnnotations()
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspectDiagnosticMessages(
-                m -> assertEnumIsBoxed(CompilationEnum.class, TestClass.class.getSimpleName(), m))
-            .run(parameters.getRuntime(), TestClass.class)
-            .assertFailureWithErrorThatMatches(containsString("NoSuchFieldError"));
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addProgramClassFileData(getEnumProgramData())
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(inspector -> inspector.assertNotUnboxed(CompilationEnum.class))
+        .enableNeverClassInliningAnnotations()
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatMatches(containsString("NoSuchFieldError"))
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   private byte[] getEnumProgramData() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
index b21cbd0..0d7a5dfc 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
@@ -70,9 +70,7 @@
                   options.enableEnumUnboxing = enumUnboxing;
                   options.enableEnumValueOptimization = enumValueOptimization;
                   options.enableEnumSwitchMapRemoval = enumValueOptimization;
-                  options.testing.enableEnumUnboxingDebugLogs = enumUnboxing;
                 })
-            .allowDiagnosticInfoMessages(enumUnboxing)
             .setMinApi(parameters.getApiLevel())
             .compile();
       compile
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java
index d4b73ff..4776d5f 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import org.junit.Test;
@@ -18,12 +17,12 @@
 @RunWith(Parameterized.class)
 public class EnumUnboxingArrayTest extends EnumUnboxingTestBase {
 
-  private static final Class<?>[] SUCCESSES = {
-    EnumVarArgs.class,
-    EnumArrayReadWriteNoEscape.class,
-    EnumArrayReadWrite.class,
+  private static final Class<?>[] TESTS = {
+    Enum2DimArrayReadWrite.class,
     EnumArrayNullRead.class,
-    Enum2DimArrayReadWrite.class
+    EnumArrayReadWrite.class,
+    EnumArrayReadWriteNoEscape.class,
+    EnumVarArgs.class,
   };
 
   private final TestParameters parameters;
@@ -47,24 +46,26 @@
     R8TestCompileResult compile =
         testForR8(parameters.getBackend())
             .addInnerClasses(EnumUnboxingArrayTest.class)
-            .addKeepMainRules(SUCCESSES)
+            .addKeepMainRules(TESTS)
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
             .addKeepRules(enumKeepRules.getKeepRules())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
+            .addEnumUnboxingInspector(
+                inspector ->
+                    inspector.assertUnboxed(
+                        Enum2DimArrayReadWrite.MyEnum.class,
+                        EnumArrayNullRead.MyEnum.class,
+                        EnumArrayReadWrite.MyEnum.class,
+                        EnumArrayReadWriteNoEscape.MyEnum.class,
+                        EnumVarArgs.MyEnum.class))
             .setMinApi(parameters.getApiLevel())
             .compile();
-    for (Class<?> success : SUCCESSES) {
-      R8TestRunResult run =
-          compile
-              .inspectDiagnosticMessages(
-                  m ->
-                      assertEnumIsUnboxed(
-                          success.getDeclaredClasses()[0], success.getSimpleName(), m))
-              .run(parameters.getRuntime(), success)
-              .assertSuccess();
-      assertLines2By2Correct(run.getStdOut());
+    for (Class<?> main : TESTS) {
+      compile
+          .run(parameters.getRuntime(), main)
+          .assertSuccess()
+          .inspectStdOut(this::assertLines2By2Correct);
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java
index 590cc7c..a8fe3f9 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java
@@ -7,7 +7,6 @@
 import static org.hamcrest.core.StringContains.containsString;
 
 import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.nio.file.Path;
@@ -50,18 +49,14 @@
             .addProgramFiles(javaLibShrunk)
             .addKeepMainRules(ProgramValueOf.class, ProgramStaticMethod.class)
             .addKeepRules(enumKeepRules.getKeepRules())
-            .addOptionsModification(
-                options -> {
-                  assert options.enableEnumUnboxing;
-                  options.testing.enableEnumUnboxingDebugLogs = true;
-                })
+            .addEnumUnboxingInspector(
+                inspector ->
+                    inspector
+                        .assertUnboxed(Lib.LibEnum.class)
+                        .assertUnboxedIf(!missingStaticMethods, Lib.LibEnumStaticMethod.class))
             .allowDiagnosticMessages()
             .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspectDiagnosticMessages(
-                // The enums cannot be unboxed if static methods are missing,
-                // but they should be unboxed otherwise.
-                this::assertEnumUnboxedIfStaticMethodsPresent);
+            .compile();
     if (missingStaticMethods) {
       compile
           .run(parameters.getRuntime(), ProgramStaticMethod.class)
@@ -92,36 +87,19 @@
         .addKeepRules(missingStaticMethods ? "" : "-keep enum * { static <methods>; }")
         .addOptionsModification(
             options -> {
+              assert !options.enableEnumUnboxing;
               options.enableEnumUnboxing = true;
-              options.testing.enableEnumUnboxingDebugLogs = true;
             })
         .addKeepClassRules(Lib.LibEnumStaticMethod.class)
+        .addEnumUnboxingInspector(
+            inspector ->
+                inspector.assertNotUnboxed(Lib.LibEnum.class, Lib.LibEnumStaticMethod.class))
         .allowDiagnosticMessages()
         .setMinApi(parameters.getApiLevel())
         .compile()
-        .inspectDiagnosticMessages(
-            msg -> {
-              assertEnumIsBoxed(
-                  Lib.LibEnumStaticMethod.class,
-                  Lib.LibEnumStaticMethod.class.getSimpleName(),
-                  msg);
-              assertEnumIsBoxed(Lib.LibEnum.class, Lib.LibEnum.class.getSimpleName(), msg);
-            })
         .writeToZip();
   }
 
-  private void assertEnumUnboxedIfStaticMethodsPresent(TestDiagnosticMessages msg) {
-    if (missingStaticMethods) {
-      assertEnumIsBoxed(
-          Lib.LibEnumStaticMethod.class, Lib.LibEnumStaticMethod.class.getSimpleName(), msg);
-      assertEnumIsBoxed(Lib.LibEnum.class, Lib.LibEnum.class.getSimpleName(), msg);
-    } else {
-      assertEnumIsUnboxed(
-          Lib.LibEnumStaticMethod.class, Lib.LibEnumStaticMethod.class.getSimpleName(), msg);
-      assertEnumIsUnboxed(Lib.LibEnum.class, Lib.LibEnum.class.getSimpleName(), msg);
-    }
-  }
-
   public static class Lib {
 
     public enum LibEnumStaticMethod {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
index 750c6fe..c531710 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
@@ -10,7 +10,6 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -40,28 +39,21 @@
 
   @Test
   public void testEnumUnboxing() throws Exception {
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(EnumUnboxingClassStaticizerTest.class)
-            .addKeepMainRule(TestClass.class)
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .enableNeverClassInliningAnnotations()
-            .enableInliningAnnotations()
-            .noMinification() // For assertions.
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspect(this::assertClassStaticized)
-            .inspectDiagnosticMessages(
-                m ->
-                    assertEnumIsUnboxed(
-                        UnboxableEnum.class,
-                        EnumUnboxingClassStaticizerTest.class.getSimpleName(),
-                        m))
-            .run(parameters.getRuntime(), TestClass.class)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(EnumUnboxingClassStaticizerTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(UnboxableEnum.class))
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .noMinification() // For assertions.
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::assertClassStaticized)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   private void assertClassStaticized(CodeInspector codeInspector) {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingReturnNullTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingReturnNullTest.java
index a0173b5..392807c 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingReturnNullTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingReturnNullTest.java
@@ -16,7 +16,7 @@
 @RunWith(Parameterized.class)
 public class EnumUnboxingReturnNullTest extends EnumUnboxingTestBase {
 
-  private static final Class<?> ENUM_CLASS = MyEnum.class;
+  private static final Class<MyEnum> ENUM_CLASS = MyEnum.class;
   private static final String EXPECTED_RESULT =
       StringUtils.lines(
           "print1", "true", "print2", "true", "print2", "false", "0", "print3", "true");
@@ -44,14 +44,12 @@
         .addProgramClasses(classToTest, ENUM_CLASS)
         .addKeepMainRule(classToTest)
         .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(ENUM_CLASS))
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-        .allowDiagnosticInfoMessages()
         .setMinApi(parameters.getApiLevel())
         .compile()
-        .inspectDiagnosticMessages(
-            m -> assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m))
         .run(parameters.getRuntime(), classToTest)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingSideEffectClInitTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingSideEffectClInitTest.java
index 89621a2..3475ecf 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingSideEffectClInitTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingSideEffectClInitTest.java
@@ -14,7 +14,7 @@
 
 @RunWith(Parameterized.class)
 public class EnumUnboxingSideEffectClInitTest extends EnumUnboxingTestBase {
-  private static final Class<?> ENUM_CLASS = MyEnum.class;
+  private static final Class<MyEnum> ENUM_CLASS = MyEnum.class;
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
   private final EnumKeepRules enumKeepRules;
@@ -38,13 +38,11 @@
         .addInnerClasses(EnumUnboxingSideEffectClInitTest.class)
         .addKeepMainRule(classToTest)
         .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(ENUM_CLASS))
         .enableNeverClassInliningAnnotations()
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-        .allowDiagnosticInfoMessages()
         .setMinApi(parameters.getApiLevel())
         .compile()
-        .inspectDiagnosticMessages(
-            m -> assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m))
         .run(parameters.getRuntime(), classToTest)
         .assertSuccessWithOutputLines("0");
   }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
index c494152..c36733b 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
@@ -5,11 +5,8 @@
 package com.android.tools.r8.enumunboxing;
 
 import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertTrue;
 
-import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -35,6 +32,10 @@
       return keepRules;
     }
 
+    public boolean isNone() {
+      return this == NONE;
+    }
+
     public boolean isStudio() {
       return this == STUDIO;
     }
@@ -58,32 +59,6 @@
   protected void enableEnumOptions(InternalOptions options, boolean enumValueOptimization) {
     options.enableEnumValueOptimization = enumValueOptimization;
     options.enableEnumSwitchMapRemoval = enumValueOptimization;
-    options.testing.enableEnumUnboxingDebugLogs = true;
-  }
-
-  protected void assertEnumIsUnboxed(
-      Class<?> enumClass, String testName, TestDiagnosticMessages m) {
-    assertTrue(enumClass.isEnum());
-    assertEnumIsUnboxed(enumClass.getSimpleName(), testName, m);
-  }
-
-  protected void assertEnumIsUnboxed(String enumClass, String testName, TestDiagnosticMessages m) {
-    Diagnostic diagnostic = m.getInfos().get(0);
-    assertTrue(diagnostic.getDiagnosticMessage().startsWith("Unboxed enums"));
-    assertTrue(
-        StringUtils.joinLines(
-            "Expected enum to be removed (" + testName + "):",
-            m.getInfos().get(1).getDiagnosticMessage()),
-        diagnostic.getDiagnosticMessage().contains(enumClass));
-  }
-
-  void assertEnumIsBoxed(Class<?> enumClass, String testName, TestDiagnosticMessages m) {
-    assertTrue(enumClass.isEnum());
-    Diagnostic diagnostic = m.getInfos().get(1);
-    assertTrue(diagnostic.getDiagnosticMessage().startsWith("Boxed enums"));
-    assertTrue(
-        "Expected enum NOT to be removed (" + testName + ")",
-        diagnostic.getDiagnosticMessage().contains(enumClass.getSimpleName()));
   }
 
   static List<Object[]> enumUnboxingTestParameters() {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingVerticalClassMergeTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingVerticalClassMergeTest.java
index d8396cb..f240835 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingVerticalClassMergeTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingVerticalClassMergeTest.java
@@ -8,7 +8,6 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.List;
@@ -37,28 +36,21 @@
 
   @Test
   public void testEnumUnboxing() throws Exception {
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(EnumUnboxingVerticalClassMergeTest.class)
-            .addKeepMainRule(Main.class)
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .enableNeverClassInliningAnnotations()
-            .enableInliningAnnotations()
-            .noMinification() // For assertions.
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspect(this::assertVerticalClassMerged)
-            .inspectDiagnosticMessages(
-                m ->
-                    assertEnumIsUnboxed(
-                        UnboxableEnum.class,
-                        EnumUnboxingVerticalClassMergeTest.class.getSimpleName(),
-                        m))
-            .run(parameters.getRuntime(), Main.class)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(EnumUnboxingVerticalClassMergeTest.class)
+        .addKeepMainRule(Main.class)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(UnboxableEnum.class))
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .noMinification() // For assertions.
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::assertVerticalClassMerged)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   private void assertVerticalClassMerged(CodeInspector codeInspector) {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EqualsCompareToEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EqualsCompareToEnumUnboxingTest.java
index 2888ee4..4e38f3f 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EqualsCompareToEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EqualsCompareToEnumUnboxingTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.enumunboxing;
 
 import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import org.junit.Test;
@@ -35,23 +34,19 @@
   @Test
   public void testEnumUnboxing() throws Exception {
     Class<?> success = EnumEqualscompareTo.class;
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(EqualsCompareToEnumUnboxingTest.class)
-            .addKeepMainRule(EnumEqualscompareTo.class)
-            .enableNeverClassInliningAnnotations()
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspectDiagnosticMessages(
-                m ->
-                    assertEnumIsUnboxed(
-                        success.getDeclaredClasses()[0], success.getSimpleName(), m))
-            .run(parameters.getRuntime(), success)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(EqualsCompareToEnumUnboxingTest.class)
+        .addKeepMainRule(EnumEqualscompareTo.class)
+        .addEnumUnboxingInspector(
+            inspector -> inspector.assertUnboxed(EnumEqualscompareTo.MyEnum.class))
+        .enableNeverClassInliningAnnotations()
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), success)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   static class EnumEqualscompareTo {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
index 8bf8034..e3df746 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.enumunboxing;
 
 import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import org.junit.Test;
@@ -34,25 +33,19 @@
 
   @Test
   public void testEnumUnboxingFailure() throws Exception {
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(FailingEnumUnboxingTest.class)
-            .addKeepMainRule(EnumStaticFieldMain.class)
-            .enableNeverClassInliningAnnotations()
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspectDiagnosticMessages(
-                m ->
-                    assertEnumIsBoxed(
-                        EnumStaticFieldMain.EnumStaticField.class,
-                        EnumStaticFieldMain.class.getSimpleName(),
-                        m))
-            .run(parameters.getRuntime(), EnumStaticFieldMain.class)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(FailingEnumUnboxingTest.class)
+        .addKeepMainRule(EnumStaticFieldMain.class)
+        .enableNeverClassInliningAnnotations()
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .addEnumUnboxingInspector(
+            inspector -> inspector.assertNotUnboxed(EnumStaticFieldMain.EnumStaticField.class))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), EnumStaticFieldMain.class)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   static class EnumStaticFieldMain {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
index 702ce3e..ce3e3f9 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.EnumSet;
@@ -21,7 +21,7 @@
 
 @RunWith(Parameterized.class)
 public class FailingMethodEnumUnboxingTest extends EnumUnboxingTestBase {
-  private static final Class<?>[] FAILURES = {
+  private static final Class<?>[] TESTS = {
     InstanceFieldPutObject.class,
     StaticFieldPutObject.class,
     EnumSetTest.class,
@@ -50,30 +50,31 @@
     R8TestCompileResult compile =
         testForR8(parameters.getBackend())
             .addInnerClasses(FailingMethodEnumUnboxingTest.class)
-            .addKeepMainRules(FAILURES)
+            .addKeepMainRules(TESTS)
             .addKeepRules(enumKeepRules.getKeepRules())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
+            .addEnumUnboxingInspector(
+                inspector ->
+                    inspector.assertNotUnboxed(
+                        InstanceFieldPutObject.MyEnum.class,
+                        StaticFieldPutObject.MyEnum.class,
+                        EnumSetTest.MyEnum.class,
+                        FailingPhi.MyEnum.class,
+                        FailingReturnType.MyEnum.class,
+                        FailingParameterType.MyEnum.class))
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
             .setMinApi(parameters.getApiLevel())
             .compile()
             .inspect(this::assertEnumsAsExpected);
-    for (Class<?> failure : FAILURES) {
-      R8TestRunResult run =
-          compile
-              .inspectDiagnosticMessages(
-                  m ->
-                      assertEnumIsBoxed(
-                          failure.getDeclaredClasses()[0], failure.getSimpleName(), m))
-              .run(parameters.getRuntime(), failure);
-      if (failure == EnumSetTest.class && enumKeepRules.getKeepRules().isEmpty()) {
-        // EnumSet and EnumMap cannot be used without the enumKeepRules.
-        run.assertFailure();
-      } else {
-        run.assertSuccess();
-        assertLines2By2Correct(run.getStdOut());
-      }
+    for (Class<?> main : TESTS) {
+      compile
+          .run(parameters.getRuntime(), main)
+          .applyIf(
+              main == EnumSetTest.class && enumKeepRules.getKeepRules().isEmpty(),
+              // EnumSet and EnumMap cannot be used without the enumKeepRules.
+              SingleTestRunResult::assertFailure,
+              result -> result.assertSuccess().inspectStdOut(this::assertLines2By2Correct));
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
index 4d222d5..d7627a6 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import org.junit.Test;
@@ -47,7 +46,10 @@
             .addKeepMainRules(INPUTS)
             .addKeepRules(enumKeepRules.getKeepRules())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
+            .addEnumUnboxingInspector(
+                inspector ->
+                    inspector.assertUnboxed(
+                        InstanceFieldPut.MyEnum.class, StaticFieldPut.MyEnum.class))
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
             .setMinApi(parameters.getApiLevel())
@@ -62,14 +64,11 @@
                       1, i.clazz(StaticFieldPut.class).getDexProgramClass().staticFields().size());
                 });
 
-    for (Class<?> input : INPUTS) {
-      R8TestRunResult run =
-          compile
-              .inspectDiagnosticMessages(
-                  m -> assertEnumIsUnboxed(input.getDeclaredClasses()[0], input.getSimpleName(), m))
-              .run(parameters.getRuntime(), input)
-              .assertSuccess();
-      assertLines2By2Correct(run.getStdOut());
+    for (Class<?> main : INPUTS) {
+      compile
+          .run(parameters.getRuntime(), main)
+          .assertSuccess()
+          .inspectStdOut(this::assertLines2By2Correct);
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/InstanceFieldsEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/InstanceFieldsEnumUnboxingTest.java
index dc21961..e359c36 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/InstanceFieldsEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/InstanceFieldsEnumUnboxingTest.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import org.junit.Test;
@@ -18,15 +17,11 @@
 @RunWith(Parameterized.class)
 public class InstanceFieldsEnumUnboxingTest extends EnumUnboxingTestBase {
 
-  private static final Class<?>[] FAILURES = {
+  private static final Class<?>[] TESTS = {
     FailureIntField.class,
-    FailurePrivateIntField.class,
     FailureBoxedInnerEnumField.class,
     FailureUnboxedEnumField.class,
-    FailureTooManyUsedFields.class
-  };
-
-  private static final Class<?>[] SUCCESSES = {
+    FailureTooManyUsedFields.class,
     SuccessUnusedField.class,
     SuccessIntField.class,
     SuccessDoubleField.class,
@@ -34,6 +29,7 @@
     SuccessIntFieldInitializerInit.class,
     SuccessStringField.class,
     SuccessMultiConstructorIntField.class,
+    SuccessPrivateIntField.class,
   };
 
   private final TestParameters parameters;
@@ -57,44 +53,41 @@
     R8TestCompileResult compile =
         testForR8(parameters.getBackend())
             .addInnerClasses(InstanceFieldsEnumUnboxingTest.class)
-            .addKeepMainRules(SUCCESSES)
-            .addKeepMainRules(FAILURES)
+            .addKeepMainRules(TESTS)
+            .addEnumUnboxingInspector(
+                inspector ->
+                    inspector
+                        .assertUnboxed(
+                            SuccessUnusedField.EnumField.class,
+                            SuccessIntField.EnumField.class,
+                            SuccessDoubleField.EnumField.class,
+                            SuccessIntFieldOrdinal.EnumField.class,
+                            SuccessIntFieldInitializerInit.EnumField.class,
+                            SuccessStringField.EnumField.class,
+                            SuccessMultiConstructorIntField.EnumField.class,
+                            SuccessPrivateIntField.EnumField.class)
+                        .assertNotUnboxed(
+                            FailureIntField.EnumField.class,
+                            FailureBoxedInnerEnumField.EnumField.class,
+                            FailureUnboxedEnumField.EnumField.class,
+                            FailureTooManyUsedFields.EnumField.class))
             .noMinification()
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
             .addKeepRules(enumKeepRules.getKeepRules())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
             .compile();
-    for (Class<?> failure : FAILURES) {
-      testClass(compile, failure, true);
-    }
-    for (Class<?> success : SUCCESSES) {
-      testClass(compile, success, false);
+    for (Class<?> main : TESTS) {
+      testClass(compile, main);
     }
   }
 
-  private void testClass(R8TestCompileResult compile, Class<?> testClass, boolean failure)
-      throws Exception {
-    R8TestRunResult run =
-        compile
-            .inspectDiagnosticMessages(
-                m -> {
-                  for (Class<?> declaredClass : testClass.getDeclaredClasses()) {
-                    if (declaredClass.isEnum()
-                        && !declaredClass.getSimpleName().equals("InnerEnum")) {
-                      if (failure) {
-                        assertEnumIsBoxed(declaredClass, testClass.getSimpleName(), m);
-                      } else {
-                        assertEnumIsUnboxed(declaredClass, testClass.getSimpleName(), m);
-                      }
-                    }
-                  }
-                })
-            .run(parameters.getRuntime(), testClass)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+  private void testClass(R8TestCompileResult compile, Class<?> testClass) throws Exception {
+    compile
+        .run(parameters.getRuntime(), testClass)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   static class SuccessUnusedField {
@@ -161,7 +154,7 @@
     }
   }
 
-  static class FailurePrivateIntField {
+  static class SuccessPrivateIntField {
 
     public static void main(String[] args) {
       System.out.println(getEnumA().field);
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java
index 7311ab2..2f27855 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import org.junit.Test;
@@ -19,11 +18,9 @@
 @RunWith(Parameterized.class)
 public class InterfaceEnumUnboxingTest extends EnumUnboxingTestBase {
 
-  private static final Class<?>[] FAILURES = {
-    FailureDefaultMethodUsed.class, FailureUsedAsInterface.class,
-  };
-
-  private static final Class<?>[] SUCCESSES = {
+  private static final Class<?>[] TESTS = {
+    FailureDefaultMethodUsed.class,
+    FailureUsedAsInterface.class,
     SuccessAbstractMethod.class,
     SuccessEmptyInterface.class,
     SuccessUnusedDefaultMethod.class,
@@ -52,44 +49,37 @@
     R8TestCompileResult compile =
         testForR8(parameters.getBackend())
             .addInnerClasses(InterfaceEnumUnboxingTest.class)
-            .addKeepMainRules(SUCCESSES)
-            .addKeepMainRules(FAILURES)
+            .addKeepMainRules(TESTS)
+            .addEnumUnboxingInspector(
+                inspector ->
+                    inspector
+                        .assertUnboxed(
+                            SuccessAbstractMethod.EnumInterface.class,
+                            SuccessEmptyInterface.EnumInterface.class,
+                            SuccessUnusedDefaultMethod.EnumInterface.class,
+                            SuccessUnusedDefaultMethodOverride.EnumInterface.class,
+                            SuccessUnusedDefaultMethodOverrideEnum.EnumInterface.class)
+                        .assertNotUnboxed(
+                            FailureDefaultMethodUsed.EnumInterface.class,
+                            FailureUsedAsInterface.EnumInterface.class))
             .noMinification()
             .enableNoVerticalClassMergingAnnotations()
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
             .addKeepRules(enumKeepRules.getKeepRules())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
             .compile();
-    for (Class<?> failure : FAILURES) {
-      testClass(compile, failure, true);
-    }
-    for (Class<?> success : SUCCESSES) {
-      testClass(compile, success, false);
+    for (Class<?> main : TESTS) {
+      testClass(compile, main);
     }
   }
 
-  private void testClass(R8TestCompileResult compile, Class<?> testClass, boolean failure)
-      throws Exception {
-    R8TestRunResult run =
-        compile
-            .inspectDiagnosticMessages(
-                m -> {
-                  for (Class<?> declaredClass : testClass.getDeclaredClasses()) {
-                    if (declaredClass.isEnum()) {
-                      if (failure) {
-                        assertEnumIsBoxed(declaredClass, testClass.getSimpleName(), m);
-                      } else {
-                        assertEnumIsUnboxed(declaredClass, testClass.getSimpleName(), m);
-                      }
-                    }
-                  }
-                })
-            .run(parameters.getRuntime(), testClass)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+  private void testClass(R8TestCompileResult compile, Class<?> testClass) throws Exception {
+    compile
+        .run(parameters.getRuntime(), testClass)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   static class SuccessEmptyInterface {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/JavaCGeneratedMethodTest.java b/src/test/java/com/android/tools/r8/enumunboxing/JavaCGeneratedMethodTest.java
index 5d4501f..aaf0239 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/JavaCGeneratedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/JavaCGeneratedMethodTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.enumunboxing;
 
 import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import org.junit.Test;
@@ -15,7 +14,7 @@
 @RunWith(Parameterized.class)
 public class JavaCGeneratedMethodTest extends EnumUnboxingTestBase {
 
-  private static final Class<?> ENUM_CLASS = MyEnum.class;
+  private static final Class<MyEnum> ENUM_CLASS = MyEnum.class;
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
@@ -36,23 +35,19 @@
   @Test
   public void testEnumUnboxing() throws Exception {
     Class<?> classToTest = Ordinal.class;
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addProgramClasses(classToTest, ENUM_CLASS)
-            .addKeepMainRule(classToTest)
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .enableNeverClassInliningAnnotations()
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .addOptionsModification(
-                opt -> opt.testing.enumUnboxingRewriteJavaCGeneratedMethod = true)
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspectDiagnosticMessages(
-                m -> assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m))
-            .run(parameters.getRuntime(), classToTest)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addProgramClasses(classToTest, ENUM_CLASS)
+        .addKeepMainRule(classToTest)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(ENUM_CLASS))
+        .enableNeverClassInliningAnnotations()
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .addOptionsModification(opt -> opt.testing.enumUnboxingRewriteJavaCGeneratedMethod = true)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), classToTest)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/LambdaEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/LambdaEnumUnboxingTest.java
index d6eb6d1..aba1bb8 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/LambdaEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/LambdaEnumUnboxingTest.java
@@ -51,7 +51,6 @@
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-        .addOptionsModification(options -> options.testing.enableEnumUnboxingDebugLogs = false)
         .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
         .setMinApi(parameters.getApiLevel())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/LargeEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/LargeEnumUnboxingTest.java
index 66c861a..ca48de2 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/LargeEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/LargeEnumUnboxingTest.java
@@ -97,13 +97,11 @@
         .addProgramClasses(mainClass, LargeEnum.class)
         .addKeepMainRule(mainClass)
         .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(LargeEnum.class))
         .enableNeverClassInliningAnnotations()
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-        .allowDiagnosticInfoMessages()
         .setMinApi(parameters.getApiLevel())
         .compile()
-        .inspectDiagnosticMessages(
-            m -> assertEnumIsUnboxed(LargeEnum.class, mainClass.getSimpleName(), m))
         .run(parameters.getRuntime(), mainClass)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/NullCheckEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/NullCheckEnumUnboxingTest.java
index 0f313cf..e8cc088 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/NullCheckEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/NullCheckEnumUnboxingTest.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import java.util.Objects;
@@ -36,28 +35,24 @@
 
   @Test
   public void testEnumUnboxing() throws Exception {
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(NullCheckEnumUnboxingTest.class)
-            .addKeepMainRule(MainNullTest.class)
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .enableNeverClassInliningAnnotations()
-            .enableInliningAnnotations()
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspectDiagnosticMessages(
-                m -> {
-                  assertEnumIsUnboxed(MyEnum.class, MyEnum.class.getSimpleName(), m);
-                  // MyEnum19 is always unboxed. If minAPI > 19 the unboxer will identify
-                  // Objects#requiredNonNull usage. For 19 and prior, the backport code should not
-                  // prohibit the unboxing either.
-                  assertEnumIsUnboxed(MyEnum19.class, MyEnum19.class.getSimpleName(), m);
-                })
-            .run(parameters.getRuntime(), MainNullTest.class)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(NullCheckEnumUnboxingTest.class)
+        .addKeepMainRule(MainNullTest.class)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        // MyEnum19 is always unboxed. If minAPI > 19 the unboxer will identify
+        // Objects#requiredNonNull usage. For 19 and prior, the backport code should not
+        // prohibit the unboxing either.
+        .addEnumUnboxingInspector(
+            inspector -> inspector.assertUnboxed(MyEnum.class, MyEnum19.class))
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .allowDiagnosticMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), MainNullTest.class)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/NullOutValueInvokeEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/NullOutValueInvokeEnumUnboxingTest.java
index 8e15ad1..80eb9ab 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/NullOutValueInvokeEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/NullOutValueInvokeEnumUnboxingTest.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import org.junit.Test;
@@ -17,7 +16,7 @@
 @RunWith(Parameterized.class)
 public class NullOutValueInvokeEnumUnboxingTest extends EnumUnboxingTestBase {
 
-  private static final Class<?> ENUM_CLASS = MyEnum.class;
+  private static final Class<MyEnum> ENUM_CLASS = MyEnum.class;
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
@@ -38,22 +37,19 @@
   @Test
   public void testEnumUnboxing() throws Exception {
     Class<?> classToTest = NullOutValueInvoke.class;
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addProgramClasses(classToTest, ENUM_CLASS)
-            .addKeepMainRule(classToTest)
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .enableNeverClassInliningAnnotations()
-            .enableInliningAnnotations()
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspectDiagnosticMessages(
-                m -> assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m))
-            .run(parameters.getRuntime(), classToTest)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addProgramClasses(classToTest, ENUM_CLASS)
+        .addKeepMainRule(classToTest)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(ENUM_CLASS))
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), classToTest)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java
index 1946116..49b4f76 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import org.junit.Test;
@@ -17,7 +16,7 @@
 @RunWith(Parameterized.class)
 public class OrdinalHashCodeEnumUnboxingTest extends EnumUnboxingTestBase {
 
-  private static final Class<?> ENUM_CLASS = MyEnum.class;
+  private static final Class<MyEnum> ENUM_CLASS = MyEnum.class;
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
@@ -38,22 +37,19 @@
   @Test
   public void testEnumUnboxing() throws Exception {
     Class<?> classToTest = OrdinalHashCode.class;
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addProgramClasses(classToTest, ENUM_CLASS)
-            .addKeepMainRule(classToTest)
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .enableNeverClassInliningAnnotations()
-            .enableInliningAnnotations()
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspectDiagnosticMessages(
-                m -> assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m))
-            .run(parameters.getRuntime(), classToTest)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addProgramClasses(classToTest, ENUM_CLASS)
+        .addKeepMainRule(classToTest)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(ENUM_CLASS))
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), classToTest)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java
index bf038fa..5df98f1 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import org.junit.Test;
@@ -36,25 +35,20 @@
   @Test
   public void testEnumUnboxing() throws Exception {
     Class<?> classToTest = TestClass.class;
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(OverloadingEnumUnboxingTest.class)
-            .addKeepMainRule(classToTest)
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .enableNeverClassInliningAnnotations()
-            .enableInliningAnnotations()
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspectDiagnosticMessages(
-                m -> {
-                  assertEnumIsUnboxed(MyEnum1.class, MyEnum1.class.getSimpleName(), m);
-                  assertEnumIsUnboxed(MyEnum2.class, MyEnum2.class.getSimpleName(), m);
-                })
-            .run(parameters.getRuntime(), classToTest)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(OverloadingEnumUnboxingTest.class)
+        .addKeepMainRule(classToTest)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(
+            inspector -> inspector.assertUnboxed(MyEnum1.class, MyEnum2.class))
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), classToTest)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   @SuppressWarnings("SameParameterValue")
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java
index d2851c1..536d066 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import org.junit.Test;
@@ -35,22 +34,19 @@
 
   @Test
   public void testEnumUnboxing() throws Exception {
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addProgramClasses(Phi.class, MyEnum.class)
-            .addKeepMainRule(Phi.class)
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .enableInliningAnnotations()
-            .enableNeverClassInliningAnnotations()
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspectDiagnosticMessages(
-                m -> assertEnumIsUnboxed(MyEnum.class, Phi.class.getSimpleName(), m))
-            .run(parameters.getRuntime(), Phi.class)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Phi.class, MyEnum.class)
+        .addKeepMainRule(Phi.class)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Phi.class)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/PinnedEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/PinnedEnumUnboxingTest.java
index 1c34891..6391a99 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/PinnedEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/PinnedEnumUnboxingTest.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.enumunboxing.PinnedEnumUnboxingTest.MainWithKeptEnum.MyEnum;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -16,7 +17,7 @@
 @RunWith(Parameterized.class)
 public class PinnedEnumUnboxingTest extends EnumUnboxingTestBase {
 
-  private static final Class<?>[] BOXED = {MainWithKeptEnum.class, MainWithKeptEnumArray.class};
+  private static final Class<?>[] TESTS = {MainWithKeptEnum.class, MainWithKeptEnumArray.class};
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
@@ -39,21 +40,20 @@
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
             .addInnerClasses(PinnedEnumUnboxingTest.class)
-            .addKeepMainRules(BOXED)
-            .addKeepClassRules(MainWithKeptEnum.MyEnum.class)
+            .addKeepMainRules(TESTS)
+            .addKeepClassRules(MyEnum.class)
             .addKeepMethodRules(MainWithKeptEnumArray.class, "keptMethod()")
             .addKeepRules(enumKeepRules.getKeepRules())
+            .addEnumUnboxingInspector(
+                inspector ->
+                    inspector.assertNotUnboxed(
+                        MainWithKeptEnum.MyEnum.class, MainWithKeptEnumArray.MyEnum.class))
             .enableNeverClassInliningAnnotations()
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
             .compile();
-    for (Class<?> boxed : BOXED) {
-      compileResult
-          .inspectDiagnosticMessages(
-              m -> assertEnumIsBoxed(boxed.getDeclaredClasses()[0], boxed.getSimpleName(), m))
-          .run(parameters.getRuntime(), boxed)
-          .assertSuccessWithOutputLines("0");
+    for (Class<?> main : TESTS) {
+      compileResult.run(parameters.getRuntime(), main).assertSuccessWithOutputLines("0");
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/StaticMethodsEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/StaticMethodsEnumUnboxingTest.java
index 390fe18..43be887 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/StaticMethodsEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/StaticMethodsEnumUnboxingTest.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import org.junit.Test;
@@ -36,25 +35,19 @@
   @Test
   public void testEnumUnboxing() throws Exception {
     Class<?> classToTest = StaticMethods.class;
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(StaticMethodsEnumUnboxingTest.class)
-            .addKeepMainRule(classToTest)
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .enableNeverClassInliningAnnotations()
-            .enableInliningAnnotations()
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspectDiagnosticMessages(
-                m -> {
-                  assertEnumIsUnboxed(MyEnum.class, classToTest.getSimpleName(), m);
-                  assertEnumIsUnboxed(MyEnum2.class, classToTest.getSimpleName(), m);
-                })
-            .run(parameters.getRuntime(), classToTest)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(StaticMethodsEnumUnboxingTest.class)
+        .addKeepMainRule(classToTest)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class, MyEnum2.class))
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), classToTest)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   @SuppressWarnings("SameParameterValue")
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 1ab86b3..904fc48f 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/StringValueOfEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/StringValueOfEnumUnboxingTest.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import org.junit.Test;
@@ -34,22 +33,19 @@
   @Test
   public void testEnumUnboxing() throws Exception {
     Class<?> classToTest = Main.class;
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addProgramClasses(classToTest, MyEnum.class)
-            .addKeepMainRule(classToTest)
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .enableNeverClassInliningAnnotations()
-            .enableInliningAnnotations()
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspectDiagnosticMessages(
-                m -> assertEnumIsUnboxed(MyEnum.class, classToTest.getSimpleName(), m))
-            .run(parameters.getRuntime(), classToTest)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addProgramClasses(classToTest, MyEnum.class)
+        .addKeepMainRule(classToTest)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), classToTest)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
index f58a069..43dec5d 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
@@ -9,7 +9,6 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -22,7 +21,7 @@
 @RunWith(Parameterized.class)
 public class SwitchEnumUnboxingTest extends EnumUnboxingTestBase {
 
-  private static final Class<?> ENUM_CLASS = MyEnumFewCases.class;
+  private static final Class<MyEnumFewCases> ENUM_CLASS = MyEnumFewCases.class;
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
@@ -43,24 +42,21 @@
   @Test
   public void testEnumUnboxing() throws Exception {
     Class<Switch> classToTest = Switch.class;
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(SwitchEnumUnboxingTest.class)
-            .addKeepMainRule(classToTest)
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .enableInliningAnnotations()
-            .enableNeverClassInliningAnnotations()
-            .noMinification() // For assertions.
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspect(this::assertSwitchPresentButSwitchMapRemoved)
-            .inspectDiagnosticMessages(
-                m -> assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m))
-            .run(parameters.getRuntime(), classToTest)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(SwitchEnumUnboxingTest.class)
+        .addKeepMainRule(classToTest)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(ENUM_CLASS))
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .noMinification() // For assertions.
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::assertSwitchPresentButSwitchMapRemoved)
+        .run(parameters.getRuntime(), classToTest)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   private void assertSwitchPresentButSwitchMapRemoved(CodeInspector i) {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ToStringEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ToStringEnumUnboxingTest.java
index 50a4d58..99e4e25 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ToStringEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ToStringEnumUnboxingTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.enumunboxing;
 
 import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import org.junit.Test;
@@ -35,23 +34,19 @@
   @Test
   public void testEnumUnboxing() throws Exception {
     Class<?> success = EnumNameToString.class;
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(ToStringEnumUnboxingTest.class)
-            .addKeepMainRule(EnumNameToString.class)
-            .enableNeverClassInliningAnnotations()
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspectDiagnosticMessages(
-                m ->
-                    assertEnumIsUnboxed(
-                        success.getDeclaredClasses()[0], success.getSimpleName(), m))
-            .run(parameters.getRuntime(), success)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ToStringEnumUnboxingTest.class)
+        .addKeepMainRule(EnumNameToString.class)
+        .addEnumUnboxingInspector(
+            inspector -> inspector.assertUnboxed(EnumNameToString.MyEnum.class))
+        .enableNeverClassInliningAnnotations()
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), success)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   static class EnumNameToString {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ToStringOverrideEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ToStringOverrideEnumUnboxingTest.java
index 8b9b546..d8d115c 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ToStringOverrideEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ToStringOverrideEnumUnboxingTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.enumunboxing;
 
 import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import org.junit.Test;
@@ -35,21 +34,19 @@
   @Test
   public void testEnumUnboxing() throws Exception {
     Class<?> success = EnumNameToString.class;
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(ToStringOverrideEnumUnboxingTest.class)
-            .addKeepMainRule(EnumNameToString.class)
-            .enableNeverClassInliningAnnotations()
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspectDiagnosticMessages(
-                m -> assertEnumIsBoxed(success.getDeclaredClasses()[0], success.getSimpleName(), m))
-            .run(parameters.getRuntime(), success)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ToStringOverrideEnumUnboxingTest.class)
+        .addKeepMainRule(EnumNameToString.class)
+        .enableNeverClassInliningAnnotations()
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .addEnumUnboxingInspector(
+            inspector -> inspector.assertNotUnboxed(EnumNameToString.MyEnum.class))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), success)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   static class EnumNameToString {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/UnusedCaseEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/UnusedCaseEnumUnboxingTest.java
index a6b7027..6a35396 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/UnusedCaseEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/UnusedCaseEnumUnboxingTest.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.List;
@@ -34,23 +33,20 @@
 
   @Test
   public void testEnumUnboxing() throws Exception {
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(UnusedCaseEnumUnboxingTest.class)
-            .addKeepMainRule(Main.class)
-            .enableNeverClassInliningAnnotations()
-            .enableInliningAnnotations()
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspect(this::assertFieldsRemoved)
-            .inspectDiagnosticMessages(
-                m -> assertEnumIsUnboxed(MyEnum.class, Main.class.getSimpleName(), m))
-            .run(parameters.getRuntime(), Main.class)
-            .assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(UnusedCaseEnumUnboxingTest.class)
+        .addKeepMainRule(Main.class)
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::assertFieldsRemoved)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   private void assertFieldsRemoved(CodeInspector codeInspector) {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingFailureTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingFailureTest.java
index 1da8c62..15bdcb7 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingFailureTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingFailureTest.java
@@ -36,14 +36,12 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(ValueOfEnumUnboxingFailureTest.class)
         .addKeepMainRule(success)
+        .addEnumUnboxingInspector(inspector -> inspector.assertNotUnboxed(Main.Enum.class))
         .enableNeverClassInliningAnnotations()
         .addKeepRules(enumKeepRules.getKeepRules())
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-        .allowDiagnosticInfoMessages()
         .setMinApi(parameters.getApiLevel())
         .compile()
-        .inspectDiagnosticMessages(
-            m -> assertEnumIsBoxed(success.getDeclaredClasses()[0], success.getSimpleName(), m))
         .run(parameters.getRuntime(), success)
         .assertSuccessWithOutput("VALUE1");
   }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java
index 23293f5..eabf368 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import org.junit.Test;
@@ -17,7 +16,7 @@
 @RunWith(Parameterized.class)
 public class ValueOfEnumUnboxingTest extends EnumUnboxingTestBase {
 
-  private static final Class<?>[] SUCCESSES = {
+  private static final Class<?>[] TESTS = {
     EnumValueOf.class,
   };
 
@@ -42,23 +41,19 @@
     R8TestCompileResult compile =
         testForR8(parameters.getBackend())
             .addInnerClasses(ValueOfEnumUnboxingTest.class)
-            .addKeepMainRules(SUCCESSES)
+            .addKeepMainRules(TESTS)
+            .addEnumUnboxingInspector(
+                inspector -> inspector.assertUnboxed(EnumValueOf.MyEnum.class))
             .enableNeverClassInliningAnnotations()
             .addKeepRules(enumKeepRules.getKeepRules())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
             .compile();
-    for (Class<?> success : SUCCESSES) {
-      R8TestRunResult run =
-          compile
-              .inspectDiagnosticMessages(
-                  m ->
-                      assertEnumIsUnboxed(
-                          success.getDeclaredClasses()[0], success.getSimpleName(), m))
-              .run(parameters.getRuntime(), success)
-              .assertSuccess();
-      assertLines2By2Correct(run.getStdOut());
+    for (Class<?> main : TESTS) {
+      compile
+          .run(parameters.getRuntime(), main)
+          .assertSuccess()
+          .inspectStdOut(this::assertLines2By2Correct);
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java
index 6020f1a..4c69310 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java
@@ -49,7 +49,6 @@
         .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .addOptionsModification(options -> enableEnumOptions(options, enumValueOptimization))
-        .addOptionsModification(options -> options.testing.enableEnumUnboxingDebugLogs = false)
         .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
         .setMinApi(parameters.getApiLevel())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsAccessibilityErrorEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsAccessibilityErrorEnumUnboxingTest.java
index 52d8688..03e6de9 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsAccessibilityErrorEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsAccessibilityErrorEnumUnboxingTest.java
@@ -67,7 +67,6 @@
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-        .allowDiagnosticInfoMessages()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
index 3a1fbab..c24a4d9 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
@@ -5,8 +5,6 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
 import org.junit.Test;
@@ -35,33 +33,28 @@
   @Test
   public void testEnumUnboxing() throws Exception {
     Class<?> classToTest = VirtualMethods.class;
-    R8TestCompileResult compile =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(VirtualMethodsEnumUnboxingTest.class)
-            .addKeepMainRule(classToTest)
-            .addKeepRules(enumKeepRules.getKeepRules())
-            .enableNeverClassInliningAnnotations()
-            .enableInliningAnnotations()
-            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-            .allowDiagnosticInfoMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspectDiagnosticMessages(
-                m -> {
-                  assertEnumIsUnboxed(MyEnum.class, classToTest.getSimpleName(), m);
-                  assertEnumIsUnboxed(MyEnum2.class, classToTest.getSimpleName(), m);
-                  assertEnumIsUnboxed(MyEnumWithCollisions.class, classToTest.getSimpleName(), m);
-                  assertEnumIsUnboxed(
-                      MyEnumWithPackagePrivateCall.class, classToTest.getSimpleName(), m);
-                  assertEnumIsUnboxed(
-                      MyEnumWithProtectedCall.class, classToTest.getSimpleName(), m);
-                  assertEnumIsUnboxed(
-                      MyEnumWithPackagePrivateFieldAccess.class, classToTest.getSimpleName(), m);
-                  assertEnumIsUnboxed(
-                      MyEnumWithPackagePrivateAndPrivateCall.class, classToTest.getSimpleName(), m);
-                });
-    R8TestRunResult run = compile.run(parameters.getRuntime(), classToTest).assertSuccess();
-    assertLines2By2Correct(run.getStdOut());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(VirtualMethodsEnumUnboxingTest.class)
+        .addKeepMainRule(classToTest)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(
+            inspector ->
+                inspector.assertUnboxed(
+                    MyEnum.class,
+                    MyEnum2.class,
+                    MyEnumWithCollisions.class,
+                    MyEnumWithPackagePrivateCall.class,
+                    MyEnumWithProtectedCall.class,
+                    MyEnumWithPackagePrivateFieldAccess.class,
+                    MyEnumWithPackagePrivateAndPrivateCall.class))
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), classToTest)
+        .assertSuccess()
+        .inspectStdOut(this::assertLines2By2Correct);
   }
 
   @SuppressWarnings("SameParameterValue")
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/kotlin/SimpleKotlinEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/kotlin/SimpleKotlinEnumUnboxingTest.java
index 6f4b565..2d89396 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/kotlin/SimpleKotlinEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/kotlin/SimpleKotlinEnumUnboxingTest.java
@@ -71,19 +71,17 @@
         .addKeepRules(enumKeepRules.getKeepRules())
         .addKeepRuntimeVisibleAnnotations()
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(PKG + ".Color"))
         .allowDiagnosticMessages()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspectDiagnosticMessages(
-            messages -> {
-              messages
-                  .assertNoErrors()
-                  .assertAllWarningsMatch(
-                      diagnosticMessage(
-                          containsString("Resource 'META-INF/MANIFEST.MF' already exists.")));
-              assertEnumIsUnboxed(
-                  PKG + ".Color", SimpleKotlinEnumUnboxingTest.class.getSimpleName(), messages);
-            })
+            messages ->
+                messages
+                    .assertNoErrors()
+                    .assertAllWarningsMatch(
+                        diagnosticMessage(
+                            containsString("Resource 'META-INF/MANIFEST.MF' already exists."))))
         .run(parameters.getRuntime(), PKG + ".MainKt")
         .assertSuccessWithOutputLines("RED", "GREEN", "BLUE");
   }
diff --git a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
index b165f4b..83a8577 100644
--- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -18,8 +18,8 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.ResourceException;
-import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -38,9 +38,6 @@
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.google.common.io.ByteStreams;
 import com.google.common.io.Closer;
-import it.unimi.dsi.fastutil.ints.IntList;
-import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
-import it.unimi.dsi.fastutil.ints.IntSet;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -64,7 +61,7 @@
 import org.junit.Rule;
 import org.junit.rules.TemporaryFolder;
 
-public abstract class CompilationTestBase extends TestBase {
+public abstract class CompilationTestBase extends DesugaredLibraryTestBase {
 
   protected KeepingDiagnosticHandler handler;
   protected Reporter reporter;
diff --git a/src/test/java/com/android/tools/r8/internal/D8YouTubeDeployJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/D8YouTubeDeployJarVerificationTest.java
index 202878c..f6de3f9 100644
--- a/src/test/java/com/android/tools/r8/internal/D8YouTubeDeployJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/D8YouTubeDeployJarVerificationTest.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import org.junit.Test;
 
-public class D8YouTubeDeployJarVerificationTest extends YouTubeCompilationBase {
+public class D8YouTubeDeployJarVerificationTest extends YouTubeCompilationTestBase {
 
   public D8YouTubeDeployJarVerificationTest() {
     super(12, 17);
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java b/src/test/java/com/android/tools/r8/internal/YouTubeCompilationTestBase.java
similarity index 66%
rename from src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
rename to src/test/java/com/android/tools/r8/internal/YouTubeCompilationTestBase.java
index c8be865..b9c868c 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeCompilationTestBase.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.internal;
 
+import static org.junit.Assert.assertTrue;
+
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.ToolHelper;
@@ -14,7 +16,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
-public abstract class YouTubeCompilationBase extends CompilationTestBase {
+public abstract class YouTubeCompilationTestBase extends CompilationTestBase {
 
   static final String APK = "YouTubeRelease_unsigned.apk";
   static final String DEPLOY_JAR = "YouTubeRelease_deploy.jar";
@@ -26,7 +28,7 @@
 
   final String base;
 
-  public YouTubeCompilationBase(int majorVersion, int minorVersion) {
+  public YouTubeCompilationTestBase(int majorVersion, int minorVersion) {
     this.base =
         "third_party/youtube/youtube.android_"
             + majorVersion
@@ -35,6 +37,33 @@
             + "/";
   }
 
+  protected Path getDesugaredLibraryConfiguration() {
+    Path path = Paths.get(base, "desugar_jdk_libs/full_desugar_jdk_libs.json");
+    assertTrue(path.toFile().exists());
+    return path;
+  }
+
+  protected Path getDesugaredLibraryJDKLibs() {
+    Path path = Paths.get(base, "desugar_jdk_libs/jdk_libs_to_desugar.jar");
+    assertTrue(path.toFile().exists());
+    return path;
+  }
+
+  protected Path getDesugaredLibraryJDKLibsConfiguration() {
+    Path path = Paths.get(base, "desugar_jdk_libs/desugar_jdk_libs_configuration.jar");
+    assertTrue(path.toFile().exists());
+    return path;
+  }
+
+  protected List<Path> getDesugaredLibraryKeepRuleFiles() {
+    ImmutableList<Path> keepRuleFiles =
+        ImmutableList.of(
+            Paths.get(base, "desugar_jdk_libs/base.pgcfg"),
+            Paths.get(base, "desugar_jdk_libs/minify_desugar_jdk_libs.pgcfg"));
+    assertTrue(keepRuleFiles.stream().allMatch(keepRuleFile -> keepRuleFile.toFile().exists()));
+    return keepRuleFiles;
+  }
+
   protected List<Path> getKeepRuleFiles() {
     ImmutableList.Builder<Path> builder = ImmutableList.builder();
     builder.add(Paths.get(base).resolve(PG_CONF));
@@ -49,7 +78,14 @@
   }
 
   protected List<Path> getLibraryFiles() {
-    return ImmutableList.of(Paths.get(base, "legacy_YouTubeRelease_combined_library_jars.jar"));
+    Path filtered =
+        Paths.get(base).resolve("legacy_YouTubeRelease_combined_library_jars_filtered.jar");
+    if (filtered.toFile().exists()) {
+      return ImmutableList.of(filtered);
+    }
+    Path unfiltered = Paths.get(base, "legacy_YouTubeRelease_combined_library_jars.jar");
+    assertTrue(unfiltered.toFile().exists());
+    return ImmutableList.of(unfiltered);
   }
 
   protected List<Path> getMainDexRuleFiles() {
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeDeployJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeDeployJarVerificationTest.java
index 5bf893d..f6b78f6 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeDeployJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeDeployJarVerificationTest.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import org.junit.Test;
 
-public class YouTubeDeployJarVerificationTest extends YouTubeCompilationBase {
+public class YouTubeDeployJarVerificationTest extends YouTubeCompilationTestBase {
 
   public YouTubeDeployJarVerificationTest() {
     super(12, 17);
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeDexVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeDexVerificationTest.java
index a01ee01..32b7973 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeDexVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeDexVerificationTest.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.CompilationMode;
 import org.junit.Test;
 
-public class YouTubeDexVerificationTest extends YouTubeCompilationBase {
+public class YouTubeDexVerificationTest extends YouTubeCompilationTestBase {
 
   public YouTubeDexVerificationTest() {
     super(12, 17);
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeProguardJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeProguardJarVerificationTest.java
index 5092831..ffe4e14 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeProguardJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeProguardJarVerificationTest.java
@@ -8,7 +8,7 @@
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
-public class YouTubeProguardJarVerificationTest extends YouTubeCompilationBase {
+public class YouTubeProguardJarVerificationTest extends YouTubeCompilationTestBase {
 
   public YouTubeProguardJarVerificationTest() {
     super(12, 17);
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1217TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1217TreeShakeJarVerificationTest.java
index 69b4748..5f39a31 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1217TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1217TreeShakeJarVerificationTest.java
@@ -14,7 +14,7 @@
 import java.nio.file.Path;
 import org.junit.Test;
 
-public class YouTubeV1217TreeShakeJarVerificationTest extends YouTubeCompilationBase {
+public class YouTubeV1217TreeShakeJarVerificationTest extends YouTubeCompilationTestBase {
 
   public YouTubeV1217TreeShakeJarVerificationTest() {
     super(12, 17);
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java
index 15d8928..42fc8f7 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java
@@ -23,7 +23,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class YouTubeV1419TreeShakeJarVerificationTest extends YouTubeCompilationBase {
+public class YouTubeV1419TreeShakeJarVerificationTest extends YouTubeCompilationTestBase {
 
   private static final int MAX_SIZE = 27500000;
 
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java
index f2afcb4..a998521 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java
@@ -23,7 +23,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class YouTubeV1444TreeShakeJarVerificationTest extends YouTubeCompilationBase {
+public class YouTubeV1444TreeShakeJarVerificationTest extends YouTubeCompilationTestBase {
 
   private static final boolean DUMP = false;
   private static final int MAX_SIZE = 27500000;
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java
index 406ff62..eccb336d 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java
@@ -23,7 +23,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class YouTubeV1508TreeShakeJarVerificationTest extends YouTubeCompilationBase {
+public class YouTubeV1508TreeShakeJarVerificationTest extends YouTubeCompilationTestBase {
 
   private static final boolean DUMP = false;
   private static final int MAX_SIZE = 27500000;
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java
index 88c7692..1fe6af5 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java
@@ -3,10 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.internal;
 
-import static com.android.tools.r8.ToolHelper.isLocalDevelopment;
-import static com.android.tools.r8.ToolHelper.shouldRunSlowTests;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
@@ -23,7 +20,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class YouTubeV1533TreeShakeJarVerificationTest extends YouTubeCompilationBase {
+public class YouTubeV1533TreeShakeJarVerificationTest extends YouTubeCompilationTestBase {
 
   private static final boolean DUMP = false;
   private static final int MAX_SIZE = 27500000;
@@ -41,8 +38,8 @@
   @Test
   public void testR8() throws Exception {
     // TODO(b/141603168): Enable this on the bots.
-    assumeTrue(isLocalDevelopment());
-    assumeTrue(shouldRunSlowTests());
+    // assumeTrue(isLocalDevelopment());
+    // assumeTrue(shouldRunSlowTests());
 
     LibrarySanitizer librarySanitizer =
         new LibrarySanitizer(temp)
@@ -57,7 +54,9 @@
             .addLibraryFiles(librarySanitizer.getSanitizedLibrary())
             .addKeepRuleFiles(getKeepRuleFiles())
             .addMainDexRuleFiles(getMainDexRuleFiles())
+            .addIgnoreWarnings()
             .allowDiagnosticMessages()
+            .allowUnusedDontWarnPatterns()
             .allowUnusedProguardConfigurationRules()
             .setMinApi(AndroidApiLevel.H_MR2)
             .compile();
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1612TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1612TreeShakeJarVerificationTest.java
new file mode 100644
index 0000000..0f07300
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1612TreeShakeJarVerificationTest.java
@@ -0,0 +1,113 @@
+// 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.internal;
+
+import static com.android.tools.r8.ToolHelper.isLocalDevelopment;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.analysis.ProtoApplicationStats;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+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 YouTubeV1612TreeShakeJarVerificationTest extends YouTubeCompilationTestBase {
+
+  private static final boolean DUMP = false;
+  private static final int MAX_SIZE = 30000000;
+
+  private final Path dumpDirectory = Paths.get("YouTubeV1612-" + System.currentTimeMillis());
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public YouTubeV1612TreeShakeJarVerificationTest(TestParameters parameters) {
+    super(16, 12);
+    parameters.assertNoneRuntime();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assumeTrue(isLocalDevelopment());
+
+    KeepRuleConsumer keepRuleConsumer = new PresentKeepRuleConsumer();
+    R8TestCompileResult compileResult =
+        testForR8(Backend.DEX)
+            .addProgramFiles(getProgramFiles())
+            .addLibraryFiles(getLibraryFiles())
+            .addKeepRuleFiles(getKeepRuleFiles())
+            .addIgnoreWarnings()
+            .allowDiagnosticMessages()
+            .allowUnusedDontWarnPatterns()
+            .allowUnusedProguardConfigurationRules()
+            .setMinApi(AndroidApiLevel.L)
+            .enableCoreLibraryDesugaring(
+                AndroidApiLevel.L,
+                keepRuleConsumer,
+                StringResource.fromFile(getDesugaredLibraryConfiguration()))
+            .compile();
+
+    if (ToolHelper.isLocalDevelopment()) {
+      if (DUMP) {
+        dumpDirectory.toFile().mkdirs();
+        compileResult.writeToZip(dumpDirectory.resolve("app.zip"));
+        compileResult.writeProguardMap(dumpDirectory.resolve("mapping.txt"));
+      }
+
+      DexItemFactory dexItemFactory = new DexItemFactory();
+      ProtoApplicationStats original =
+          new ProtoApplicationStats(dexItemFactory, new CodeInspector(getProgramFiles()));
+      ProtoApplicationStats actual =
+          new ProtoApplicationStats(dexItemFactory, compileResult.inspector(), original);
+      ProtoApplicationStats baseline =
+          new ProtoApplicationStats(
+              dexItemFactory, new CodeInspector(getReleaseApk(), getReleaseProguardMap()));
+      System.out.println(actual.getStats(baseline));
+    }
+
+    int applicationSize = compileResult.getApp().applicationSize();
+    System.out.println("Dex size (app, excluding desugared library): " + applicationSize);
+
+    Path desugaredLibrary =
+        testForL8(AndroidApiLevel.L)
+            .setDesugaredLibraryConfiguration(getDesugaredLibraryConfiguration())
+            .setDesugarJDKLibs(getDesugaredLibraryJDKLibs())
+            .setDesugarJDKLibsConfiguration(getDesugaredLibraryJDKLibsConfiguration())
+            .addKeepRules(keepRuleConsumer.get())
+            .addKeepRuleFiles(getDesugaredLibraryKeepRuleFiles())
+            .compile();
+
+    byte[] desugaredLibraryDex = ZipUtils.readSingleEntry(desugaredLibrary, "classes.dex");
+
+    if (DUMP) {
+      Files.write(dumpDirectory.resolve("desugared_jdk_libs.dex"), desugaredLibraryDex);
+    }
+
+    int desugaredLibrarySize = desugaredLibraryDex.length;
+    System.out.println("Dex size (desugared library): " + desugaredLibrarySize);
+
+    int totalApplicationSize = applicationSize + desugaredLibrarySize;
+    System.out.println("Dex size (total): " + totalApplicationSize);
+
+    assertTrue(
+        "Expected max size of " + MAX_SIZE + ", got " + totalApplicationSize,
+        totalApplicationSize < MAX_SIZE);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/proto/YouTubeV1508ProtoRewritingTest.java b/src/test/java/com/android/tools/r8/internal/proto/YouTubeV1508ProtoRewritingTest.java
index 7cdab46..dbdeed0 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/YouTubeV1508ProtoRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/YouTubeV1508ProtoRewritingTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.internal.LibrarySanitizer;
-import com.android.tools.r8.internal.YouTubeCompilationBase;
+import com.android.tools.r8.internal.YouTubeCompilationTestBase;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
@@ -22,7 +22,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class YouTubeV1508ProtoRewritingTest extends YouTubeCompilationBase {
+public class YouTubeV1508ProtoRewritingTest extends YouTubeCompilationTestBase {
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeElementWidthTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeElementWidthTest.java
index c07c431..609c892 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeElementWidthTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeElementWidthTest.java
@@ -76,8 +76,7 @@
   public void testReferenceWidth() {
     DexItemFactory dexItemFactory = new DexItemFactory();
     ClassTypeElement referenceType =
-        ClassTypeElement.create(
-            dexItemFactory.objectType, Nullability.maybeNull(), InterfaceCollection.empty());
+        ClassTypeElement.createForD8(dexItemFactory.objectType, Nullability.maybeNull());
     assertFalse(referenceType.isSinglePrimitive());
     assertFalse(referenceType.isWidePrimitive());
     assertEquals(1, referenceType.requiredRegisters());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java
index 98628aa..3d48fb9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java
@@ -70,7 +70,7 @@
 
   private boolean isInvokeSuper(InstructionSubject instruction) {
     if (parameters.isCfRuntime()) {
-      return instruction.isInvokeSpecial();
+      return instruction.asCfInstruction().isInvokeSpecial();
     } else {
       return instruction.asDexInstruction().isInvokeSuper();
     }
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index f6b24da..65e33d1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.kotlin;
 
 import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0_M2;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -49,11 +50,6 @@
     super(parameters, kotlinParameters, true);
   }
 
-  private static boolean isLambda(DexClass clazz) {
-    return !clazz.getType().getPackageDescriptor().startsWith("kotlin")
-        && (isKStyleLambda(clazz) || isJStyleLambda(clazz));
-  }
-
   private static boolean isKStyleLambda(DexClass clazz) {
     return clazz.getSuperType().getTypeName().equals("kotlin.jvm.internal.Lambda");
   }
@@ -65,6 +61,8 @@
 
   @Test
   public void testJStyleLambdas() throws Exception {
+    // TODO(b/185497606): Unable to class inline j style lambdas.
+    assumeTrue(kotlinc.isNot(KOTLINC_1_5_0_M2));
     String mainClassName = "class_inliner_lambda_j_style.MainKt";
     runTest(
             "class_inliner_lambda_j_style",
diff --git a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
index b0dffa9..548ce4d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_4_20;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
 
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
@@ -46,11 +46,11 @@
         .addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
         .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
         .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
-        .allowUnusedDontWarnKotlinReflectJvmInternal(kotlinc.is(KOTLINC_1_4_20))
-        .allowUnusedProguardConfigurationRules(kotlinc.is(KOTLINC_1_4_20))
+        .allowUnusedDontWarnKotlinReflectJvmInternal(kotlinc.isNot(KOTLINC_1_3_72))
+        .allowUnusedProguardConfigurationRules(kotlinc.isNot(KOTLINC_1_3_72))
         .apply(consumer)
         .compile()
-        .apply(assertUnusedKeepRuleForKotlinMetadata(kotlinc.is(KOTLINC_1_4_20)));
+        .apply(assertUnusedKeepRuleForKotlinMetadata(kotlinc.isNot(KOTLINC_1_3_72)));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
index 1b2dd74..2b2eed4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -4,9 +4,11 @@
 
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0_M2;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.R8TestBuilder;
@@ -274,8 +276,12 @@
                       propertyName, AccessorKind.FROM_COMPANION);
 
               assertTrue(fieldSubject.getField().accessFlags.isPublic());
-              checkMethodIsRemoved(outerClass, getterAccessor);
-              checkMethodIsRemoved(outerClass, setterAccessor);
+
+              // kotlinc 1.5 do not generate accessors for public late-init properties.
+              if (kotlinc.isNot(KOTLINC_1_5_0_M2)) {
+                checkMethodIsRemoved(outerClass, getterAccessor);
+                checkMethodIsRemoved(outerClass, setterAccessor);
+              }
             });
   }
 
@@ -303,8 +309,12 @@
                       propertyName, AccessorKind.FROM_COMPANION);
 
               assertTrue(fieldSubject.getField().accessFlags.isPublic());
-              checkMethodIsRemoved(outerClass, getterAccessor);
-              checkMethodIsRemoved(outerClass, setterAccessor);
+
+              // kotlinc 1.5 do not generate accessors for public late-init properties.
+              if (kotlinc.isNot(KOTLINC_1_5_0_M2)) {
+                checkMethodIsRemoved(outerClass, getterAccessor);
+                checkMethodIsRemoved(outerClass, setterAccessor);
+              }
             });
   }
 
@@ -365,6 +375,8 @@
 
   @Test
   public void testAccessorForInnerClassIsRemovedWhenNotUsed() throws Exception {
+    // TODO(b/185493636): Kotlinc 1.5 generated property accessors are not removed.
+    assumeTrue(kotlinc.isNot(KOTLINC_1_5_0_M2));
     String mainClass =
         addMainToClasspath(
             "accessors.PropertyAccessorForInnerClassKt", "noUseOfPropertyAccessorFromInnerClass");
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
index 6fbd439..00d2418 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.kotlin.lambda;
 
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0_M2;
 import static com.android.tools.r8.ToolHelper.getKotlinAnnotationJar;
 import static com.android.tools.r8.utils.PredicateUtils.not;
 import static junit.framework.TestCase.assertEquals;
@@ -73,6 +74,8 @@
 
   @Test
   public void testR8() throws Exception {
+    // TODO(b/185497606): Unable to merge jstyle lambda.
+    assumeTrue(kotlinc.isNot(KOTLINC_1_5_0_M2));
     testForR8(parameters.getBackend())
         .addProgramFiles(getProgramFiles())
         .addKeepMainRule(getMainClassName())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
index 401087b..deb4e5e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0_M2;
 import static com.android.tools.r8.ToolHelper.getKotlinAnnotationJar;
 import static com.android.tools.r8.ToolHelper.getKotlinStdlibJar;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionProperty;
@@ -139,9 +140,9 @@
     KmPropertySubject age = kmClass.kmPropertyWithUniqueName("age");
     assertThat(age, isPresent());
     assertThat(age, not(isExtensionProperty()));
-    assertEquals(age.fieldSignature().asString(), "a:I");
-    assertEquals(age.getterSignature().asString(), "getAge()I");
-    assertEquals(age.setterSignature().asString(), "setAge(I)V");
+    assertEquals(kotlinc.is(KOTLINC_1_5_0_M2) ? "b:I" : "a:I", age.fieldSignature().asString());
+    assertEquals("getAge()I", age.getterSignature().asString());
+    assertEquals("setAge(I)V", age.setterSignature().asString());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
index 90907da..4f5f64f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
@@ -16,6 +16,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -163,8 +164,15 @@
             .compileRaw();
 
     assertNotEquals(0, kotlinTestCompileResult.exitCode);
-    assertThat(kotlinTestCompileResult.stderr, containsString("cannot access"));
-    assertThat(kotlinTestCompileResult.stderr, containsString("private in 'Expr'"));
+    if (kotlinc.is(KotlinCompilerVersion.KOTLINC_1_5_0_M2)) {
+      assertThat(
+          kotlinTestCompileResult.stderr,
+          containsString(
+              "inheritance of sealed classes or interfaces from different module is prohibited"));
+    } else {
+      assertThat(kotlinTestCompileResult.stderr, containsString("cannot access"));
+      assertThat(kotlinTestCompileResult.stderr, containsString("private in 'Expr'"));
+    }
   }
 
   private void inspectInvalid(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index 0e03e02..6406593 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_4_20;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
 import static com.android.tools.r8.ToolHelper.getKotlinAnnotationJar;
 import static com.android.tools.r8.ToolHelper.getKotlinReflectJar;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
@@ -60,7 +60,7 @@
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
             .allowDiagnosticWarningMessages()
             .setMinApi(parameters.getApiLevel())
-            .allowUnusedDontWarnKotlinReflectJvmInternal(kotlinc.is(KOTLINC_1_4_20))
+            .allowUnusedDontWarnKotlinReflectJvmInternal(kotlinc.isNot(KOTLINC_1_3_72))
             .compile()
             .apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib)
             .run(parameters.getRuntime(), mainClassName);
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_app/main.kt
index d7ed237..414da89 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_app/main.kt
@@ -28,7 +28,7 @@
   val nested = Quux::methodWithNestedAnnotations.returnType.arguments[0].type?.annotations?.get(0) as Nested
   println(nested.message)
   nested.kept.printAnnoWithClassAndEnum()
-  if (nested::class::memberProperties.get().any { it.name.equals("notKept") }) {
+  if (nested::class.memberProperties.any { it.name.equals("notKept") }) {
     println("com.android.tools.r8.kotlin.metadata.annotation_lib.Foo")
   } else {
     println("a.b.c")
diff --git a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
index fcb64b6..4e376ed 100644
--- a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.kotlin.reflection;
 
-import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_4_20;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
@@ -92,7 +92,7 @@
         .addKeepAllClassesRule()
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .allowDiagnosticWarningMessages()
-        .allowUnusedDontWarnKotlinReflectJvmInternal(kotlinc.is(KOTLINC_1_4_20))
+        .allowUnusedDontWarnKotlinReflectJvmInternal(kotlinc.isNot(KOTLINC_1_3_72))
         .compile()
         .writeToZip(foo.toPath())
         .apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib)
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
index 413a4fd..e25fd15 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
@@ -10,6 +10,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
@@ -70,6 +71,9 @@
     ClassSubject enumClass = inspector.clazz(ENUM_CLASS_NAME);
     assertThat(enumClass, isPresent());
     assertEquals(minify, enumClass.isRenamed());
-    assertThat(enumClass.clinit(), isAbsent());
+    // TODO(b/179994975): Kotlin enum changed in 1.5.
+    assertThat(
+        enumClass.clinit(),
+        kotlinc.is(KotlinCompilerVersion.KOTLINC_1_5_0_M2) ? isPresent() : isAbsent());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/MapReaderVersionTest.java b/src/test/java/com/android/tools/r8/naming/MapReaderVersionTest.java
index 1412ed0..c678a70 100644
--- a/src/test/java/com/android/tools/r8/naming/MapReaderVersionTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MapReaderVersionTest.java
@@ -10,8 +10,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
-import com.android.tools.r8.naming.mappinginformation.ScopeReference;
-import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.StringUtils;
 import java.io.IOException;
 import org.junit.Assert;
@@ -81,13 +79,11 @@
       String finalName, String originalName, boolean isSynthesized, ClassNameMapper mapper) {
     ClassNamingForNameMapper naming = mapper.getClassNaming(finalName);
     assertEquals(originalName, naming.originalName);
-    Assert.assertEquals(isSynthesized, isCompilerSynthesized(mapper, finalName));
+    Assert.assertEquals(isSynthesized, isCompilerSynthesized(naming));
   }
 
-  private boolean isCompilerSynthesized(ClassNameMapper mapper, String finalName) {
-    ScopeReference reference =
-        ScopeReference.fromClassReference(Reference.classFromTypeName(finalName));
-    return mapper.getAdditionalMappingInfo(reference).stream()
+  private boolean isCompilerSynthesized(ClassNamingForNameMapper naming) {
+    return naming.getAdditionalMappingInfo().stream()
         .anyMatch(MappingInformation::isCompilerSynthesizedMappingInformation);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java b/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java
index 218e037..22a3470 100644
--- a/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java
+++ b/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java
@@ -10,7 +10,6 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.KotlinCompilerTool;
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
@@ -39,9 +38,10 @@
 
   private static final KotlinCompileMemoizer kotlinJars =
       getCompileMemoizer(getKotlinFilesInResource("lambdas_kstyle_generics"))
-          .configure(KotlinCompilerTool::includeRuntime);
+          // TODO(b/185465199): This is not really the test for testing shrinking reflect.
+          .configure(kotlinCompilerTool -> kotlinCompilerTool.includeRuntime().noReflect());
 
-  @Parameters(name = "{0}")
+  @Parameters(name = "{0}, {1}")
   public static List<Object[]> data() {
     return buildParameters(
         getTestParameters().withDexRuntimes().withAllApiLevels().build(),
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
index 425f3b0..62286f9 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
@@ -150,7 +150,8 @@
     CodeInspector inspector = new CodeInspector(appView.appInfo().app());
     MethodSubject foo = inspector.clazz(callerClass).uniqueMethodWithName("foo");
     assertTrue(
-        foo.streamInstructions().anyMatch(i -> i.isInvokeSpecial() && i.getMethod() == target));
+        foo.streamInstructions()
+            .anyMatch(i -> i.asCfInstruction().isInvokeSpecial() && i.getMethod() == target));
   }
 
   private DexMethod getTargetMethodSignature(
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessWithIntermediateTest.java
index af6e166..83d363b 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessWithIntermediateTest.java
@@ -21,7 +21,6 @@
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -131,7 +130,8 @@
     CodeInspector inspector = new CodeInspector(appInfo.app());
     MethodSubject foo = inspector.clazz(callerClass).uniqueMethodWithName("foo");
     assertTrue(
-        foo.streamInstructions().anyMatch(i -> i.isInvokeSpecial() && i.getMethod() == method));
+        foo.streamInstructions()
+            .anyMatch(i -> i.asCfInstruction().isInvokeSpecial() && i.getMethod() == method));
   }
 
   private DexMethod getTargetMethodSignature(Class<?> declaredClass, AppInfoWithLiveness appInfo) {
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java
index c6a2dbf..20575adb 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java
@@ -125,7 +125,8 @@
     CodeInspector inspector = new CodeInspector(appView.appInfo().app());
     MethodSubject foo = inspector.clazz(callerClass).uniqueMethodWithName("foo");
     assertTrue(
-        foo.streamInstructions().anyMatch(i -> i.isInvokeSpecial() && i.getMethod() == target));
+        foo.streamInstructions()
+            .anyMatch(i -> i.asCfInstruction().isInvokeSpecial() && i.getMethod() == target));
   }
 
   private DexMethod getTargetMethodSignature(Class<?> declaredClass, AppView<?> appView) {
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
index 5498d06..9286245 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
@@ -160,7 +160,8 @@
     CodeInspector inspector = new CodeInspector(appView.appInfo().app());
     MethodSubject foo = inspector.clazz(callerClass).uniqueMethodWithName("foo");
     assertTrue(
-        foo.streamInstructions().anyMatch(i -> i.isInvokeSpecial() && i.getMethod() == target));
+        foo.streamInstructions()
+            .anyMatch(i -> i.asCfInstruction().isInvokeSpecial() && i.getMethod() == target));
   }
 
   private DexMethod getTargetMethodSignature(Class<?> declaredClass, AppView<?> appView) {
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodPublicAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodPublicAccessWithIntermediateTest.java
index ada12fe..b4dab4e 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodPublicAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodPublicAccessWithIntermediateTest.java
@@ -122,7 +122,8 @@
     CodeInspector inspector = new CodeInspector(appInfo.app());
     MethodSubject foo = inspector.clazz(callerClass).uniqueMethodWithName("foo");
     assertTrue(
-        foo.streamInstructions().anyMatch(i -> i.isInvokeSpecial() && i.getMethod() == target));
+        foo.streamInstructions()
+            .anyMatch(i -> i.asCfInstruction().isInvokeSpecial() && i.getMethod() == target));
   }
 
   private DexMethod getTargetMethodSignature(Class<?> declaredClass, AppInfoWithLiveness appInfo) {
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
index b65563e..dfe1002 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.retrace;
 
-import static com.android.tools.r8.Collectors.toSingle;
 import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
 import static com.android.tools.r8.ToolHelper.getKotlinAnnotationJar;
 import static com.android.tools.r8.ToolHelper.getKotlinStdlibJar;
@@ -14,7 +13,6 @@
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.StringContains.containsString;
-import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
@@ -22,6 +20,7 @@
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.Matchers.LinePosition;
@@ -30,6 +29,7 @@
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.List;
+import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -69,10 +69,13 @@
     }
   }
 
+  private int getObfuscatedLinePosition() {
+    // TODO(b/185358363): This should go away when we correctly retrace.
+    return kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72) ? 43 : 32;
+  }
+
   @Test
   public void testRuntime() throws Exception {
-    // TODO(b/179666509): SMAP has changed.
-    assumeTrue(kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
     testForRuntime(parameters)
         .addProgramFiles(compilationResults.getForConfiguration(kotlinc, targetVersion))
         .addRunClasspathFiles(buildOnDexRuntime(parameters, getKotlinStdlibJar(kotlinc)))
@@ -80,13 +83,12 @@
         .assertFailureWithErrorThatMatches(containsString("foo"))
         .assertFailureWithErrorThatMatches(
             containsString(
-                "at retrace.InlineFunctionsInSameFileKt.main(InlineFunctionsInSameFile.kt:43"));
+                "at retrace.InlineFunctionsInSameFileKt.main(InlineFunctionsInSameFile.kt:"
+                    + getObfuscatedLinePosition()));
   }
 
   @Test
   public void testRetraceKotlinInlineStaticFunction() throws Exception {
-    // TODO(b/179666509): SMAP has changed.
-    assumeTrue(kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
     Path kotlinSources = compilationResults.getForConfiguration(kotlinc, targetVersion);
     CodeInspector kotlinInspector = new CodeInspector(kotlinSources);
     testForR8(parameters.getBackend())
@@ -116,7 +118,10 @@
                           8,
                           FILENAME_INLINE),
                       LinePosition.create(
-                          mainSubject.asFoundMethodSubject(), 1, 43, FILENAME_INLINE));
+                          mainSubject.asFoundMethodSubject(),
+                          1,
+                          getObfuscatedLinePosition(),
+                          FILENAME_INLINE));
               checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
             });
   }
@@ -128,10 +133,11 @@
       LinePosition inlineStack) {
     assertThat(mainSubject, isPresent());
     RetraceFrameResult retraceResult =
-        mainSubject
-            .streamInstructions()
-            .filter(InstructionSubject::isThrow)
-            .collect(toSingle())
+        ListUtils.last(
+                mainSubject
+                    .streamInstructions()
+                    .filter(InstructionSubject::isThrow)
+                    .collect(Collectors.toList()))
             .retraceLinePosition(codeInspector.retrace());
     assertThat(retraceResult, isInlineFrame());
     assertThat(retraceResult, isInlineStack(inlineStack));
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
index 17d4dc6..232c1f5 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.retrace;
 
-import static com.android.tools.r8.Collectors.toSingle;
 import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
 import static com.android.tools.r8.ToolHelper.getKotlinAnnotationJar;
 import static com.android.tools.r8.ToolHelper.getKotlinStdlibJar;
@@ -15,14 +14,13 @@
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.StringContains.containsString;
-import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -32,6 +30,7 @@
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.List;
+import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -96,8 +95,6 @@
 
   @Test
   public void testRetraceKotlinInlineStaticFunction() throws Exception {
-    // TODO(b/179666509): SMAP has changed.
-    assumeTrue(kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
     String main = "retrace.MainKt";
     String mainFileName = "Main.kt";
     Path kotlinSources = compilationResults.getForConfiguration(kotlinc, targetVersion);
@@ -128,8 +125,6 @@
 
   @Test
   public void testRetraceKotlinInlineInstanceFunction() throws Exception {
-    // TODO(b/179666509): SMAP has changed.
-    assumeTrue(kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
     String main = "retrace.MainInstanceKt";
     String mainFileName = "MainInstance.kt";
     Path kotlinSources = compilationResults.getForConfiguration(kotlinc, targetVersion);
@@ -163,8 +158,6 @@
 
   @Test
   public void testRetraceKotlinNestedInlineFunction() throws Exception {
-    // TODO(b/179666509): SMAP has changed.
-    assumeTrue(kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
     String main = "retrace.MainNestedKt";
     String mainFileName = "MainNested.kt";
     Path kotlinSources = compilationResults.getForConfiguration(kotlinc, targetVersion);
@@ -197,8 +190,6 @@
 
   @Test
   public void testRetraceKotlinNestedInlineFunctionOnFirstLine() throws Exception {
-    // TODO(b/179666509): SMAP has changed.
-    assumeTrue(kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
     String main = "retrace.MainNestedFirstLineKt";
     String mainFileName = "MainNestedFirstLine.kt";
     Path kotlinSources = compilationResults.getForConfiguration(kotlinc, targetVersion);
@@ -236,10 +227,11 @@
       LinePosition inlineStack) {
     assertThat(mainSubject, isPresent());
     RetraceFrameResult retraceResult =
-        mainSubject
-            .streamInstructions()
-            .filter(InstructionSubject::isThrow)
-            .collect(toSingle())
+        ListUtils.last(
+                mainSubject
+                    .streamInstructions()
+                    .filter(InstructionSubject::isThrow)
+                    .collect(Collectors.toList()))
             .retraceLinePosition(codeInspector.retrace());
     assertThat(retraceResult, isInlineFrame());
     assertThat(retraceResult, isInlineStack(inlineStack));
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java b/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
index 25cbfbe..38f7725 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.google.common.collect.ImmutableList;
@@ -60,41 +59,20 @@
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkRunResult)
         .apply(this::checkNoOutputSynthetics)
-        .inspectStackTrace(
-            stackTrace ->
-                assertThat(
-                    stackTrace,
-                    isSameExceptForFileNameAndLineNumber(
-                        StackTrace.builder()
-                            .addWithoutFileNameAndLineNumber(Main.class, JAVAC_LAMBDA_METHOD)
-                            .addWithoutFileNameAndLineNumber(Main.class, "runIt")
-                            .addWithoutFileNameAndLineNumber(Main.class, "main")
-                            .build())));
+        .inspectStackTrace(RetraceLambdaTest::checkExpectedStackTrace);
   }
 
   @Test
   public void testD8() throws Exception {
     testForD8(parameters.getBackend())
         .internalEnableMappingOutput()
+        .enableExperimentalMapFileVersion()
         .addInnerClasses(getClass())
         .setMinApi(parameters.getApiLevel())
-        .enableExperimentalMapFileVersion()
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkRunResult)
         .apply(this::checkOneOutputSynthetic)
-        .inspectStackTrace(
-            stackTrace ->
-                assertThat(
-                    stackTrace,
-                    isSameExceptForFileNameAndLineNumber(
-                        StackTrace.builder()
-                            .addWithoutFileNameAndLineNumber(Main.class, JAVAC_LAMBDA_METHOD)
-                            // TODO(b/172014416): Support a D8 mapping and prune the synthetic.
-                            .addWithoutFileNameAndLineNumber(
-                                SyntheticItemsTestUtils.syntheticLambdaClass(Main.class, 0), "run")
-                            .addWithoutFileNameAndLineNumber(Main.class, "runIt")
-                            .addWithoutFileNameAndLineNumber(Main.class, "main")
-                            .build())));
+        .inspectStackTrace(RetraceLambdaTest::checkExpectedStackTrace);
   }
 
   @Test
@@ -140,7 +118,7 @@
             stackTrace -> {
               int frames = parameters.isCfRuntime() ? 3 : 5;
               checkRawStackTraceFrameCount(stackTrace, frames, "Expected nothing to be inlined");
-              checkCurrentlyIncorrectStackTrace(stackTrace);
+              checkExpectedStackTrace(stackTrace);
             });
   }
 
@@ -184,6 +162,17 @@
     assertEquals(message + stackTrace.getOriginalStderr(), expectedFrames, linesFromTest);
   }
 
+  private static void checkExpectedStackTrace(StackTrace stackTrace) {
+    assertThat(
+        stackTrace,
+        isSameExceptForFileNameAndLineNumber(
+            StackTrace.builder()
+                .addWithoutFileNameAndLineNumber(Main.class, JAVAC_LAMBDA_METHOD)
+                .addWithoutFileNameAndLineNumber(Main.class, "runIt")
+                .addWithoutFileNameAndLineNumber(Main.class, "main")
+                .build()));
+  }
+
   private void checkCurrentlyIncorrectStackTrace(StackTrace stackTrace) {
     assertThat(
         stackTrace,
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 22d4f15..a1707c5 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -47,6 +47,8 @@
 import com.android.tools.r8.retrace.stacktraces.SourceFileWithNumberAndEmptyStackTrace;
 import com.android.tools.r8.retrace.stacktraces.StackTraceForTest;
 import com.android.tools.r8.retrace.stacktraces.SuppressedStackTrace;
+import com.android.tools.r8.retrace.stacktraces.SyntheticLambdaMethodStackTrace;
+import com.android.tools.r8.retrace.stacktraces.SyntheticLambdaMethodWithInliningStackTrace;
 import com.android.tools.r8.retrace.stacktraces.UnicodeInFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.UnknownSourceStackTrace;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -254,6 +256,16 @@
     runRetraceTest(new AutoStackTrace());
   }
 
+  @Test
+  public void testRetraceSynthesizedLambda() throws Exception {
+    runRetraceTest(new SyntheticLambdaMethodStackTrace());
+  }
+
+  @Test
+  public void testRetraceSynthesizedLambdaWithInlining() throws Exception {
+    runRetraceTest(new SyntheticLambdaMethodWithInliningStackTrace());
+  }
+
   private void inspectRetraceTest(
       StackTraceForTest stackTraceForTest, Consumer<Retracer> inspection) {
     inspection.accept(
@@ -304,7 +316,10 @@
               .setStackTrace(stackTraceForTest.obfuscatedStackTrace())
               .setRegularExpression(useRegExpParsing ? DEFAULT_REGULAR_EXPRESSION : null)
               .setRetracedStackTraceConsumer(
-                  retraced -> assertEquals(stackTraceForTest.retracedStackTrace(), retraced))
+                  retraced ->
+                      assertEquals(
+                          StringUtils.joinLines(stackTraceForTest.retracedStackTrace()),
+                          StringUtils.joinLines(retraced)))
               .build();
       Retrace.run(retraceCommand);
       return diagnosticsHandler;
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodStackTrace.java
new file mode 100644
index 0000000..efe3f0f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodStackTrace.java
@@ -0,0 +1,53 @@
+// 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class SyntheticLambdaMethodStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "  at a.a.a(a.java:5)",
+        "  at a.b.a(Unknown Source)",
+        "  at a.a.b(a.java:3)",
+        "  at a.a.c(a.java:2)",
+        "  at example.Main.main(Main.java:1)");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "  at example.Foo.lambda$main$0(Foo.java:225)",
+        "  at example.Foo.runIt(Foo.java:218)",
+        "  at example.Foo.main(Foo.java:223)",
+        "  at example.Main.main(Main.java:123)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "# {'id':'com.android.tools.r8.metainf','map-version':'experimental'}",
+        "example.Main -> example.Main:",
+        "  1:1:void main(java.lang.String[]):123 -> main",
+        "example.Foo -> a.a:",
+        "  5:5:void lambda$main$0():225 -> a",
+        "  3:3:void runIt():218 -> b",
+        "  2:2:void main():223 -> c",
+        "example.Foo$$ExternalSyntheticLambda0 -> a.b:",
+        "  void run(example.Foo) -> a",
+        "    # {'id':'com.android.tools.r8.synthesized'}");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodWithInliningStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodWithInliningStackTrace.java
new file mode 100644
index 0000000..5fda85f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodWithInliningStackTrace.java
@@ -0,0 +1,52 @@
+// 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class SyntheticLambdaMethodWithInliningStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "  at a.b.a(Unknown Source:4)",
+        "  at a.a.b(a.java:3)",
+        "  at a.a.c(a.java:2)",
+        "  at example.Main.main(Main.java:1)");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "  at example.Foo.lambda$main$0(Foo.java:225)",
+        "  at example.Foo.runIt(Foo.java:218)",
+        "  at example.Foo.main(Foo.java:223)",
+        "  at example.Main.main(Main.java:123)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "# {'id':'com.android.tools.r8.metainf','map-version':'experimental'}",
+        "example.Main -> example.Main:",
+        "  1:1:void main(java.lang.String[]):123 -> main",
+        "example.Foo -> a.a:",
+        "  3:3:void runIt():218 -> b",
+        "  2:2:void main():223 -> c",
+        "example.Foo$$ExternalSyntheticLambda0 -> a.b:",
+        "  4:4:void example.Foo.lambda$main$0():225 -> a",
+        "  4:4:void run(example.Foo):0 -> a",
+        "    # {'id':'com.android.tools.r8.synthesized'}");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesMaterializeFieldReadTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesMaterializeFieldReadTest.java
new file mode 100644
index 0000000..0caf415
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesMaterializeFieldReadTest.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.assumevalues;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class AssumeValuesMaterializeFieldReadTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public AssumeValuesMaterializeFieldReadTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepRules(
+            "-assumevalues class " + Main.class.getTypeName() + " {",
+            "  static java.lang.Object get() return " + Main.class.getTypeName() + ".field;",
+            "}")
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject mainClassSubject = inspector.clazz(Main.class);
+              assertThat(mainClassSubject, isPresent());
+
+              MethodSubject getMethodSubject = mainClassSubject.uniqueMethodWithName("get");
+              assertThat(getMethodSubject, isAbsent());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithEmptyOutput();
+  }
+
+  static class Main {
+
+    static Object field = new Object();
+
+    public static void main(String[] args) {
+      if (field != get()) {
+        throw new RuntimeException();
+      }
+    }
+
+    @NeverInline
+    public static Object get() {
+      return new Object();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index 9324b87..21501d0 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -1682,16 +1682,15 @@
         "    sget-object         v1, Ljava/lang/System;->out:Ljava/io/PrintStream;",
         "    new-instance        v2, Ljava/util/ArrayList;",
         "    invoke-direct       { v2 }, Ljava/util/ArrayList;-><init>()V",
-        "    invoke-virtual      { v0, v1, v2 }, " +
-        "                            LTest;->method1(Ljava/io/PrintStream;Ljava/util/ArrayList;)Z",
+        "    invoke-direct       { v0, v1, v2 }, "
+            + "LTest;->method1(Ljava/io/PrintStream;Ljava/util/ArrayList;)Z",
         "    move-result         v3",
         "    invoke-virtual      { v1, v3 }, Ljava/io/PrintStream;->print(Z)V",
-        "    invoke-virtual      { v0, v1, v2 }, " +
-        "                            LTest;->method2(Ljava/io/PrintStream;Ljava/util/ArrayList;)Z",
+        "    invoke-direct       { v0, v1, v2 }, "
+            + "LTest;->method2(Ljava/io/PrintStream;Ljava/util/ArrayList;)Z",
         "    move-result         v3",
         "    invoke-virtual      { v1, v3 }, Ljava/io/PrintStream;->print(Z)V",
-        "    return-void"
-    );
+        "    return-void");
 
     // Outline 2 times two instructions.
     Consumer<InternalOptions> options =
@@ -1759,14 +1758,13 @@
         "    sget-object         v1, Ljava/lang/System;->out:Ljava/io/PrintStream;",
         "    new-instance        v2, Ljava/util/ArrayList;",
         "    invoke-direct       { v2 }, Ljava/util/ArrayList;-><init>()V",
-        "    invoke-virtual      { v0, v2 }, LTest;->method1(Ljava/util/List;)Z",
+        "    invoke-direct       { v0, v2 }, LTest;->method1(Ljava/util/List;)Z",
         "    move-result         v3",
         "    invoke-virtual      { v1, v3 }, Ljava/io/PrintStream;->print(Z)V",
-        "    invoke-virtual      { v0, v2 }, LTest;->method2(Ljava/util/List;)Z",
+        "    invoke-direct       { v0, v2 }, LTest;->method2(Ljava/util/List;)Z",
         "    move-result         v3",
         "    invoke-virtual      { v1, v3 }, Ljava/io/PrintStream;->print(Z)V",
-        "    return-void"
-    );
+        "    return-void");
 
     // Outline 2 times two instructions.
     Consumer<InternalOptions> options =
@@ -1836,14 +1834,13 @@
         "    sget-object         v1, Ljava/lang/System;->out:Ljava/io/PrintStream;",
         "    new-instance        v2, Ljava/util/ArrayList;",
         "    invoke-direct       { v2 }, Ljava/util/ArrayList;-><init>()V",
-        "    invoke-virtual      { v0, v2 }, LTest;->method1(Ljava/util/ArrayList;)Z",
+        "    invoke-direct       { v0, v2 }, LTest;->method1(Ljava/util/ArrayList;)Z",
         "    move-result         v3",
         "    invoke-virtual      { v1, v3 }, Ljava/io/PrintStream;->print(Z)V",
-        "    invoke-virtual      { v0, v2 }, LTest;->method2(Ljava/util/ArrayList;)Z",
+        "    invoke-direct       { v0, v2 }, LTest;->method2(Ljava/util/ArrayList;)Z",
         "    move-result         v3",
         "    invoke-virtual      { v1, v3 }, Ljava/io/PrintStream;->print(Z)V",
-        "    return-void"
-    );
+        "    return-void");
 
     // Outline 2 times two instructions.
     Consumer<InternalOptions> options =
@@ -1913,14 +1910,13 @@
         "    sget-object         v1, Ljava/lang/System;->out:Ljava/io/PrintStream;",
         "    new-instance        v2, Ljava/util/ArrayList;",
         "    invoke-direct       { v2 }, Ljava/util/ArrayList;-><init>()V",
-        "    invoke-virtual      { v0, v2 }, LTest;->method1(Ljava/util/ArrayList;)Z",
+        "    invoke-direct       { v0, v2 }, LTest;->method1(Ljava/util/ArrayList;)Z",
         "    move-result         v3",
         "    invoke-virtual      { v1, v3 }, Ljava/io/PrintStream;->print(Z)V",
-        "    invoke-virtual      { v0, v2 }, LTest;->method2(Ljava/util/ArrayList;)Z",
+        "    invoke-direct       { v0, v2 }, LTest;->method2(Ljava/util/ArrayList;)Z",
         "    move-result         v3",
         "    invoke-virtual      { v1, v3 }, Ljava/io/PrintStream;->print(Z)V",
-        "    return-void"
-    );
+        "    return-void");
 
     // Outline 2 times two instructions.
     Consumer<InternalOptions> options =
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index f927e62..64b3914 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -53,11 +53,21 @@
   }
 
   @Override
+  public boolean isDexInstruction() {
+    return false;
+  }
+
+  @Override
   public DexInstructionSubject asDexInstruction() {
     return null;
   }
 
   @Override
+  public boolean isCfInstruction() {
+    return true;
+  }
+
+  @Override
   public CfInstructionSubject asCfInstruction() {
     return this;
   }
@@ -279,7 +289,6 @@
         && ((CfSwitch) instruction).getKind() == CfSwitch.Kind.LOOKUP;
   }
 
-  @Override
   public boolean isInvokeSpecial() {
     return instruction instanceof CfInvoke
         && ((CfInvoke) instruction).getOpcode() == Opcodes.INVOKESPECIAL;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 8c8ba0f..077af2f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -29,7 +29,6 @@
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
-import com.android.tools.r8.naming.mappinginformation.ScopeReference;
 import com.android.tools.r8.naming.signature.GenericSignatureAction;
 import com.android.tools.r8.naming.signature.GenericSignatureParser;
 import com.android.tools.r8.origin.Origin;
@@ -292,8 +291,7 @@
     }
 
     public Collection<MappingInformation> getAdditionalMappings() {
-      return mapper.getAdditionalMappingInfo(
-          ScopeReference.fromClassReference(Reference.classFromTypeName(naming.renamedName)));
+      return naming.getAdditionalMappingInfo();
     }
 
     public ClassNamingForNameMapper getNaming() {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 2ae5f05..2bfadc8 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -116,11 +116,21 @@
   }
 
   @Override
+  public boolean isDexInstruction() {
+    return true;
+  }
+
+  @Override
   public DexInstructionSubject asDexInstruction() {
     return this;
   }
 
   @Override
+  public boolean isCfInstruction() {
+    return false;
+  }
+
+  @Override
   public CfInstructionSubject asCfInstruction() {
     return null;
   }
@@ -205,11 +215,6 @@
   }
 
   @Override
-  public boolean isInvokeSpecial() {
-    return false;
-  }
-
-  @Override
   public boolean isInvokeDynamic() {
     return isInvokeCustom();
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/EnumUnboxingInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/EnumUnboxingInspector.java
index 38f7e92..d554d50 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/EnumUnboxingInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/EnumUnboxingInspector.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
+import com.android.tools.r8.utils.DescriptorUtils;
 
 public class EnumUnboxingInspector {
 
@@ -21,8 +22,23 @@
     this.unboxedEnums = unboxedEnums;
   }
 
+  public EnumUnboxingInspector assertUnboxed(String typeName) {
+    assertTrue(
+        unboxedEnums.isUnboxedEnum(
+            dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(typeName))));
+    return this;
+  }
+
   public EnumUnboxingInspector assertUnboxed(Class<? extends Enum<?>> clazz) {
-    assertTrue(unboxedEnums.isUnboxedEnum(toDexType(clazz, dexItemFactory)));
+    assertTrue(clazz.getTypeName(), unboxedEnums.isUnboxedEnum(toDexType(clazz, dexItemFactory)));
+    return this;
+  }
+
+  @SafeVarargs
+  public final EnumUnboxingInspector assertUnboxed(Class<? extends Enum<?>>... classes) {
+    for (Class<? extends Enum<?>> clazz : classes) {
+      assertUnboxed(clazz);
+    }
     return this;
   }
 
@@ -36,15 +52,24 @@
   }
 
   @SafeVarargs
-  public final EnumUnboxingInspector assertUnboxed(Class<? extends Enum<?>>... classes) {
+  public final EnumUnboxingInspector assertUnboxedIf(
+      boolean condition, Class<? extends Enum<?>>... classes) {
     for (Class<? extends Enum<?>> clazz : classes) {
-      assertUnboxed(clazz);
+      assertUnboxedIf(condition, clazz);
     }
     return this;
   }
 
   public EnumUnboxingInspector assertNotUnboxed(Class<? extends Enum<?>> clazz) {
-    assertFalse(unboxedEnums.isUnboxedEnum(toDexType(clazz, dexItemFactory)));
+    assertFalse(clazz.getTypeName(), unboxedEnums.isUnboxedEnum(toDexType(clazz, dexItemFactory)));
+    return this;
+  }
+
+  @SafeVarargs
+  public final EnumUnboxingInspector assertNotUnboxed(Class<? extends Enum<?>>... classes) {
+    for (Class<? extends Enum<?>> clazz : classes) {
+      assertNotUnboxed(clazz);
+    }
     return this;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index e8818b2..36d9ed5 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -17,8 +17,12 @@
     DISALLOW
   };
 
+  boolean isDexInstruction();
+
   DexInstructionSubject asDexInstruction();
 
+  boolean isCfInstruction();
+
   CfInstructionSubject asCfInstruction();
 
   boolean isFieldAccess();
@@ -41,10 +45,17 @@
 
   boolean isInvokeStatic();
 
-  boolean isInvokeSpecial();
-
   boolean isInvokeDynamic();
 
+  default boolean isInvokeSpecialOrDirect() {
+    if (isCfInstruction()) {
+      return asCfInstruction().isInvokeSpecial();
+    } else {
+      assert isDexInstruction();
+      return asDexInstruction().isInvokeDirect();
+    }
+  }
+
   DexMethod getMethod();
 
   boolean isNop();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index aa6d435..5083d42 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -521,9 +521,8 @@
         RetraceFrameElement single = item.stream().collect(Collectors.toSingle());
         Box<LinePosition> currentPosition = new Box<>(startPosition);
         Box<Boolean> returnValue = new Box<>();
-        single.visitFrames(
+        single.visitAllFrames(
             (method, __) -> {
-              boolean sameMethod;
               LinePosition currentInline = currentPosition.get();
               if (currentInline == null) {
                 returnValue.set(false);
@@ -533,7 +532,7 @@
                 returnValue.set(false);
                 return;
               }
-              sameMethod =
+              boolean sameMethod =
                   method.asKnown().getMethodReference().equals(currentInline.methodReference);
               boolean samePosition =
                   method.getOriginalPositionOrDefault(currentInline.minifiedPosition)
@@ -560,7 +559,7 @@
       protected boolean matchesSafely(RetraceFrameResult item) {
         RetraceFrameElement single = item.stream().collect(Collectors.toSingle());
         Box<Boolean> matches = new Box<>(true);
-        single.visitFrames(
+        single.visitAllFrames(
             (method, index) -> {
               StackTraceLine stackTraceLine = stackTrace.get(index);
               if (!stackTraceLine.methodName.equals(method.getMethodName())
diff --git a/third_party/kotlin/kotlin-compiler-1.5.0-M2.tar.gz.sha1 b/third_party/kotlin/kotlin-compiler-1.5.0-M2.tar.gz.sha1
new file mode 100644
index 0000000..e0b251e
--- /dev/null
+++ b/third_party/kotlin/kotlin-compiler-1.5.0-M2.tar.gz.sha1
@@ -0,0 +1 @@
+7368d9ee73d01fc609d7ded1bf0506f72aa3ac66
\ No newline at end of file
diff --git a/third_party/youtube/youtube.android_16.12.tar.gz.sha1 b/third_party/youtube/youtube.android_16.12.tar.gz.sha1
new file mode 100644
index 0000000..580b87e
--- /dev/null
+++ b/third_party/youtube/youtube.android_16.12.tar.gz.sha1
@@ -0,0 +1 @@
+1e50d558d85ebd9741cf4bd62e01cf415d4d480f
\ No newline at end of file
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index b58eafb..60cd4f1 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -454,72 +454,62 @@
 def should_build(options):
   return not options.no_build and not options.golem
 
-def build_desugared_library_dex(options, quiet, temp, android_java8_libs, inputs, outdir):
-  if not input:
-    raise Exception("If 'android_java8_libs' is specified the inputs must be explicit"
-       + "(not defined using '-injars' in Proguard configuration files)")
+def build_desugared_library_dex(
+    options,
+    quiet,
+    temp,
+    android_java8_libs,
+    desugared_lib_pg_conf,
+    inputs,
+    outdir):
+  if not inputs:
+    raise Exception(
+        "If 'android_java8_libs' is specified the inputs must be explicit"
+            + "(not defined using '-injars' in Proguard configuration files)")
   if outdir.endswith('.zip') or outdir.endswith('.jar'):
-    raise Exception("If 'android_java8_libs' is specified the output must be a directory")
+    raise Exception(
+        "If 'android_java8_libs' is specified the output must be a directory")
 
   jar = None
   main = None
   if options.hash:
     jar = os.path.join(utils.LIBS, 'r8-' + options.hash + '.jar')
+    main = 'com.android.tools.r8.R8'
 
+  # Determine the l8 tool.
   assert(options.compiler_build in ['full', 'lib'])
   lib_prefix = 'r8lib-' if options.compiler_build == 'lib' else ''
+  tool = lib_prefix + 'l8'
 
-  # Determine the tracereferences tool.
-  tool = lib_prefix + 'tracereferences'
-  if options.hash:
-    main = 'com.android.tools.r8.TraceReferences'
-  tracereferences_output = os.path.join(outdir, 'tracereferences.pgcfg')
-  args = [
-    '--map-diagnostics:MissingDefinitionsDiagnostic', 'error', 'warning',
-    '--keep-rules',
-    '--lib', 'third_party/android_jar/lib-v30/android.jar',
-    '--target', android_java8_libs['library'],
-    '--output', tracereferences_output,
-  ]
-  for source in inputs:
-    args.extend(['--source', source])
-  exit_code = toolhelper.run(tool, args,
-      build=should_build(options),
-      debug=not options.no_debug,
-      quiet=quiet,
-      jar=jar,
-      main=main)
-  if exit_code != 0:
-    raise Exception("tracereferences failed")
-
-  # Determine the r8 tool.
-  tool = lib_prefix + 'r8'
-  if options.compiler_build == 'full':
-    tool = ''
-  else:
-    assert(options.compiler_build == 'lib')
-    tool = 'r8lib-r8'
-  if options.hash:
-    main = 'com.android.tools.r8.R8'
+  # Prepare out directory.
   android_java8_libs_output = os.path.join(temp, 'android_java8_libs')
   os.makedirs(android_java8_libs_output)
+
+  # Prepare arguments for L8.
   args = [
-    '--no-desugaring',
-    '--lib', '%s/android_jar/lib-v30/android.jar' % utils.THIRD_PARTY,
+    '--desugared-lib', android_java8_libs['config'],
+    '--lib', android_java8_libs['library'],
     '--output', android_java8_libs_output,
-    '--pg-conf', tracereferences_output,
-    android_java8_libs['library']
+    '--pg-conf', desugared_lib_pg_conf,
+    '--release',
   ]
-  for pgconf in android_java8_libs['pgconf']:
-    args.extend(['--pg-conf', pgconf])
-  exit_code = toolhelper.run(tool, args,
+  if 'pgconf' in android_java8_libs:
+    for pgconf in android_java8_libs['pgconf']:
+      args.extend(['--pg-conf', pgconf])
+  args.extend(android_java8_libs['program'])
+
+  # Run L8.
+  exit_code = toolhelper.run(
+      tool, args,
       build=should_build(options),
       debug=not options.no_debug,
       quiet=quiet,
       jar=jar,
       main=main)
+
   # Copy the desugared library DEX to the output.
-  dex_file_name = 'classes' + str(len(glob(os.path.join(outdir, '*.dex'))) + 1) + '.dex'
+  dex_file_name = (
+      'classes' + str(len(glob(os.path.join(outdir, '*.dex'))) + 1) + '.dex')
   shutil.copyfile(
       os.path.join(android_java8_libs_output, 'classes.dex'),
       os.path.join(outdir, dex_file_name))
@@ -680,6 +670,15 @@
           additional_pg_conf = GenerateAdditionalProguardConfiguration(
               temp, os.path.abspath(pg_outdir))
           args.extend(['--pg-conf', additional_pg_conf])
+
+      android_java8_libs = values.get('android_java8_libs')
+      if android_java8_libs:
+        desugared_lib_pg_conf = os.path.join(
+            temp, 'desugared-lib-pg-conf.txt')
+        args.extend(['--desugared-lib', android_java8_libs['config']])
+        args.extend(
+            ['--desugared-lib-pg-conf-output', desugared_lib_pg_conf])
+
       stderr_path = os.path.join(temp, 'stderr')
       with open(stderr_path, 'w') as stderr:
         jar = None
@@ -722,9 +721,10 @@
             .format(options.print_memoryuse,
                 utils.grep_memoryuse(options.track_memory_to_file)))
 
-      if 'android_java8_libs' in values:
-        android_java8_libs = values['android_java8_libs']
-        build_desugared_library_dex(options, quiet, temp, android_java8_libs, inputs, outdir)
+      if android_java8_libs:
+        build_desugared_library_dex(
+            options, quiet, temp, android_java8_libs,
+            desugared_lib_pg_conf, inputs, outdir)
 
 
   if options.print_runtimeraw:
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
index b66b70f..02cb549 100644
--- a/tools/toolhelper.py
+++ b/tools/toolhelper.py
@@ -39,6 +39,8 @@
     cmd.extend(['-cp', jar, main])
   elif tool == 'r8lib-d8':
     cmd.extend(['-cp', utils.R8LIB_JAR, 'com.android.tools.r8.D8'])
+  elif tool == 'r8lib-l8':
+    cmd.extend(['-cp', utils.R8LIB_JAR, 'com.android.tools.r8.L8'])
   elif tool == 'r8lib-r8':
     cmd.extend(['-cp', utils.R8LIB_JAR, 'com.android.tools.r8.R8'])
   elif tool == 'r8lib-tracereferences':
diff --git a/tools/youtube_data.py b/tools/youtube_data.py
index eafe5bb..04d9e1f 100644
--- a/tools/youtube_data.py
+++ b/tools/youtube_data.py
@@ -37,6 +37,9 @@
 V15_33_BASE = os.path.join(BASE, 'youtube.android_15.33')
 V15_33_PREFIX = os.path.join(V15_33_BASE, 'YouTubeRelease')
 
+V16_12_BASE = os.path.join(BASE, 'youtube.android_16.12')
+V16_12_PREFIX = os.path.join(V16_12_BASE, 'YouTubeRelease')
+
 # NOTE: we always use android.jar for SDK v25, later we might want to revise it
 #       to use proper android.jar version for each of youtube version separately.
 ANDROID_JAR = utils.get_android_jar(25)
@@ -283,4 +286,35 @@
       'min-api' : ANDROID_L_API,
     }
   },
+  '16.12': {
+    'deploy' : {
+      'sanitize_libraries': False,
+      'inputs': ['%s_deploy.jar' % V16_12_PREFIX],
+      'libraries' : [
+          os.path.join(
+              V16_12_BASE,
+              'legacy_YouTubeRelease_combined_library_jars_filtered.jar')],
+      'pgconf': [
+          '%s_proguard.config' % V16_12_PREFIX,
+          '%s/proguardsettings/YouTubeRelease_proguard.config' % utils.THIRD_PARTY,
+          utils.IGNORE_WARNINGS_RULES],
+      'min-api' : ANDROID_L_API,
+      'android_java8_libs': {
+        'config': '%s/desugar_jdk_libs/full_desugar_jdk_libs.json' % V16_12_BASE,
+        'program': [
+            '%s/desugar_jdk_libs/jdk_libs_to_desugar.jar' % V16_12_BASE,
+            '%s/desugar_jdk_libs/desugar_jdk_libs_configuration.jar' % V16_12_BASE],
+        'library': '%s/android_jar/lib-v30/android.jar' % utils.THIRD_PARTY,
+        'pgconf': [
+          '%s/desugar_jdk_libs/base.pgcfg' % V16_12_BASE,
+          '%s/desugar_jdk_libs/minify_desugar_jdk_libs.pgcfg' % V16_12_BASE
+        ]
+      }
+    },
+    'proguarded' : {
+      'inputs': ['%s_proguard.jar' % V16_12_PREFIX],
+      'pgmap': '%s_proguard.map' % V16_12_PREFIX,
+      'min-api' : ANDROID_L_API,
+    }
+  },
 }