Merge commit '8f6c03384ef834acec9a9497024748b9815bb61f' into dev-release
diff --git a/build.gradle b/build.gradle
index 338df88..6050eda 100644
--- a/build.gradle
+++ b/build.gradle
@@ -35,7 +35,7 @@
 
 ext {
     androidSupportVersion = '25.4.0'
-    asmVersion = '7.2'
+    asmVersion = '8.0'
     espressoVersion = '3.0.0'
     fastutilVersion = '7.2.0'
     guavaVersion = '23.0'
@@ -279,6 +279,7 @@
     examplesKotlinCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
     kotlinR8TestResourcesCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
     errorprone("com.google.errorprone:error_prone_core:$errorproneVersion")
+    testImplementation "org.jetbrains.kotlin:kotlin-reflect:1.3.31"
 }
 
 def r8LibPath = "$buildDir/libs/r8lib.jar"
@@ -404,6 +405,7 @@
         "benchmarks/kotlin-benches",
         "chrome/chrome_180917_ffbaa8",
         "chrome/chrome_200430",
+        "chrome/monochrome_public_minimal_apks/chrome_200520",
         "classlib",
         "cf_segments",
         "desugar/desugar_20180308",
@@ -2398,4 +2400,4 @@
       println commandLine.join(' ')
     }
   }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 5781482..cf475fa 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -393,7 +393,6 @@
     assert internal.neverMergePrefixes.contains("j$.");
 
     // Assert some of R8 optimizations are disabled.
-    assert !internal.enableDynamicTypeOptimization;
     assert !internal.enableInlining;
     assert !internal.enableClassInlining;
     assert !internal.enableHorizontalClassMerging;
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index 3c7fdd2..ac56a10 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -130,7 +130,10 @@
                       + ".",
                   "  --intermediate          # Compile an intermediate result intended for later",
                   "                          # merging.",
-                  "  --file-per-class        # Produce a separate dex file per input class",
+                  "  --file-per-class        # Produce a separate dex file per class.",
+                  "                          # Synthetic classes are in their own file.",
+                  "  --file-per-class-file   # Produce a separate dex file per input .class file.",
+                  "                          # Synthetic classes are with their originating class.",
                   "  --no-desugaring         # Force disable desugaring.",
                   "  --desugared-lib <file>  # Specify desugared library configuration.",
                   "                          # <file> is a desugared library configuration (json).",
@@ -210,6 +213,8 @@
         compilationMode = CompilationMode.RELEASE;
       } else if (arg.equals("--file-per-class")) {
         outputMode = OutputMode.DexFilePerClass;
+      } else if (arg.equals("--file-per-class-file")) {
+        outputMode = OutputMode.DexFilePerClassFile;
       } else if (arg.equals("--classfile")) {
         outputMode = OutputMode.ClassFile;
       } else if (arg.equals("--output")) {
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index b1072df..9b0f9de 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -162,7 +162,6 @@
     assert !internal.passthroughDexCode;
 
     // Assert some of R8 optimizations are disabled.
-    assert !internal.enableDynamicTypeOptimization;
     assert !internal.enableInlining;
     assert !internal.enableClassInlining;
     assert !internal.enableHorizontalClassMerging;
diff --git a/src/main/java/com/android/tools/r8/L8CommandParser.java b/src/main/java/com/android/tools/r8/L8CommandParser.java
index 0802127..e083cec 100644
--- a/src/main/java/com/android/tools/r8/L8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/L8CommandParser.java
@@ -18,8 +18,8 @@
 
 public class L8CommandParser extends BaseCompilerCommandParser<L8Command, L8Command.Builder> {
 
-  private static final Set<String> OPTIONS_WITH_PARAMETER =
-      ImmutableSet.of("--output", "--lib", MIN_API_FLAG, "--desugared-lib", THREAD_COUNT_FLAG);
+  private static final Set<String> OPTIONS_WITH_PARAMETER = ImmutableSet.of(
+      "--output", "--lib", MIN_API_FLAG, "--desugared-lib", THREAD_COUNT_FLAG, "--pg-conf");
 
   public static void main(String[] args) throws CompilationFailedException {
     L8Command command = parse(args, Origin.root()).build();
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 8406706..34677e0 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -515,10 +515,8 @@
             assert changed;
             appView.setVerticallyMergedClasses(verticalClassMerger.getMergedClasses());
             application = application.asDirect().rewrittenWithLens(lens);
-            lens.initializeCacheForLookupMethodInAllContexts();
             appViewWithLiveness.setAppInfo(
                 appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
-            lens.unsetCacheForLookupMethodInAllContexts();
           }
           timing.end();
         }
@@ -544,6 +542,7 @@
             timing.begin("UninstantiatedTypeOptimization");
             UninstantiatedTypeOptimizationGraphLense lens =
                 new UninstantiatedTypeOptimization(appViewWithLiveness)
+                    .strenghtenOptimizationInfo()
                     .run(
                         new MethodPoolCollection(appViewWithLiveness, subtypingInfo),
                         executorService,
@@ -697,7 +696,7 @@
 
           appView.withGeneratedMessageLiteBuilderShrinker(
               shrinker ->
-                  shrinker.removeDeadBuilderReferencesFromDynamicMethods(
+                  shrinker.rewriteDeadBuilderReferencesFromDynamicMethods(
                       appViewWithLiveness, executorService, timing));
 
           if (options.isShrinking()) {
@@ -914,6 +913,10 @@
           shrinker ->
               shrinker.setDeadProtoTypes(appViewWithLiveness.appInfo().getDeadProtoTypes()));
     }
+    appView.withGeneratedMessageLiteBuilderShrinker(
+        shrinker ->
+            shrinker.rewriteDeadBuilderReferencesFromDynamicMethods(
+                appViewWithLiveness, executorService, timing));
     return appViewWithLiveness;
   }
 
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 656f3aa..5abbc25 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -842,7 +842,6 @@
             ? LineNumberOptimization.ON
             : LineNumberOptimization.OFF;
 
-    assert internal.enableDynamicTypeOptimization || !proguardConfiguration.isOptimizing();
     assert internal.enableHorizontalClassMerging || !proguardConfiguration.isOptimizing();
     assert !internal.enableTreeShakingOfLibraryMethodOverrides;
     assert internal.enableVerticalClassMerging || !proguardConfiguration.isOptimizing();
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index 6220eb4..8c93579 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -33,6 +33,7 @@
           "--pg-conf",
           "--pg-map-output",
           "--desugared-lib",
+          "--desugared-lib-pg-conf-output",
           THREAD_COUNT_FLAG);
 
   private static final Set<String> OPTIONS_WITH_TWO_PARAMETERS = ImmutableSet.of("--feature");
@@ -83,6 +84,8 @@
                       + " <file>.",
                   "  --desugared-lib <file>  # Specify desugared library configuration.",
                   "                          # <file> is a desugared library configuration (json).",
+                  "  --desugared-lib-pg-conf-output <file>  # Output the Proguard configuration ",
+                  "                          # needed by L8 to <file>.",
                   "  --no-tree-shaking       # Force disable tree shaking of unreachable classes.",
                   "  --no-minification       # Force disable minification of names.",
                   "  --no-data-resources     # Ignore all data resources.",
@@ -252,6 +255,9 @@
         builder.setProguardMapOutputPath(Paths.get(nextArg));
       } else if (arg.equals("--desugared-lib")) {
         builder.addDesugaredLibraryConfiguration(StringResource.fromFile(Paths.get(nextArg)));
+      } else if (arg.equals("--desugared-lib-pg-conf-output")) {
+        StringConsumer consumer = new StringConsumer.FileConsumer(Paths.get(nextArg));
+        builder.setDesugaredLibraryKeepRuleConsumer(consumer);
       } else if (arg.equals("--no-data-resources")) {
         state.includeDataResources = false;
       } else if (arg.startsWith("--")) {
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 8ee86f3..5d102bdf 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -455,7 +455,7 @@
   private void insertAttributeAnnotations() {
     // Convert inner-class attributes to DEX annotations
     for (DexProgramClass clazz : application.classes()) {
-      EnclosingMethodAttribute enclosingMethod = clazz.getEnclosingMethod();
+      EnclosingMethodAttribute enclosingMethod = clazz.getEnclosingMethodAttribute();
       List<InnerClassAttribute> innerClasses = clazz.getInnerClasses();
       if (enclosingMethod == null && innerClasses.isEmpty()) {
         continue;
@@ -521,7 +521,7 @@
       }
 
       // Clear the attribute structures now that they are represented in annotations.
-      clazz.clearEnclosingMethod();
+      clazz.clearEnclosingMethodAttribute();
       clazz.clearInnerClasses();
     }
   }
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 26dca03..a416e51 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -642,8 +642,7 @@
       int size,
       DexMethodAnnotation[] annotations,
       DexParameterAnnotation[] parameters,
-      boolean skipCodes,
-      boolean ensureNonAbstract) {
+      boolean skipCodes) {
     DexEncodedMethod[] methods = new DexEncodedMethod[size];
     int methodIndex = 0;
     MemberAnnotationIterator<DexMethod, DexAnnotationSet> annotationIterator =
@@ -661,19 +660,13 @@
         code = codes.get(codeOff);
       }
       DexMethod method = indexedItems.getMethod(methodIndex);
-      DexEncodedMethod encodedMethod =
+      methods[i] =
           new DexEncodedMethod(
               method,
               accessFlags,
               annotationIterator.getNextFor(method),
               parameterAnnotationsIterator.getNextFor(method),
               code);
-      if (accessFlags.isAbstract() && ensureNonAbstract) {
-        accessFlags.unsetAbstract();
-        assert !options.isGeneratingClassFiles();
-        encodedMethod = encodedMethod.toEmptyThrowingMethodDex(false);
-      }
-      methods[i] = encodedMethod;
     }
     return methods;
   }
@@ -754,17 +747,13 @@
                 directMethodsSize,
                 annotationsDirectory.methods,
                 annotationsDirectory.parameters,
-                classKind != ClassKind.PROGRAM,
-                options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()
-                    && !flags.isAbstract());
+                classKind != ClassKind.PROGRAM);
         virtualMethods =
             readMethods(
                 virtualMethodsSize,
                 annotationsDirectory.methods,
                 annotationsDirectory.parameters,
-                classKind != ClassKind.PROGRAM,
-                options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()
-                    && !flags.isAbstract());
+                classKind != ClassKind.PROGRAM);
       }
 
       AttributesAndAnnotations attrs =
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 0b5d100..3706dec 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -21,9 +21,6 @@
   private final DexApplication app;
   private final DexItemFactory dexItemFactory;
 
-  // TODO(b/151804585): Remove this cache.
-  private final ConcurrentHashMap<DexType, Map<DexField, DexEncodedField>> fieldDefinitionsCache;
-
   // For some optimizations, e.g. optimizing synthetic classes, we may need to resolve the current
   // class being optimized.
   private final ConcurrentHashMap<DexType, DexProgramClass> synthesizedClasses;
@@ -33,31 +30,28 @@
   private final BooleanBox obsolete;
 
   public AppInfo(DexApplication application) {
-    this(application, new ConcurrentHashMap<>(), new ConcurrentHashMap<>(), new BooleanBox());
+    this(application, new ConcurrentHashMap<>(), new BooleanBox());
   }
 
   // For desugaring.
   protected AppInfo(AppInfo appInfo) {
-    this(appInfo.app, appInfo.fieldDefinitionsCache, appInfo.synthesizedClasses, appInfo.obsolete);
+    this(appInfo.app, appInfo.synthesizedClasses, appInfo.obsolete);
   }
 
   // For AppInfoWithLiveness.
   protected AppInfo(AppInfoWithClassHierarchy previous) {
     this(
         ((AppInfo) previous).app,
-        new ConcurrentHashMap<>(((AppInfo) previous).fieldDefinitionsCache),
         new ConcurrentHashMap<>(((AppInfo) previous).synthesizedClasses),
         new BooleanBox());
   }
 
   private AppInfo(
       DexApplication application,
-      ConcurrentHashMap<DexType, Map<DexField, DexEncodedField>> fieldDefinitionsCache,
       ConcurrentHashMap<DexType, DexProgramClass> synthesizedClasses,
       BooleanBox obsolete) {
     this.app = application;
     this.dexItemFactory = application.dexItemFactory;
-    this.fieldDefinitionsCache = fieldDefinitionsCache;
     this.synthesizedClasses = synthesizedClasses;
     this.obsolete = obsolete;
   }
@@ -102,7 +96,6 @@
     assert checkIfObsolete();
     assert clazz.type.isD8R8SynthesizedClassType();
     DexProgramClass previous = synthesizedClasses.put(clazz.type, clazz);
-    invalidateFieldCacheFor(clazz.type);
     assert previous == null || previous == clazz;
   }
 
@@ -131,19 +124,6 @@
   }
 
   @Override
-  public DexDefinition definitionFor(DexReference reference) {
-    assert checkIfObsolete();
-    if (reference.isDexType()) {
-      return definitionFor(reference.asDexType());
-    }
-    if (reference.isDexMethod()) {
-      return definitionFor(reference.asDexMethod());
-    }
-    assert reference.isDexField();
-    return definitionFor(reference.asDexField());
-  }
-
-  @Override
   public DexClass definitionFor(DexType type) {
     return definitionForWithoutExistenceAssert(type);
   }
@@ -181,6 +161,7 @@
     return definition == null ? Origin.unknown() : definition.origin;
   }
 
+  @Deprecated
   @Override
   public DexEncodedMethod definitionFor(DexMethod method) {
     assert checkIfObsolete();
@@ -195,20 +176,6 @@
     return clazz.getMethodCollection().getMethod(method);
   }
 
-  @Override
-  public DexEncodedField definitionFor(DexField field) {
-    assert checkIfObsolete();
-    return getFieldDefinitions(field.holder).get(field);
-  }
-
-  private Map<DexField, DexEncodedField> getFieldDefinitions(DexType type) {
-    return fieldDefinitionsCache.computeIfAbsent(type, this::computeFieldDefinitions);
-  }
-
-  public void invalidateFieldCacheFor(DexType type) {
-    fieldDefinitionsCache.remove(type);
-  }
-
   /**
    * Lookup static method on the method holder, or answers null.
    *
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 8077269..b4534f5 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -226,16 +226,7 @@
     return this.sourceDebugExtensions.get(clazz);
   }
 
-  @Override
-  public final DexDefinition definitionFor(DexReference reference) {
-    return appInfo().definitionFor(reference);
-  }
-
-  @Override
-  public final DexEncodedField definitionFor(DexField field) {
-    return appInfo().definitionFor(field);
-  }
-
+  @Deprecated
   @Override
   public final DexEncodedMethod definitionFor(DexMethod method) {
     return appInfo().definitionFor(method);
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
index 030497d..e33792c 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -167,6 +167,10 @@
     return isSet(Constants.ACC_ABSTRACT);
   }
 
+  public void demoteFromAbstract() {
+    demote(Constants.ACC_ABSTRACT);
+  }
+
   public void setAbstract() {
     set(Constants.ACC_ABSTRACT);
   }
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 c2cdc32..09aeb11 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -176,12 +176,10 @@
 
   private boolean verifyNoAbstractMethodsOnNonAbstractClasses(
       Iterable<DexEncodedMethod> methods, InternalOptions options) {
-    if (options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()) {
-      if (!isAbstract()) {
-        for (DexEncodedMethod method : methods) {
-          assert !method.isAbstract()
-              : "Non-abstract method on abstract class: `" + method.method.toSourceString() + "`";
-        }
+    if (options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug() && !isAbstract()) {
+      for (DexEncodedMethod method : methods) {
+        assert !method.isAbstract()
+            : "Non-abstract method on abstract class: `" + method.method.toSourceString() + "`";
       }
     }
     return true;
@@ -406,6 +404,15 @@
     return methodCollection.getVirtualMethod(predicate);
   }
 
+  /** Find member in this class matching {@param member}. */
+  @SuppressWarnings("unchecked")
+  public <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> D lookupMember(
+      DexMember<D, R> member) {
+    DexEncodedMember<?, ?> definition =
+        member.isDexField() ? lookupField(member.asDexField()) : lookupMethod(member.asDexMethod());
+    return (D) definition;
+  }
+
   /** Find method in this class matching {@param method}. */
   public DexEncodedMethod lookupMethod(DexMethod method) {
     return methodCollection.getMethod(method);
@@ -700,15 +707,15 @@
     return innerClasses;
   }
 
-  public EnclosingMethodAttribute getEnclosingMethod() {
+  public EnclosingMethodAttribute getEnclosingMethodAttribute() {
     return enclosingMethod;
   }
 
-  public void clearEnclosingMethod() {
+  public void clearEnclosingMethodAttribute() {
     enclosingMethod = null;
   }
 
-  public void removeEnclosingMethod(Predicate<EnclosingMethodAttribute> predicate) {
+  public void removeEnclosingMethodAttribute(Predicate<EnclosingMethodAttribute> predicate) {
     if (enclosingMethod != null && predicate.test(enclosingMethod)) {
       enclosingMethod = null;
     }
@@ -752,7 +759,7 @@
   public boolean isMemberClass() {
     InnerClassAttribute innerClass = getInnerClassAttributeForThisClass();
     boolean isMember = innerClass != null && innerClass.getOuter() != null && innerClass.isNamed();
-    assert !isMember || getEnclosingMethod() == null;
+    assert !isMember || getEnclosingMethodAttribute() == null;
     return isMember;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
index 3d4e92b..214e41c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
@@ -6,18 +6,9 @@
 
 public interface DexDefinitionSupplier {
 
-  DexDefinition definitionFor(DexReference reference);
-
-  DexEncodedField definitionFor(DexField field);
-
+  @Deprecated
   DexEncodedMethod definitionFor(DexMethod method);
 
-  @SuppressWarnings("unchecked")
-  default <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
-      DexEncodedMember<D, R> definitionFor(DexMember<D, R> member) {
-    return (DexEncodedMember<D, R>) definitionFor((DexReference) member);
-  }
-
   DexClass definitionFor(DexType type);
 
   DexProgramClass definitionForProgramType(DexType type);
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 7071fda..c12f3f4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -705,6 +705,10 @@
     return builder.toString();
   }
 
+  public void clearParameterAnnotations() {
+    parameterAnnotationsList = ParameterAnnotationsList.empty();
+  }
+
   public String toSmaliString(ClassNameMapper naming) {
     checkIfObsolete();
     StringBuilder builder = new StringBuilder();
@@ -767,17 +771,7 @@
         null);
   }
 
-  public DexCode buildEmptyThrowingDexCode() {
-    Instruction insn[] = {new Const(0, 0), new Throw(0)};
-    return generateCodeFromTemplate(1, 0, insn);
-  }
-
   public DexEncodedMethod toEmptyThrowingMethod(InternalOptions options) {
-    // Note that we are not marking this instance obsolete, since this util is only used by
-    // TreePruner while keeping non-live yet targeted, empty method. Such method can be retrieved
-    // again only during the 2nd round of tree sharking, and seeing an obsolete empty body v.s.
-    // seeing this empty throwing code do not matter.
-    // If things are changed, the cure point is obsolete instances inside RootSet.
     return options.isGeneratingClassFiles()
         ? toEmptyThrowingMethodCf()
         : toEmptyThrowingMethodDex(true);
@@ -791,7 +785,28 @@
     if (setIsLibraryOverride && isNonPrivateVirtualMethod()) {
       builder.setIsLibraryMethodOverride(isLibraryMethodOverride());
     }
-    return builder.build();
+    DexEncodedMethod result = builder.build();
+    setObsolete();
+    return result;
+  }
+
+  private DexEncodedMethod toEmptyThrowingMethodCf() {
+    checkIfObsolete();
+    assert !shouldNotHaveCode();
+    Builder builder = builder(this);
+    builder.setCode(buildEmptyThrowingCfCode());
+    if (isNonPrivateVirtualMethod()) {
+      builder.setIsLibraryMethodOverride(isLibraryMethodOverride());
+    }
+    DexEncodedMethod result = builder.build();
+    setObsolete();
+    return result;
+  }
+
+  public Code buildEmptyThrowingCode(InternalOptions options) {
+    return options.isGeneratingClassFiles()
+        ? buildEmptyThrowingCfCode()
+        : buildEmptyThrowingDexCode();
   }
 
   public CfCode buildEmptyThrowingCfCode() {
@@ -805,17 +820,9 @@
         Collections.emptyList());
   }
 
-  private DexEncodedMethod toEmptyThrowingMethodCf() {
-    checkIfObsolete();
-    assert !shouldNotHaveCode();
-    Builder builder = builder(this);
-    builder.setCode(buildEmptyThrowingCfCode());
-    if (isNonPrivateVirtualMethod()) {
-      builder.setIsLibraryMethodOverride(isLibraryMethodOverride());
-    }
-    // Note that we are not marking this instance obsolete:
-    // refer to Dex-backend version of this method above.
-    return builder.build();
+  public DexCode buildEmptyThrowingDexCode() {
+    Instruction insn[] = {new Const(0, 0), new Throw(0)};
+    return generateCodeFromTemplate(1, 0, insn);
   }
 
   public DexEncodedMethod toMethodThatLogsError(AppView<?> appView) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index d10a2c8..84d581e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -6,6 +6,9 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.naming.NamingLens;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
 
 public class DexField extends DexMember<DexEncodedField, DexField> {
 
@@ -23,6 +26,36 @@
   }
 
   @Override
+  public DexEncodedField lookupOnClass(DexClass clazz) {
+    return clazz != null ? clazz.lookupField(this) : null;
+  }
+
+  @Override
+  public <T> T apply(
+      Function<DexType, T> classConsumer,
+      Function<DexField, T> fieldConsumer,
+      Function<DexMethod, T> methodConsumer) {
+    return fieldConsumer.apply(this);
+  }
+
+  @Override
+  public void accept(
+      Consumer<DexType> classConsumer,
+      Consumer<DexField> fieldConsumer,
+      Consumer<DexMethod> methodConsumer) {
+    fieldConsumer.accept(this);
+  }
+
+  @Override
+  public <T> void accept(
+      BiConsumer<DexType, T> classConsumer,
+      BiConsumer<DexField, T> fieldConsumer,
+      BiConsumer<DexMethod, T> methodConsumer,
+      T arg) {
+    fieldConsumer.accept(this, arg);
+  }
+
+  @Override
   public int computeHashCode() {
     return holder.hashCode()
         + type.hashCode() * 7
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 1a318c0..d61746d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -147,6 +147,10 @@
   public final DexString boxedNumberDescriptor = createString("Ljava/lang/Number;");
   public final DexString boxedVoidDescriptor = createString("Ljava/lang/Void;");
 
+  public final DexString waitMethodName = createString("wait");
+  public final DexString notifyMethodName = createString("notify");
+  public final DexString notifyAllMethodName = createString("notifyAll");
+
   public final DexString unboxBooleanMethodName = createString("booleanValue");
   public final DexString unboxByteMethodName = createString("byteValue");
   public final DexString unboxCharMethodName = createString("charValue");
@@ -164,6 +168,7 @@
   public final DexString startsWithMethodName = createString("startsWith");
   public final DexString endsWithMethodName = createString("endsWith");
   public final DexString equalsMethodName = createString("equals");
+  public final DexString hashCodeMethodName = createString("hashCode");
   public final DexString equalsIgnoreCaseMethodName = createString("equalsIgnoreCase");
   public final DexString contentEqualsMethodName = createString("contentEquals");
   public final DexString indexOfMethodName = createString("indexOf");
@@ -1269,6 +1274,7 @@
     public final DexMethod toString;
     public final DexMethod compareTo;
     public final DexMethod equals;
+    public final DexMethod hashCode;
 
     public final DexMethod constructor =
         createMethod(enumType, createProto(voidType, stringType, intType), constructorMethodName);
@@ -1309,6 +1315,8 @@
               equalsMethodName,
               booleanDescriptor,
               new DexString[] {objectDescriptor});
+      hashCode =
+          createMethod(enumDescriptor, hashCodeMethodName, intDescriptor, DexString.EMPTY_ARRAY);
     }
 
     public boolean isValuesMethod(DexMethod method, DexClass enumClass) {
@@ -1495,7 +1503,7 @@
           createMethod(stringDescriptor, compareToIgnoreCaseMethodName, intDescriptor,
               needsOneString);
 
-      hashCode = createMethod(stringType, createProto(intType), "hashCode");
+      hashCode = createMethod(stringType, createProto(intType), hashCodeMethodName);
       valueOf = createMethod(
           stringDescriptor, valueOfMethodName, stringDescriptor, needsOneObject);
       toString = createMethod(
diff --git a/src/main/java/com/android/tools/r8/graph/DexMember.java b/src/main/java/com/android/tools/r8/graph/DexMember.java
index d04808b..02fd431 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -13,6 +13,10 @@
     this.holder = holder;
   }
 
+  public DexEncodedMember<?, ?> lookupOnClass(DexClass clazz) {
+    return clazz != null ? clazz.lookupMember(this) : null;
+  }
+
   public abstract boolean match(R entry);
 
   public abstract boolean match(D entry);
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index d3dd5fc..8a50931 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -11,6 +11,9 @@
 import com.android.tools.r8.references.TypeReference;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
 
 public class DexMethod extends DexMember<DexEncodedMethod, DexMethod> {
 
@@ -29,6 +32,36 @@
   }
 
   @Override
+  public <T> T apply(
+      Function<DexType, T> classConsumer,
+      Function<DexField, T> fieldConsumer,
+      Function<DexMethod, T> methodConsumer) {
+    return methodConsumer.apply(this);
+  }
+
+  @Override
+  public void accept(
+      Consumer<DexType> classConsumer,
+      Consumer<DexField> fieldConsumer,
+      Consumer<DexMethod> methodConsumer) {
+    methodConsumer.accept(this);
+  }
+
+  @Override
+  public <T> void accept(
+      BiConsumer<DexType, T> classConsumer,
+      BiConsumer<DexField, T> fieldConsumer,
+      BiConsumer<DexMethod, T> methodConsumer,
+      T arg) {
+    methodConsumer.accept(this, arg);
+  }
+
+  @Override
+  public DexEncodedMethod lookupOnClass(DexClass clazz) {
+    return clazz != null ? clazz.lookupMember(this) : null;
+  }
+
+  @Override
   public String toString() {
     return "Method " + holder + "." + name + " " + proto.toString();
   }
@@ -191,4 +224,8 @@
     return name == dexItemFactory.deserializeLambdaMethodName
         && proto == dexItemFactory.deserializeLambdaMethodProto;
   }
+
+  public boolean isInstanceInitializer(DexDefinitionSupplier definitions) {
+    return definitions.dexItemFactory().isConstructor(this);
+  }
 }
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 900712b..423acef 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -133,6 +133,10 @@
     synthesizedDirectlyFrom.forEach(this::addSynthesizedFrom);
   }
 
+  public void forEachProgramField(Consumer<ProgramField> consumer) {
+    forEachField(field -> consumer.accept(new ProgramField(this, field)));
+  }
+
   public void forEachProgramMethod(Consumer<ProgramMethod> consumer) {
     forEachProgramMethodMatching(alwaysTrue(), consumer);
   }
@@ -242,8 +246,8 @@
       if (interfaces != null) {
         interfaces.collectIndexedItems(indexedItems, method, instructionOffset);
       }
-      if (getEnclosingMethod() != null) {
-        getEnclosingMethod().collectIndexedItems(indexedItems);
+      if (getEnclosingMethodAttribute() != null) {
+        getEnclosingMethodAttribute().collectIndexedItems(indexedItems);
       }
       for (InnerClassAttribute attribute : getInnerClasses()) {
         attribute.collectIndexedItems(indexedItems);
@@ -269,7 +273,7 @@
 
   @Override
   void collectMixedSectionItems(MixedSectionCollection mixedItems) {
-    assert getEnclosingMethod() == null;
+    assert getEnclosingMethodAttribute() == null;
     assert getInnerClasses().isEmpty();
     if (hasAnnotations()) {
       mixedItems.setAnnotationsDirectoryForClass(this, new DexAnnotationDirectory(this));
@@ -278,7 +282,7 @@
 
   @Override
   public void addDependencies(MixedSectionCollection collector) {
-    assert getEnclosingMethod() == null;
+    assert getEnclosingMethodAttribute() == null;
     assert getInnerClasses().isEmpty();
     // We only have a class data item if there are methods or fields.
     if (hasMethodsOrFields()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexReference.java b/src/main/java/com/android/tools/r8/graph/DexReference.java
index 42286db..c37c9de 100644
--- a/src/main/java/com/android/tools/r8/graph/DexReference.java
+++ b/src/main/java/com/android/tools/r8/graph/DexReference.java
@@ -3,7 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import java.util.Objects;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
@@ -13,6 +14,22 @@
  */
 public abstract class DexReference extends IndexedDexItem {
 
+  public abstract <T> T apply(
+      Function<DexType, T> classConsumer,
+      Function<DexField, T> fieldConsumer,
+      Function<DexMethod, T> methodConsumer);
+
+  public abstract void accept(
+      Consumer<DexType> classConsumer,
+      Consumer<DexField> fieldConsumer,
+      Consumer<DexMethod> methodConsumer);
+
+  public abstract <T> void accept(
+      BiConsumer<DexType, T> classConsumer,
+      BiConsumer<DexField, T> fieldConsumer,
+      BiConsumer<DexMethod, T> methodConsumer,
+      T arg);
+
   public boolean isDexType() {
     return false;
   }
@@ -49,22 +66,6 @@
     return DexItem.filter(stream, DexReference.class);
   }
 
-  public DexDefinition toDefinition(AppInfo appInfo) {
-    if (isDexType()) {
-      return appInfo.definitionFor(asDexType());
-    } else if (isDexField()) {
-      return appInfo.definitionFor(asDexField());
-    } else {
-      assert isDexMethod();
-      return appInfo.definitionFor(asDexMethod());
-    }
-  }
-
-  public static Stream<DexDefinition> mapToDefinition(
-      Stream<DexReference> references, AppInfo appInfo) {
-    return references.map(r -> r.toDefinition(appInfo)).filter(Objects::nonNull);
-  }
-
   private static <T extends DexReference> Stream<T> filter(
       Stream<DexReference> stream,
       Predicate<DexReference> pred,
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 01c5298..fc7ae3e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -24,14 +24,15 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
-import com.android.tools.r8.utils.Pair;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 public class DexType extends DexReference implements PresortedComparable<DexType> {
@@ -117,6 +118,31 @@
   }
 
   @Override
+  public <T> T apply(
+      Function<DexType, T> classConsumer,
+      Function<DexField, T> fieldConsumer,
+      Function<DexMethod, T> methodConsumer) {
+    return classConsumer.apply(this);
+  }
+
+  @Override
+  public void accept(
+      Consumer<DexType> classConsumer,
+      Consumer<DexField> fieldConsumer,
+      Consumer<DexMethod> methodConsumer) {
+    classConsumer.accept(this);
+  }
+
+  @Override
+  public <T> void accept(
+      BiConsumer<DexType, T> classConsumer,
+      BiConsumer<DexField, T> fieldConsumer,
+      BiConsumer<DexMethod, T> methodConsumer,
+      T arg) {
+    classConsumer.accept(this, arg);
+  }
+
+  @Override
   public String toSourceString() {
     if (toStringCache == null) {
       // TODO(ager): Pass in a ProguardMapReader to map names back to original names.
@@ -409,15 +435,4 @@
   public String getPackageName() {
     return DescriptorUtils.getPackageNameFromBinaryName(toBinaryName());
   }
-
-  public Pair<String, String> rewritingPrefixIn(Map<String, String> map) {
-    // TODO(b/134732760): Rewrite this to use descriptors and not Strings.
-    String javaClassName = this.toString();
-    for (String rewritePrefix : map.keySet()) {
-      if (javaClassName.startsWith(rewritePrefix)) {
-        return new Pair<>(rewritePrefix, map.get(rewritePrefix));
-      }
-    }
-    return null;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 5f0a349..3699d2e 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -73,24 +73,7 @@
     return classpathClasses;
   }
 
-  @Override
-  public DexDefinition definitionFor(DexReference reference) {
-    if (reference.isDexType()) {
-      return definitionFor(reference.asDexType());
-    }
-    if (reference.isDexMethod()) {
-      return definitionFor(reference.asDexMethod());
-    }
-    assert reference.isDexField();
-    return definitionFor(reference.asDexField());
-  }
-
-  @Override
-  public DexEncodedField definitionFor(DexField field) {
-    DexClass clazz = definitionFor(field.holder);
-    return clazz != null ? clazz.lookupField(field) : null;
-  }
-
+  @Deprecated
   @Override
   public DexEncodedMethod definitionFor(DexMethod method) {
     DexClass clazz = definitionFor(method.holder);
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index ea55ec4..0bcbc76 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -4,6 +4,8 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.shaking.KeepInfoCollection;
+import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableMap;
@@ -12,13 +14,11 @@
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
 import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
-import java.util.ArrayDeque;
-import java.util.Collections;
-import java.util.Deque;
+import java.util.ArrayList;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Function;
 import java.util.function.Supplier;
 
 /**
@@ -189,13 +189,6 @@
 
   public abstract RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method);
 
-  // Context sensitive graph lenses should override this method.
-  public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
-    DexMethod result = lookupMethod(method);
-    assert result != null;
-    return ImmutableSet.of(result);
-  }
-
   public abstract DexField lookupField(DexField field);
 
   public DexMethod lookupGetFieldForMethod(DexField field, DexMethod context) {
@@ -263,6 +256,14 @@
     return true;
   }
 
+  public <T extends DexReference> boolean assertPinnedNotModified(KeepInfoCollection keepInfo) {
+    List<DexReference> pinnedItems = new ArrayList<>();
+    keepInfo.forEachPinnedType(pinnedItems::add);
+    keepInfo.forEachPinnedMethod(pinnedItems::add);
+    keepInfo.forEachPinnedField(pinnedItems::add);
+    return assertReferencesNotModified(pinnedItems);
+  }
+
   public <T extends DexReference> boolean assertReferencesNotModified(Iterable<T> references) {
     for (DexReference reference : references) {
       if (reference.isDexField()) {
@@ -280,37 +281,40 @@
     return true;
   }
 
-  public ImmutableSet<DexReference> rewriteReferencesConservatively(Set<DexReference> original) {
-    ImmutableSet.Builder<DexReference> builder = ImmutableSet.builder();
-    for (DexReference item : original) {
-      if (item.isDexMethod()) {
-        DexMethod method = item.asDexMethod();
-        builder.addAll(lookupMethodInAllContexts(method));
-      } else {
-        builder.add(lookupReference(item));
-      }
+  public DexReference rewriteReference(DexReference reference) {
+    if (reference.isDexField()) {
+      return getRenamedFieldSignature(reference.asDexField());
     }
-    return builder.build();
+    if (reference.isDexMethod()) {
+      return getRenamedMethodSignature(reference.asDexMethod());
+    }
+    assert reference.isDexType();
+    return lookupType(reference.asDexType());
   }
 
-  public Object2BooleanMap<DexReference> rewriteReferencesConservatively(
-      Object2BooleanMap<DexReference> original) {
-    Object2BooleanMap<DexReference> result = new Object2BooleanArrayMap<>();
-    for (Object2BooleanMap.Entry<DexReference> entry : original.object2BooleanEntrySet()) {
-      DexReference item = entry.getKey();
-      if (item.isDexMethod()) {
-        DexMethod method = item.asDexMethod();
-        for (DexMethod candidate : lookupMethodInAllContexts(method)) {
-          result.put(candidate, entry.getBooleanValue());
-        }
-      } else {
-        result.put(lookupReference(item), entry.getBooleanValue());
-      }
+  public Set<DexReference> rewriteReferences(Set<DexReference> references) {
+    Set<DexReference> result = SetUtils.newIdentityHashSet(references.size());
+    for (DexReference reference : references) {
+      result.add(rewriteReference(reference));
     }
     return result;
   }
 
-  public ImmutableSortedSet<DexMethod> rewriteMethodsWithRenamedSignature(Set<DexMethod> methods) {
+  public <T> ImmutableMap<DexReference, T> rewriteReferenceKeys(Map<DexReference, T> map) {
+    ImmutableMap.Builder<DexReference, T> builder = ImmutableMap.builder();
+    map.forEach((reference, value) -> builder.put(rewriteReference(reference), value));
+    return builder.build();
+  }
+
+  public Object2BooleanMap<DexReference> rewriteReferenceKeys(Object2BooleanMap<DexReference> map) {
+    Object2BooleanMap<DexReference> result = new Object2BooleanArrayMap<>();
+    for (Object2BooleanMap.Entry<DexReference> entry : map.object2BooleanEntrySet()) {
+      result.put(rewriteReference(entry.getKey()), entry.getBooleanValue());
+    }
+    return result;
+  }
+
+  public ImmutableSortedSet<DexMethod> rewriteMethods(Set<DexMethod> methods) {
     ImmutableSortedSet.Builder<DexMethod> builder =
         new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare);
     for (DexMethod method : methods) {
@@ -319,25 +323,24 @@
     return builder.build();
   }
 
-  public ImmutableSortedSet<DexMethod> rewriteMethodsConservatively(Set<DexMethod> original) {
-    ImmutableSortedSet.Builder<DexMethod> builder =
+  public <T> ImmutableMap<DexField, T> rewriteFieldKeys(Map<DexField, T> map) {
+    ImmutableMap.Builder<DexField, T> builder = ImmutableMap.builder();
+    map.forEach((field, value) -> builder.put(getRenamedFieldSignature(field), value));
+    return builder.build();
+  }
+
+  public ImmutableSet<DexType> rewriteTypes(Set<DexType> types) {
+    ImmutableSortedSet.Builder<DexType> builder =
         new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare);
-    if (isContextFreeForMethods()) {
-      for (DexMethod item : original) {
-        builder.add(lookupMethod(item));
-      }
-    } else {
-      for (DexMethod item : original) {
-        builder.addAll(lookupMethodInAllContexts(item));
-      }
+    for (DexType type : types) {
+      builder.add(lookupType(type));
     }
     return builder.build();
   }
 
-  public static <T extends DexReference, S> ImmutableMap<T, S> rewriteReferenceKeys(
-      Map<T, S> original, Function<T, T> rewrite) {
-    ImmutableMap.Builder<T, S> builder = ImmutableMap.builder();
-    original.forEach((item, value) -> builder.put(rewrite.apply(item), value));
+  public <T> ImmutableMap<DexType, T> rewriteTypeKeys(Map<DexType, T> map) {
+    ImmutableMap.Builder<DexType, T> builder = ImmutableMap.builder();
+    map.forEach((type, value) -> builder.put(lookupType(type), value));
     return builder.build();
   }
 
@@ -380,51 +383,13 @@
           continue;
         }
         DexMethod originalMethod = getOriginalMethodSignature(method.method);
-        assert originalMethods.contains(originalMethod)
-                || verifyIsBridgeMethod(
-                    originalMethod, originalApplication, originalMethods, dexItemFactory)
-            : "Unable to map method `"
-                + originalMethod.toSourceString()
-                + "` back to original program";
+        assert originalMethods.contains(originalMethod);
       }
     }
 
     return true;
   }
 
-  // Check if `method` is a bridge method for a method that is in the original application.
-  // This is needed because member rebinding synthesizes bridge methods for visibility.
-  private static boolean verifyIsBridgeMethod(
-      DexMethod method,
-      DexApplication originalApplication,
-      Set<DexMethod> originalMethods,
-      DexItemFactory dexItemFactory) {
-    Deque<DexType> worklist = new ArrayDeque<>();
-    Set<DexType> visited = Sets.newIdentityHashSet();
-    worklist.add(method.holder);
-    while (!worklist.isEmpty()) {
-      DexType holder = worklist.removeFirst();
-      if (!visited.add(holder)) {
-        // Already visited previously.
-        continue;
-      }
-      DexMethod targetMethod = dexItemFactory.createMethod(holder, method.proto, method.name);
-      if (originalMethods.contains(targetMethod)) {
-        return true;
-      }
-      // Stop traversing upwards if we reach the Object.
-      if (holder == dexItemFactory.objectType) {
-        continue;
-      }
-      DexClass clazz = originalApplication.definitionFor(holder);
-      if (clazz != null) {
-        worklist.add(clazz.superType);
-        Collections.addAll(worklist, clazz.interfaces.values);
-      }
-    }
-    return false;
-  }
-
   private static class IdentityGraphLense extends GraphLense {
 
     private static IdentityGraphLense INSTANCE = new IdentityGraphLense();
@@ -728,15 +693,6 @@
     }
 
     @Override
-    public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
-      Set<DexMethod> result = Sets.newIdentityHashSet();
-      for (DexMethod previous : previousLense.lookupMethodInAllContexts(method)) {
-        result.add(methodMap.getOrDefault(previous, previous));
-      }
-      return result;
-    }
-
-    @Override
     public DexField lookupField(DexField field) {
       DexField previous = previousLense.lookupField(field);
       return fieldMap.getOrDefault(previous, previous);
diff --git a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
index b40f328..74f9880 100644
--- a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
@@ -85,8 +85,8 @@
     DexType context = getOuter();
     if (context == null) {
       DexClass inner = appInfo.definitionFor(getInner());
-      if (inner != null && inner.getEnclosingMethod() != null) {
-        EnclosingMethodAttribute enclosingMethodAttribute = inner.getEnclosingMethod();
+      if (inner != null && inner.getEnclosingMethodAttribute() != null) {
+        EnclosingMethodAttribute enclosingMethodAttribute = inner.getEnclosingMethodAttribute();
         if (enclosingMethodAttribute.getEnclosingClass() != null) {
           context = enclosingMethodAttribute.getEnclosingClass();
         } else {
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 1f04cca..7936e33 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -110,7 +110,7 @@
     if (application.options.getProguardConfiguration() != null) {
       ProguardKeepAttributes keep =
           application.options.getProguardConfiguration().getKeepAttributes();
-      if (!keep.sourceFile && !keep.sourceDebugExtension) {
+      if (!keep.sourceFile && !keep.sourceDebugExtension && !keep.methodParameters) {
         parsingOptions |= SKIP_DEBUG;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 06f8c42..6c93947 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -1008,8 +1008,9 @@
             || keep.localVariableTypeTable
             || reachabilitySensitive;
     boolean lineInfo = keep.lineNumberTable;
+    boolean methodParaeters = keep.methodParameters;
 
-    if (!localsInfo && !lineInfo) {
+    if (!localsInfo && !lineInfo && !methodParaeters) {
       parsingOptions |= ClassReader.SKIP_DEBUG;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
index fbb42a9..01d86a1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -7,8 +7,10 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -106,8 +108,9 @@
       }
       AbstractValue abstractValue = root.getAbstractValue(appView, context);
       if (abstractValue.isSingleFieldValue()) {
-        DexEncodedField field =
-            appView.definitionFor(abstractValue.asSingleFieldValue().getField());
+        DexField fieldReference = abstractValue.asSingleFieldValue().getField();
+        DexClass holder = appView.definitionForHolder(fieldReference);
+        DexEncodedField field = fieldReference.lookupOnClass(holder);
         if (field != null && field.isEnum()) {
           return false;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
index be72b24..54d5160 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.constant;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
@@ -14,6 +16,7 @@
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.StringSwitch;
 import com.android.tools.r8.ir.code.Value;
+import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Deque;
@@ -21,6 +24,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Implementation of Sparse Conditional Constant Propagation from the paper of Wegman and Zadeck
@@ -29,6 +33,7 @@
  */
 public class SparseConditionalConstantPropagation {
 
+  private final AppView<?> appView;
   private final IRCode code;
   private final Map<Value, LatticeElement> mapping = new HashMap<>();
   private final Deque<Value> ssaEdges = new LinkedList<>();
@@ -37,7 +42,8 @@
   private final BitSet[] executableFlowEdges;
   private final BitSet visitedBlocks;
 
-  public SparseConditionalConstantPropagation(IRCode code) {
+  public SparseConditionalConstantPropagation(AppView<?> appView, IRCode code) {
+    this.appView = appView;
     this.code = code;
     nextBlockNumber = code.getHighestBlockNumber() + 1;
     executableFlowEdges = new BitSet[nextBlockNumber];
@@ -45,7 +51,6 @@
   }
 
   public void run() {
-
     BasicBlock firstBlock = code.entryBlock();
     visitInstructions(firstBlock);
 
@@ -77,8 +82,8 @@
   }
 
   private void rewriteCode() {
+    Set<Value> affectedValues = Sets.newIdentityHashSet();
     List<BasicBlock> blockToAnalyze = new ArrayList<>();
-
     mapping.entrySet().stream()
         .filter(entry -> entry.getValue().isConst())
         .forEach(
@@ -86,6 +91,7 @@
               Value value = entry.getKey();
               ConstNumber evaluatedConst = entry.getValue().asConst().getConstNumber();
               if (value.definition != evaluatedConst) {
+                value.addAffectedValuesTo(affectedValues);
                 if (value.isPhi()) {
                   // D8 relies on dead code removal to get rid of the dead phi itself.
                   if (value.hasAnyUsers()) {
@@ -112,11 +118,12 @@
                 }
               }
             });
-
     for (BasicBlock block : blockToAnalyze) {
       block.deduplicatePhis();
     }
-
+    if (!affectedValues.isEmpty()) {
+      new TypeAnalysis(appView).narrowing(affectedValues);
+    }
     code.removeAllDeadAndTrivialPhis();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index fbb6443..1481061 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -64,8 +64,13 @@
 
   FieldAssignmentTracker(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
-    this.fieldAccessGraph = new FieldAccessGraph(appView);
-    this.objectAllocationGraph = new ObjectAllocationGraph(appView);
+    this.fieldAccessGraph = new FieldAccessGraph();
+    this.objectAllocationGraph = new ObjectAllocationGraph();
+  }
+
+  public void initialize() {
+    fieldAccessGraph.initialize(appView);
+    objectAllocationGraph.initialize(appView);
     initializeAbstractInstanceFieldValues();
   }
 
@@ -308,10 +313,11 @@
     private final Reference2IntMap<DexEncodedField> pendingFieldWrites =
         new Reference2IntOpenHashMap<>();
 
-    FieldAccessGraph(AppView<AppInfoWithLiveness> appView) {
+    FieldAccessGraph() {}
+
+    public void initialize(AppView<AppInfoWithLiveness> appView) {
       FieldAccessInfoCollection<?> fieldAccessInfoCollection =
           appView.appInfo().getFieldAccessInfoCollection();
-      fieldAccessInfoCollection.flattenAccessContexts();
       fieldAccessInfoCollection.forEach(
           info -> {
             DexEncodedField field =
@@ -356,7 +362,9 @@
     private final Reference2IntMap<DexProgramClass> pendingObjectAllocations =
         new Reference2IntOpenHashMap<>();
 
-    ObjectAllocationGraph(AppView<AppInfoWithLiveness> appView) {
+    ObjectAllocationGraph() {}
+
+    public void initialize(AppView<AppInfoWithLiveness> appView) {
       ObjectAllocationInfoCollection objectAllocationInfos =
           appView.appInfo().getObjectAllocationInfoCollection();
       objectAllocationInfos.forEachClassWithKnownAllocationSites(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
index a2a932c..9fe885d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
@@ -5,7 +5,9 @@
 package com.android.tools.r8.ir.analysis.fieldvalueanalysis;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SetUtils;
@@ -71,12 +73,14 @@
     assert !isEmpty();
     ConcreteMutableFieldSet rewrittenSet = new ConcreteMutableFieldSet();
     for (DexEncodedField field : fields) {
-      DexEncodedField rewrittenField = appView.definitionFor(lens.lookupField(field.field));
+      DexField rewrittenFieldReference = lens.lookupField(field.field);
+      DexClass holder = appView.definitionForHolder(rewrittenFieldReference);
+      DexEncodedField rewrittenField = rewrittenFieldReference.lookupOnClass(holder);
       if (rewrittenField == null) {
         assert false;
         continue;
       }
-      rewrittenSet.add(field);
+      rewrittenSet.add(rewrittenField);
     }
     return rewrittenSet;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
index c64dff4..667c1ff 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
@@ -26,6 +25,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.DefaultTreePrunerConfiguration;
 import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.shaking.TreePrunerConfiguration;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
@@ -190,7 +190,7 @@
           resolutionResult.asSuccessfulResolution().getResolutionPair().asProgramField();
       return field != null
           && isDeadProtoExtensionField(
-              field, appInfo.getFieldAccessInfoCollection(), appInfo.getPinnedItems());
+              field, appInfo.getFieldAccessInfoCollection(), appInfo.getKeepInfo());
     }
     return false;
   }
@@ -198,8 +198,8 @@
   public boolean isDeadProtoExtensionField(
       ProgramField field,
       FieldAccessInfoCollection<?> fieldAccessInfoCollection,
-      Set<DexReference> pinnedItems) {
-    if (pinnedItems.contains(field.getReference())) {
+      KeepInfoCollection keepInfo) {
+    if (keepInfo.getFieldInfo(field).isPinned()) {
       return false;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index deadfa0..c589048 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -4,41 +4,48 @@
 
 package com.android.tools.r8.ir.analysis.proto;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo;
-import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 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.CheckCast;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.Switch;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
-import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
-import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer;
 import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.ir.optimize.inliner.FixedInliningReasonStrategy;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.EnqueuerWorklist;
+import com.android.tools.r8.utils.ObjectUtils;
 import com.android.tools.r8.utils.PredicateSet;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
@@ -53,6 +60,7 @@
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final ProtoReferences references;
+  private final boolean enableAggressiveBuilderOptimization;
 
   private final Map<DexProgramClass, ProgramMethod> builders = new IdentityHashMap<>();
 
@@ -60,11 +68,98 @@
       AppView<? extends AppInfoWithClassHierarchy> appView, ProtoReferences references) {
     this.appView = appView;
     this.references = references;
+    this.enableAggressiveBuilderOptimization = computeEnableAggressiveBuilderOptimization();
+    // If this fails it is likely an unsupported version of the protobuf library.
+    assert enableAggressiveBuilderOptimization;
+  }
+
+  private boolean computeEnableAggressiveBuilderOptimization() {
+    boolean unexpectedGeneratedMessageLiteBuilder =
+        ObjectUtils.getBooleanOrElse(
+            appView
+                .appInfo()
+                .definitionForWithoutExistenceAssert(references.generatedMessageLiteBuilderType),
+            clazz -> clazz.getMethodCollection().hasMethods(DexEncodedMethod::isAbstract),
+            true);
+    boolean unexpectedGeneratedMessageLiteExtendableBuilder =
+        ObjectUtils.getBooleanOrElse(
+            appView
+                .appInfo()
+                .definitionForWithoutExistenceAssert(
+                    references.generatedMessageLiteExtendableBuilderType),
+            clazz -> clazz.getMethodCollection().hasMethods(DexEncodedMethod::isAbstract),
+            true);
+    if (unexpectedGeneratedMessageLiteBuilder) {
+      appView
+          .options()
+          .reporter
+          .warning(
+              "Unexpected implementation of `"
+                  + references.generatedMessageLiteBuilderType.toSourceString()
+                  + "`: disabling aggressive protobuf builder optimization.");
+      return false;
+    }
+    if (unexpectedGeneratedMessageLiteExtendableBuilder) {
+      appView
+          .options()
+          .reporter
+          .warning(
+              "Unexpected implementation of `"
+                  + references.generatedMessageLiteExtendableBuilderType.toSourceString()
+                  + "`: disabling aggressive protobuf builder optimization.");
+      return false;
+    }
+    return true;
+  }
+
+  public EnqueuerAnalysis createEnqueuerAnalysis() {
+    Set<DexProgramClass> seen = Sets.newIdentityHashSet();
+    return new EnqueuerAnalysis() {
+      @Override
+      public void notifyFixpoint(Enqueuer enqueuer, EnqueuerWorklist worklist, Timing timing) {
+        builders.forEach(
+            (builder, dynamicMethod) -> {
+              if (seen.add(builder)) {
+                // This builder class is never used in the program except from dynamicMethod(),
+                // which creates an instance of the builder. Instead of creating an instance of the
+                // builder class, we just instantiate the parent builder class. For this to work,
+                // we make the parent builder non-abstract.
+                DexProgramClass superClass =
+                    asProgramClassOrNull(appView.definitionFor(builder.superType));
+                assert superClass != null;
+                superClass.accessFlags.demoteFromAbstract();
+                if (superClass.type == references.generatedMessageLiteBuilderType) {
+                  // Manually trace `new GeneratedMessageLite.Builder(DEFAULT_INSTANCE)` since we
+                  // haven't rewritten the code yet.
+                  worklist.enqueueTraceNewInstanceAction(
+                      references.generatedMessageLiteBuilderType, dynamicMethod);
+                  worklist.enqueueTraceInvokeDirectAction(
+                      references.generatedMessageLiteBuilderMethods.constructorMethod,
+                      dynamicMethod);
+                } else {
+                  assert superClass.type == references.generatedMessageLiteExtendableBuilderType;
+                  // Manually trace `new GeneratedMessageLite.ExtendableBuilder(DEFAULT_INSTANCE)`
+                  // since we haven't rewritten the code yet.
+                  worklist.enqueueTraceNewInstanceAction(
+                      references.generatedMessageLiteExtendableBuilderType, dynamicMethod);
+                  worklist.enqueueTraceInvokeDirectAction(
+                      references.generatedMessageLiteExtendableBuilderMethods.constructorMethod,
+                      dynamicMethod);
+                }
+                worklist.enqueueTraceStaticFieldRead(
+                    references.getDefaultInstanceField(dynamicMethod.getHolder()), dynamicMethod);
+              }
+            });
+      }
+    };
   }
 
   /** Returns true if an action was deferred. */
   public boolean deferDeadProtoBuilders(
       DexProgramClass clazz, ProgramMethod method, BooleanSupplier register) {
+    if (!enableAggressiveBuilderOptimization) {
+      return false;
+    }
     DexEncodedMethod definition = method.getDefinition();
     if (references.isDynamicMethod(definition) && references.isGeneratedMessageLiteBuilder(clazz)) {
       if (register.getAsBoolean()) {
@@ -77,40 +172,79 @@
   }
 
   /**
-   * Reprocesses each dynamicMethod() that references a dead builder to remove the dead builder
+   * Reprocesses each dynamicMethod() that references a dead builder to rewrite the dead builder
    * references.
    */
-  public void removeDeadBuilderReferencesFromDynamicMethods(
+  public void rewriteDeadBuilderReferencesFromDynamicMethods(
       AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
       throws ExecutionException {
+    if (builders.isEmpty()) {
+      return;
+    }
     timing.begin("Remove dead builder references");
     AppInfoWithLiveness appInfo = appView.appInfo();
     IRConverter converter = new IRConverter(appView, Timing.empty());
-    CodeRewriter codeRewriter = new CodeRewriter(appView, converter);
-    MethodToInvokeSwitchCaseAnalyzer switchCaseAnalyzer = new MethodToInvokeSwitchCaseAnalyzer();
-    if (switchCaseAnalyzer.isInitialized()) {
-      ThreadUtils.processItems(
-          builders.entrySet(),
-          entry -> {
-            if (!appInfo.isLiveProgramClass(entry.getKey())) {
-              removeDeadBuilderReferencesFromDynamicMethod(
-                  appView, entry.getValue(), converter, codeRewriter, switchCaseAnalyzer);
-            }
-          },
-          executorService);
-    }
+    ThreadUtils.processMap(
+        builders,
+        (builder, dynamicMethod) -> {
+          if (!appInfo.isLiveProgramClass(builder)) {
+            rewriteDeadBuilderReferencesFromDynamicMethod(
+                appView, builder, dynamicMethod, converter);
+          }
+        },
+        executorService);
     builders.clear();
     timing.end(); // Remove dead builder references
   }
 
-  private void removeDeadBuilderReferencesFromDynamicMethod(
+  private void rewriteDeadBuilderReferencesFromDynamicMethod(
       AppView<AppInfoWithLiveness> appView,
+      DexProgramClass builder,
       ProgramMethod dynamicMethod,
-      IRConverter converter,
-      CodeRewriter codeRewriter,
-      SwitchCaseAnalyzer switchCaseAnalyzer) {
+      IRConverter converter) {
     IRCode code = dynamicMethod.buildIR(appView);
-    codeRewriter.rewriteSwitch(code, switchCaseAnalyzer);
+    InstructionListIterator instructionIterator = code.instructionListIterator();
+
+    assert builder.superType == references.generatedMessageLiteBuilderType
+        || builder.superType == references.generatedMessageLiteExtendableBuilderType;
+
+    DexField defaultInstanceField = references.getDefaultInstanceField(dynamicMethod.getHolder());
+    Value builderValue =
+        code.createValue(ClassTypeElement.create(builder.superType, definitelyNotNull(), appView));
+    Value defaultInstanceValue =
+        code.createValue(ClassTypeElement.create(defaultInstanceField.type, maybeNull(), appView));
+
+    // Replace `new Message.Builder()` by `new GeneratedMessageLite.Builder()`
+    // (or `new GeneratedMessageLite.ExtendableBuilder()`).
+    NewInstance newInstance =
+        instructionIterator.nextUntil(
+            instruction ->
+                instruction.isNewInstance() && instruction.asNewInstance().clazz == builder.type);
+    assert newInstance != null;
+    instructionIterator.replaceCurrentInstruction(new NewInstance(builder.superType, builderValue));
+
+    // Replace `builder.<init>()` by `builder.<init>(Message.DEFAULT_INSTANCE)`.
+    //
+    // We may also see an accessibility bridge constructor, because the Builder constructor is
+    // private. The accessibility bridge takes null as an argument.
+    InvokeDirect constructorInvoke =
+        instructionIterator.nextUntil(
+            instruction -> {
+              assert instruction.isInvokeDirect() || instruction.isConstNumber();
+              return instruction.isInvokeDirect();
+            });
+    assert constructorInvoke != null;
+    instructionIterator.replaceCurrentInstruction(
+        new StaticGet(defaultInstanceValue, defaultInstanceField));
+    instructionIterator.setInsertionPosition(constructorInvoke.getPosition());
+    instructionIterator.add(
+        new InvokeDirect(
+            builder.superType == references.generatedMessageLiteBuilderType
+                ? references.generatedMessageLiteBuilderMethods.constructorMethod
+                : references.generatedMessageLiteExtendableBuilderMethods.constructorMethod,
+            null,
+            ImmutableList.of(builderValue, defaultInstanceValue)));
+
     converter.removeDeadCodeAndFinalizeIR(
         dynamicMethod, code, OptimizationFeedbackSimple.getInstance(), Timing.empty());
   }
@@ -232,51 +366,6 @@
     }
   }
 
-  private class MethodToInvokeSwitchCaseAnalyzer extends SwitchCaseAnalyzer {
-
-    private final int newBuilderOrdinal;
-
-    private MethodToInvokeSwitchCaseAnalyzer() {
-      EnumValueInfoMap enumValueInfoMap =
-          appView.appInfo().withLiveness().getEnumValueInfoMap(references.methodToInvokeType);
-      if (enumValueInfoMap != null) {
-        EnumValueInfo newBuilderValueInfo =
-            enumValueInfoMap.getEnumValueInfo(references.methodToInvokeMembers.newBuilderField);
-        if (newBuilderValueInfo != null) {
-          newBuilderOrdinal = newBuilderValueInfo.ordinal;
-          return;
-        }
-      }
-      newBuilderOrdinal = -1;
-    }
-
-    public boolean isInitialized() {
-      return newBuilderOrdinal >= 0;
-    }
-
-    @Override
-    public boolean switchCaseIsUnreachable(Switch theSwitch, int index) {
-      if (theSwitch.isStringSwitch()) {
-        assert false : "Unexpected string-switch instruction in dynamicMethod()";
-        return false;
-      }
-      if (index != newBuilderOrdinal) {
-        return false;
-      }
-      Value switchValue = theSwitch.value();
-      if (!switchValue.isDefinedByInstructionSatisfying(Instruction::isInvokeVirtual)) {
-        return false;
-      }
-      InvokeVirtual definition = switchValue.definition.asInvokeVirtual();
-      if (definition.getInvokedMethod() != appView.dexItemFactory().enumMethods.ordinal) {
-        return false;
-      }
-      TypeElement enumType = definition.getReceiver().getType();
-      return enumType.isClassType()
-          && enumType.asClassType().getClassType() == references.methodToInvokeType;
-    }
-  }
-
   private static class RootSetExtension {
 
     private final AppView<? extends AppInfoWithClassHierarchy> appView;
@@ -305,8 +394,9 @@
     void extend(SubtypingInfo subtypingInfo) {
       alwaysClassInlineGeneratedMessageLiteBuilders();
 
-      // GeneratedMessageLite heuristics.
+      // MessageLite and GeneratedMessageLite heuristics.
       alwaysInlineCreateBuilderFromGeneratedMessageLite();
+      neverMergeMessageLite();
 
       // * extends GeneratedMessageLite heuristics.
       bypassClinitforInliningNewBuilderMethods(subtypingInfo);
@@ -353,5 +443,11 @@
       neverMerge.add(references.generatedMessageLiteBuilderType);
       neverMerge.add(references.generatedMessageLiteExtendableBuilderType);
     }
+
+    private void neverMergeMessageLite() {
+      // MessageLite is used in several signatures that we use for recognizing methods, so don't
+      // allow it to me merged.
+      neverMerge.add(references.messageLiteType);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
index ca4ca1e..e8770c7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
@@ -43,11 +43,19 @@
       return Reason.ALWAYS;
     }
     return references.isDynamicMethod(target) || references.isDynamicMethodBridge(target)
-        ? computeInliningReasonForDynamicMethod(invoke)
+        ? computeInliningReasonForDynamicMethod(invoke, target, context)
         : parent.computeInliningReason(invoke, target, context);
   }
 
-  private Reason computeInliningReasonForDynamicMethod(InvokeMethod invoke) {
+  private Reason computeInliningReasonForDynamicMethod(
+      InvokeMethod invoke, ProgramMethod target, ProgramMethod context) {
+    // Do not allow inlining of dynamicMethod() into a proto library class. This should only happen
+    // if there is exactly one proto message in the program, since we would otherwise not be able
+    // to conclude a single target.
+    if (references.isDynamicMethod(target) && references.isProtoLibraryClass(context.getHolder())) {
+      return Reason.NEVER;
+    }
+
     Value methodToInvokeValue =
         invoke
             .inValues()
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
index 861c0cd..8fd43ee 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
@@ -17,6 +17,8 @@
 
 public class ProtoReferences {
 
+  private final DexItemFactory dexItemFactory;
+
   public final DexType enumLiteMapType;
   public final DexType extendableMessageType;
   public final DexType extensionDescriptorType;
@@ -25,6 +27,7 @@
   public final DexType generatedMessageLiteType;
   public final DexType generatedMessageLiteBuilderType;
   public final DexType generatedMessageLiteExtendableBuilderType;
+  public final DexType generatedMessageLiteExtendableMessageType;
   public final DexType rawMessageInfoType;
   public final DexType messageLiteType;
   public final DexType methodToInvokeType;
@@ -37,10 +40,13 @@
       generatedMessageLiteExtendableBuilderMethods;
   public final MethodToInvokeMembers methodToInvokeMembers;
 
+  public final DexString defaultInstanceFieldName;
   public final DexString dynamicMethodName;
   public final DexString findLiteExtensionByNumberName;
   public final DexString newBuilderMethodName;
 
+  public final DexString protobufPackageDescriptorPrefix;
+
   public final DexProto dynamicMethodProto;
   public final DexProto findLiteExtensionByNumberProto;
 
@@ -49,6 +55,8 @@
   public final DexMethod rawMessageInfoConstructor;
 
   public ProtoReferences(DexItemFactory factory) {
+    dexItemFactory = factory;
+
     // Types.
     enumLiteMapType = factory.createType("Lcom/google/protobuf/Internal$EnumLiteMap;");
     extendableMessageType =
@@ -63,6 +71,8 @@
         factory.createType("Lcom/google/protobuf/GeneratedMessageLite$Builder;");
     generatedMessageLiteExtendableBuilderType =
         factory.createType("Lcom/google/protobuf/GeneratedMessageLite$ExtendableBuilder;");
+    generatedMessageLiteExtendableMessageType =
+        factory.createType("Lcom/google/protobuf/GeneratedMessageLite$ExtendableMessage;");
     rawMessageInfoType = factory.createType("Lcom/google/protobuf/RawMessageInfo;");
     messageLiteType = factory.createType("Lcom/google/protobuf/MessageLite;");
     methodToInvokeType =
@@ -70,10 +80,14 @@
     wireFormatFieldType = factory.createType("Lcom/google/protobuf/WireFormat$FieldType;");
 
     // Names.
+    defaultInstanceFieldName = factory.createString("DEFAULT_INSTANCE");
     dynamicMethodName = factory.createString("dynamicMethod");
     findLiteExtensionByNumberName = factory.createString("findLiteExtensionByNumber");
     newBuilderMethodName = factory.createString("newBuilder");
 
+    // Other names.
+    protobufPackageDescriptorPrefix = factory.createString("Lcom/google/protobuf/");
+
     // Protos.
     dynamicMethodProto =
         factory.createProto(
@@ -105,6 +119,10 @@
     methodToInvokeMembers = new MethodToInvokeMembers(factory);
   }
 
+  public DexField getDefaultInstanceField(DexProgramClass holder) {
+    return dexItemFactory.createField(holder.type, holder.type, defaultInstanceFieldName);
+  }
+
   public boolean isAbstractGeneratedMessageLiteBuilder(DexProgramClass clazz) {
     return clazz.type == generatedMessageLiteBuilderType
         || clazz.type == generatedMessageLiteExtendableBuilderType;
@@ -123,7 +141,8 @@
   }
 
   public boolean isDynamicMethodBridge(DexMethod method) {
-    return method == generatedMessageLiteMethods.dynamicMethodBridgeMethod;
+    return method == generatedMessageLiteMethods.dynamicMethodBridgeMethod
+        || method == generatedMessageLiteMethods.dynamicMethodBridgeMethodWithObject;
   }
 
   public boolean isDynamicMethodBridge(DexEncodedMethod method) {
@@ -154,6 +173,10 @@
     return method.match(newMessageInfoMethod) || method == rawMessageInfoConstructor;
   }
 
+  public boolean isProtoLibraryClass(DexProgramClass clazz) {
+    return clazz.type.descriptor.startsWith(protobufPackageDescriptorPrefix);
+  }
+
   public class GeneratedExtensionMethods {
 
     public final DexMethod constructor;
@@ -192,6 +215,7 @@
 
     public final DexMethod createBuilderMethod;
     public final DexMethod dynamicMethodBridgeMethod;
+    public final DexMethod dynamicMethodBridgeMethodWithObject;
     public final DexMethod isInitializedMethod;
     public final DexMethod newRepeatedGeneratedExtension;
     public final DexMethod newSingularGeneratedExtension;
@@ -207,6 +231,12 @@
               generatedMessageLiteType,
               dexItemFactory.createProto(dexItemFactory.objectType, methodToInvokeType),
               "dynamicMethod");
+      dynamicMethodBridgeMethodWithObject =
+          dexItemFactory.createMethod(
+              generatedMessageLiteType,
+              dexItemFactory.createProto(
+                  dexItemFactory.objectType, methodToInvokeType, dexItemFactory.objectType),
+              "dynamicMethod");
       isInitializedMethod =
           dexItemFactory.createMethod(
               generatedMessageLiteType,
@@ -241,7 +271,7 @@
     }
   }
 
-  class GeneratedMessageLiteBuilderMethods {
+  public class GeneratedMessageLiteBuilderMethods {
 
     public final DexMethod buildPartialMethod;
     public final DexMethod constructorMethod;
@@ -260,9 +290,10 @@
     }
   }
 
-  class GeneratedMessageLiteExtendableBuilderMethods {
+  public class GeneratedMessageLiteExtendableBuilderMethods {
 
     public final DexMethod buildPartialMethod;
+    public final DexMethod constructorMethod;
 
     private GeneratedMessageLiteExtendableBuilderMethods(DexItemFactory dexItemFactory) {
       buildPartialMethod =
@@ -270,6 +301,12 @@
               generatedMessageLiteExtendableBuilderType,
               dexItemFactory.createProto(extendableMessageType),
               "buildPartial");
+      constructorMethod =
+          dexItemFactory.createMethod(
+              generatedMessageLiteExtendableBuilderType,
+              dexItemFactory.createProto(
+                  dexItemFactory.voidType, generatedMessageLiteExtendableMessageType),
+              dexItemFactory.constructorMethodName);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
index 3f0d75a..ed2cdd8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 
 public class AlwaysMaterializingDefinition extends ConstInstruction {
 
@@ -29,9 +30,9 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
+  public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
     // This instruction may never be considered dead as it must remain.
-    return false;
+    return DeadInstructionResult.notDead();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
index 3cd25ea..45fa439 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 
@@ -30,8 +31,8 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return false;
+  public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
+    return DeadInstructionResult.notDead();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
index 4b99975..21b02ba 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 
@@ -29,9 +30,9 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
+  public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
     // This instruction may never be considered dead as it must remain.
-    return false;
+    return DeadInstructionResult.notDead();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index 5b68bf5..fbca901 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.Set;
@@ -62,11 +63,11 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appview, IRCode code) {
+  public DeadInstructionResult canBeDeadCode(AppView<?> appview, IRCode code) {
     // Never remove argument instructions. That would change the signature of the method.
     // TODO(b/65810338): If we can tell that a method never uses an argument we might be able to
     // rewrite the signature and call-sites.
-    return false;
+    return DeadInstructionResult.notDead();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index e9f074b..4f0995b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -84,11 +84,6 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.context());
-  }
-
-  @Override
   public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) {
     if (super.identicalAfterRegisterAllocation(other, allocator)) {
       // The array length instruction doesn't carry the element type. The art verifier doesn't
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 860473c..9dae2a8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -191,11 +191,6 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.context());
-  }
-
-  @Override
   public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) {
     // We cannot share ArrayPut instructions without knowledge of the type of the array input.
     // If multiple primitive array types flow to the same ArrayPut instruction the art verifier
diff --git a/src/main/java/com/android/tools/r8/ir/code/Assume.java b/src/main/java/com/android/tools/r8/ir/code/Assume.java
index 117af7b..7aed1d9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Assume.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Assume.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
@@ -16,7 +15,6 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
-import com.android.tools.r8.utils.BooleanUtils;
 import java.util.Objects;
 import java.util.Set;
 
@@ -25,11 +23,11 @@
   private static final String ERROR_MESSAGE =
       "Expected Assume instructions to be removed after IR processing.";
 
-  private final DynamicTypeAssumption dynamicTypeAssumption;
+  private DynamicTypeAssumption dynamicTypeAssumption;
   private final NonNullAssumption nonNullAssumption;
   private final Instruction origin;
 
-  private Assume(
+  public Assume(
       DynamicTypeAssumption dynamicTypeAssumption,
       NonNullAssumption nonNullAssumption,
       Value dest,
@@ -38,9 +36,6 @@
       AppView<?> appView) {
     super(dest, src);
     assert dynamicTypeAssumption != null || nonNullAssumption != null;
-    assert BooleanUtils.intValue(dynamicTypeAssumption != null)
-            + BooleanUtils.intValue(nonNullAssumption != null)
-        == 1;
     assert dynamicTypeAssumption == null
         || dynamicTypeAssumption.verifyCorrectnessOfValues(dest, src, appView);
     assert nonNullAssumption == null
@@ -78,7 +73,7 @@
   }
 
   public boolean verifyInstructionIsNeeded(AppView<?> appView) {
-    if (isAssumeDynamicType()) {
+    if (hasDynamicTypeAssumption()) {
       assert dynamicTypeAssumption.verifyCorrectnessOfValues(outValue(), src(), appView);
     }
     return true;
@@ -112,13 +107,7 @@
 
   @Override
   public String getInstructionName() {
-    if (isAssumeDynamicType()) {
-      return "AssumeDynamicType";
-    }
-    if (isAssumeNonNull()) {
-      return "AssumeNonNull";
-    }
-    throw new Unimplemented();
+    return "Assume";
   }
 
   @Override
@@ -131,13 +120,15 @@
     return this;
   }
 
-  @Override
-  public boolean isAssumeDynamicType() {
+  public boolean hasDynamicTypeAssumption() {
     return dynamicTypeAssumption != null;
   }
 
-  @Override
-  public boolean isAssumeNonNull() {
+  public void unsetDynamicTypeAssumption() {
+    dynamicTypeAssumption = null;
+  }
+
+  public boolean hasNonNullAssumption() {
     return nonNullAssumption != null;
   }
 
@@ -149,7 +140,7 @@
     if (outType.isPrimitiveType()) {
       return false;
     }
-    if (isAssumeDynamicType()) {
+    if (hasDynamicTypeAssumption()) {
       outType = dynamicTypeAssumption.getDynamicUpperBoundType();
     }
     if (appView.appInfo().hasLiveness()) {
@@ -220,14 +211,11 @@
 
   @Override
   public TypeElement evaluate(AppView<?> appView) {
-    if (isAssumeDynamicType()) {
-      return src().getType();
-    }
-    if (isAssumeNonNull()) {
+    if (hasNonNullAssumption()) {
       assert src().getType().isReferenceType();
       return src().getType().asReferenceType().asMeetWithNotNull();
     }
-    throw new Unimplemented();
+    return src().getType();
   }
 
   @Override
@@ -255,15 +243,14 @@
     assert super.verifyTypes(appView);
 
     TypeElement inType = src().getType();
+    assert inType.isReferenceType() : inType;
+
     TypeElement outType = getOutType();
-    if (isAssumeDynamicType()) {
-      assert inType.isReferenceType() : inType;
-      assert outType.equals(inType)
+    if (hasNonNullAssumption()) {
+      assert inType.isNullType() || outType.equals(inType.asReferenceType().asMeetWithNotNull())
           : "At " + this + System.lineSeparator() + outType + " != " + inType;
     } else {
-      assert isAssumeNonNull() : this;
-      assert inType.isReferenceType() : inType;
-      assert inType.isNullType() || outType.equals(inType.asReferenceType().asMeetWithNotNull())
+      assert outType.equals(inType)
           : "At " + this + System.lineSeparator() + outType + " != " + inType;
     }
     return true;
@@ -276,21 +263,21 @@
     //     assumption became "truth."
     //   2) invoke-interface could be devirtualized, while its dynamic type and/or non-null receiver
     //     are still valid.
-    String originString =
-        origin.hasBlock() ? " (origin: `" + origin.toString() + "`)" : " (obsolete origin)";
-    if (isAssumeNonNull()) {
-      return super.toString() + originString;
+    StringBuilder builder = new StringBuilder(super.toString());
+    if (hasNonNullAssumption()) {
+      builder.append("; not null");
     }
-    if (isAssumeDynamicType()) {
-      return super.toString()
-          + "; upper bound: "
-          + dynamicTypeAssumption.dynamicUpperBoundType
-          + (dynamicTypeAssumption.dynamicLowerBoundType != null
-              ? "; lower bound: " + dynamicTypeAssumption.dynamicLowerBoundType
-              : "")
-          + originString;
+    if (hasDynamicTypeAssumption()) {
+      if (hasOutValue()) {
+        if (!dynamicTypeAssumption.dynamicUpperBoundType.equalUpToNullability(outValue.getType())) {
+          builder.append("; upper bound: ").append(dynamicTypeAssumption.dynamicUpperBoundType);
+        }
+      }
+      if (dynamicTypeAssumption.dynamicLowerBoundType != null) {
+        builder.append("; lower bound: ").append(dynamicTypeAssumption.dynamicLowerBoundType);
+      }
     }
-    return super.toString();
+    return builder.toString();
   }
 
   public static class DynamicTypeAssumption {
@@ -298,7 +285,7 @@
     private final TypeElement dynamicUpperBoundType;
     private final ClassTypeElement dynamicLowerBoundType;
 
-    private DynamicTypeAssumption(
+    public DynamicTypeAssumption(
         TypeElement dynamicUpperBoundType, ClassTypeElement dynamicLowerBoundType) {
       this.dynamicUpperBoundType = dynamicUpperBoundType;
       this.dynamicLowerBoundType = dynamicLowerBoundType;
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 26b0acd..b0ef472 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -1292,36 +1292,17 @@
       builder.append(": ");
       builder.append(instruction.toString());
       if (DebugLocalInfo.PRINT_LEVEL != PrintLevel.NONE) {
-        List<Value> localEnds = new ArrayList<>(instruction.getDebugValues().size());
-        List<Value> localStarts = new ArrayList<>(instruction.getDebugValues().size());
-        List<Value> localLive = new ArrayList<>(instruction.getDebugValues().size());
-        for (Value value : instruction.getDebugValues()) {
-          if (value.getDebugLocalEnds().contains(instruction)) {
-            localEnds.add(value);
-          } else if (value.getDebugLocalStarts().contains(instruction)) {
-            localStarts.add(value);
-          } else {
-            assert value.debugUsers().contains(instruction);
-            localLive.add(value);
-          }
+        if (!instruction.getDebugValues().isEmpty()) {
+          builder.append(" [end: ");
+          StringUtils.append(builder, instruction.getDebugValues(), ", ", BraceType.NONE);
+          builder.append("]");
         }
-        printDebugValueSet("live", localLive, builder);
-        printDebugValueSet("end", localEnds, builder);
-        printDebugValueSet("start", localStarts, builder);
       }
       builder.append("\n");
     }
     return builder.toString();
   }
 
-  private void printDebugValueSet(String header, List<Value> locals, StringBuilder builder) {
-    if (!locals.isEmpty()) {
-      builder.append(" [").append(header).append(": ");
-      StringUtils.append(builder, locals, ", ", BraceType.NONE);
-      builder.append("]");
-    }
-  }
-
   public void print(CfgPrinter printer) {
     printer.begin("block");
     printer.print("name \"B").append(number).append("\"\n");
@@ -1990,15 +1971,34 @@
       Phi phi = phiIt.next();
       Wrapper<Phi> key = equivalence.wrap(phi);
       Phi replacement = wrapper2phi.get(key);
-      if (replacement != null) {
-        phi.replaceUsers(replacement);
-        for (Value operand : phi.getOperands()) {
-          operand.removePhiUser(phi);
-        }
-        phiIt.remove();
-      } else {
+      if (replacement == null) {
         wrapper2phi.put(key, phi);
+        continue;
       }
+      // Two phis may be duplicates but still differ in debug info.
+      // For example, it may be that the one phi denotes the result of a local pre-increment, while
+      // the other phi represents the modified local, e.g., cond ? ++x : x will give rise to:
+      //  v2 <- phi(v0(x), v1(x))
+      //  v3(x) <- phi(v0(x), v1(x))
+      // where v2 is used as the result of the expression and v3 is the local slot value of x.
+      // This should be replaced by a single phi.
+      if (phi.getLocalInfo() != replacement.getLocalInfo()) {
+        if (replacement.getLocalInfo() == null) {
+          // The replacement must take over the debug info.
+          replacement.setLocalInfo(phi.getLocalInfo());
+        } else if (phi.getLocalInfo() == null) {
+          // The replacement already owns debug info.
+        } else {
+          // The phis define two distinct locals and cannot be de-duped.
+          assert phi.hasLocalInfo() && replacement.hasLocalInfo();
+          continue;
+        }
+      }
+      phi.replaceUsers(replacement);
+      for (Value operand : phi.getOperands()) {
+        operand.removePhiUser(phi);
+      }
+      phiIt.remove();
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index b4ceb0d..50cec6e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -87,10 +87,20 @@
   }
 
   @Override
+  public boolean hasInsertionPosition() {
+    return position != null;
+  }
+
+  @Override
   public void setInsertionPosition(Position position) {
     this.position = position;
   }
 
+  @Override
+  public void unsetInsertionPosition() {
+    this.position = null;
+  }
+
   /**
    * Adds an instruction to the block. The instruction will be added just before the current cursor
    * position.
@@ -198,7 +208,9 @@
     }
     current.moveDebugValues(newInstruction);
     newInstruction.setBlock(block);
-    newInstruction.setPosition(current.getPosition());
+    if (!newInstruction.hasPosition()) {
+      newInstruction.setPosition(current.getPosition());
+    }
     listIterator.remove();
     listIterator.add(newInstruction);
     current.clearBlock();
@@ -210,7 +222,15 @@
       IRCode code, InternalOptions options, long value, TypeElement type) {
     ConstNumber constNumberInstruction = code.createNumberConstant(value, type);
     // Note that we only keep position info for throwing instructions in release mode.
-    constNumberInstruction.setPosition(options.debug ? current.getPosition() : Position.none());
+    if (!hasInsertionPosition()) {
+      Position position;
+      if (options.debug) {
+        position = current != null ? current.getPosition() : block.getPosition();
+      } else {
+        position = Position.none();
+      }
+      constNumberInstruction.setPosition(position);
+    }
     add(constNumberInstruction);
     return constNumberInstruction.outValue();
   }
@@ -235,12 +255,6 @@
 
     // Replace the instruction by const-number.
     ConstNumber constNumber = code.createIntConstant(value, current.getLocalInfo());
-    for (Value inValue : current.inValues()) {
-      if (inValue.hasLocalInfo()) {
-        // Add this value as a debug value to avoid changing its live range.
-        constNumber.addDebugValue(inValue);
-      }
-    }
     replaceCurrentInstruction(constNumber);
   }
 
@@ -256,12 +270,6 @@
     TypeElement oldType = current.getOutType();
     Value value = code.createValue(newType, current.getLocalInfo());
     StaticGet staticGet = new StaticGet(value, field);
-    for (Value inValue : current.inValues()) {
-      if (inValue.hasLocalInfo()) {
-        // Add this value as a debug value to avoid changing its live range.
-        staticGet.addDebugValue(inValue);
-      }
-    }
     replaceCurrentInstruction(staticGet);
 
     // Update affected values.
@@ -280,58 +288,97 @@
     if (current == null) {
       throw new IllegalStateException();
     }
-    BasicBlock block = current.getBlock();
+
+    Instruction toBeReplaced = current;
+
+    BasicBlock block = toBeReplaced.getBlock();
     assert !blocksToRemove.contains(block);
     assert affectedValues != null;
 
-    BasicBlock normalSuccessorBlock = split(code, blockIterator);
+    // Split the block before the instruction that should be replaced by `throw null`.
     previous();
 
-    // Unlink all blocks that are dominated by successor.
-    {
-      DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
-      blocksToRemove.addAll(block.unlink(normalSuccessorBlock, dominatorTree, affectedValues));
+    BasicBlock throwBlock;
+    if (block.hasCatchHandlers() && !toBeReplaced.instructionTypeCanThrow()) {
+      // We need to insert the throw instruction in a block of its own, so split the current block
+      // into three blocks, where the intermediate block only contains a goto instruction.
+      throwBlock = split(code, blockIterator, true);
+      throwBlock.listIterator(code).split(code, blockIterator);
+    } else {
+      split(code, blockIterator, true);
+      throwBlock = block;
     }
 
-    // Insert constant null before the instruction.
+    // Position the instruction iterator before the goto instruction.
+    assert !hasNext();
     previous();
-    Value nullValue = insertConstNullInstruction(code, appView.options());
-    next();
+
+    // Unlink all blocks that are dominated by the unique normal successor of the throw block.
+    blocksToRemove.addAll(
+        throwBlock.unlink(
+            throwBlock.getUniqueNormalSuccessor(),
+            new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS),
+            affectedValues));
+
+    InstructionListIterator throwBlockInstructionIterator;
+    if (throwBlock == block) {
+      throwBlockInstructionIterator = this;
+    } else {
+      throwBlockInstructionIterator = throwBlock.listIterator(code);
+      throwBlockInstructionIterator.setInsertionPosition(position);
+    }
+
+    // Insert constant null before the goto instruction.
+    Value nullValue =
+        throwBlockInstructionIterator.insertConstNullInstruction(code, appView.options());
+
+    // Move past the inserted goto instruction.
+    throwBlockInstructionIterator.next();
+    assert !throwBlockInstructionIterator.hasNext();
 
     // Replace the instruction by throw.
     Throw throwInstruction = new Throw(nullValue);
-    for (Value inValue : current.inValues()) {
-      if (inValue.hasLocalInfo()) {
-        // Add this value as a debug value to avoid changing its live range.
-        throwInstruction.addDebugValue(inValue);
-      }
+    if (hasInsertionPosition()) {
+      throwInstruction.setPosition(position);
+    } else if (toBeReplaced.getPosition().isSome()) {
+      throwInstruction.setPosition(toBeReplaced.getPosition());
+    } else {
+      // The instruction that is being removed cannot throw, and thus it must be unreachable as we
+      // are replacing it by `throw null`, so we can safely use a none-position.
+      assert !toBeReplaced.instructionTypeCanThrow();
+      throwInstruction.setPosition(Position.syntheticNone());
     }
-    replaceCurrentInstruction(throwInstruction);
-    next();
-    remove();
+    throwBlockInstructionIterator.replaceCurrentInstruction(throwInstruction);
 
-    // Remove all catch handlers where the guard does not include NullPointerException.
     if (block.hasCatchHandlers()) {
-      CatchHandlers<BasicBlock> catchHandlers = block.getCatchHandlers();
-      catchHandlers.forEach(
-          (guard, target) -> {
-            if (blocksToRemove.contains(target)) {
-              // Already removed previously. This may happen if two catch handlers have the same
-              // target.
-              return;
-            }
-            if (!appView.appInfo().isSubtype(appView.dexItemFactory().npeType, guard)) {
-              // TODO(christofferqa): Consider updating previous dominator tree instead of
-              //   rebuilding it from scratch.
-              DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
-              blocksToRemove.addAll(block.unlink(target, dominatorTree, affectedValues));
-            }
-          });
+      if (block == throwBlock) {
+        // Remove all catch handlers where the guard does not include NullPointerException if the
+        // replaced instruction could throw.
+        CatchHandlers<BasicBlock> catchHandlers = block.getCatchHandlers();
+        catchHandlers.forEach(
+            (guard, target) -> {
+              if (blocksToRemove.contains(target)) {
+                // Already removed previously. This may happen if two catch handlers have the same
+                // target.
+                return;
+              }
+              if (!appView.appInfo().isSubtype(appView.dexItemFactory().npeType, guard)) {
+                // TODO(christofferqa): Consider updating previous dominator tree instead of
+                //   rebuilding it from scratch.
+                DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
+                blocksToRemove.addAll(block.unlink(target, dominatorTree, affectedValues));
+              }
+            });
+      } else {
+        // We replaced a dead, non-throwing instruction by a throwing instruction. Since this is
+        // dead code, we don't need to worry about the catch handlers of the `throwBlock`.
+      }
     }
   }
 
   @Override
-  public BasicBlock split(IRCode code, ListIterator<BasicBlock> blocksIterator) {
+  public BasicBlock split(
+      IRCode code, ListIterator<BasicBlock> blocksIterator, boolean keepCatchHandlers) {
     List<BasicBlock> blocks = code.blocks;
     assert blocksIterator == null || IteratorUtils.peekPrevious(blocksIterator) == block;
 
@@ -346,7 +393,6 @@
 
     // Prepare the new block, placing the exception handlers on the block with the throwing
     // instruction.
-    boolean keepCatchHandlers = hasPrevious() && peekPrevious().instructionTypeCanThrow();
     newBlock = block.createSplitBlock(blockNumber, keepCatchHandlers);
 
     // Add a goto instruction.
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 4594db9..d28135d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -133,11 +133,6 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.context());
-  }
-
-  @Override
   public boolean isOutConstant() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 3bd1d56..0a76de9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.cf.code.CfConstString;
@@ -11,13 +13,13 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.io.UTFDataFormatException;
 
@@ -132,9 +134,12 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
+  public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
     // No side-effect, such as throwing an exception, in CF.
-    return appView.options().isGeneratingClassFiles() || !instructionInstanceCanThrow();
+    if (appView.options().isGeneratingClassFiles() || !instructionInstanceCanThrow()) {
+      return DeadInstructionResult.deadIfOutValueIsDead();
+    }
+    return DeadInstructionResult.notDead();
   }
 
   @Override
@@ -154,7 +159,7 @@
 
   @Override
   public TypeElement evaluate(AppView<?> appView) {
-    return TypeElement.stringClassType(appView, Nullability.definitelyNotNull());
+    return TypeElement.stringClassType(appView, definitelyNotNull());
   }
 
   @Override
@@ -170,4 +175,12 @@
     }
     return UnknownValue.getInstance();
   }
+
+  @Override
+  public boolean verifyTypes(AppView<?> appView) {
+    assert super.verifyTypes(appView);
+    TypeElement expectedType = TypeElement.stringClassType(appView, definitelyNotNull());
+    assert getOutType().equals(expectedType);
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
index 292d6b7..5a3e094 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 
@@ -71,10 +72,10 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
+  public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
     // Reads are never dead code.
     // They should also have a non-empty set of debug values (see RegAlloc::computeDebugInfo)
-    return false;
+    return DeadInstructionResult.notDead();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
index 842eaa4..1ff7509 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.StringUtils;
@@ -83,8 +84,8 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return false;
+  public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
+    return DeadInstructionResult.notDead();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index fd6f9a9..c347f87 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 
@@ -67,8 +68,8 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return false;
+  public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
+    return DeadInstructionResult.notDead();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index 959dbfc..0753447 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
@@ -131,9 +132,9 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
+  public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
     // No side-effect, such as throwing an exception, in CF.
-    return true;
+    return DeadInstructionResult.deadIfOutValueIsDead();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index a0af5e2..3ef2eee 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -245,10 +245,14 @@
             liveStack.addLast(use);
           }
         }
-        assert instruction.getDebugValues().stream().allMatch(Value::needsRegister);
-        assert instruction.getDebugValues().stream().allMatch(Value::hasLocalInfo);
-        live.addAll(instruction.getDebugValues());
-        liveLocals.addAll(instruction.getDebugValues());
+        if (!instruction.getDebugValues().isEmpty()) {
+          ArrayList<Value> sortedValues = new ArrayList<>(instruction.getDebugValues());
+          sortedValues.sort(Value::compareTo);
+          assert sortedValues.stream().allMatch(Value::needsRegister);
+          assert sortedValues.stream().allMatch(Value::hasLocalInfo);
+          live.addAll(sortedValues);
+          liveLocals.addAll(sortedValues);
+        }
       }
       for (Phi phi : block.getPhis()) {
         if (phi.isValueOnStack()) {
@@ -609,17 +613,17 @@
     // We can only type check the program if we have subtyping information. Therefore, we do not
     // require that the program type checks in D8.
     if (appView.enableWholeProgramOptimizations()) {
-      assert validAssumeDynamicTypeInstructions(appView);
+      assert validAssumeInstructions(appView);
       assert new TypeChecker(appView.withLiveness()).check(this);
     }
     assert blocks.stream().allMatch(block -> block.verifyTypes(appView));
     return true;
   }
 
-  private boolean validAssumeDynamicTypeInstructions(AppView<?> appView) {
+  private boolean validAssumeInstructions(AppView<?> appView) {
     for (BasicBlock block : blocks) {
       for (Instruction instruction : block.getInstructions()) {
-        if (instruction.isAssumeDynamicType()) {
+        if (instruction.isAssume()) {
           assert instruction.asAssume().verifyInstructionIsNeeded(appView);
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index b9cc832..bec911e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -68,7 +68,8 @@
   }
 
   @Override
-  public BasicBlock split(IRCode code, ListIterator<BasicBlock> blockIterator) {
+  public BasicBlock split(
+      IRCode code, ListIterator<BasicBlock> blockIterator, boolean keepCatchHandlers) {
     throw new Unimplemented();
   }
 
@@ -159,4 +160,19 @@
   public void removeOrReplaceByDebugLocalRead() {
     instructionIterator.removeOrReplaceByDebugLocalRead();
   }
+
+  @Override
+  public boolean hasInsertionPosition() {
+    return instructionIterator.hasInsertionPosition();
+  }
+
+  @Override
+  public void setInsertionPosition(Position position) {
+    instructionIterator.setInsertionPosition(position);
+  }
+
+  @Override
+  public void unsetInsertionPosition() {
+    instructionIterator.unsetInsertionPosition();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java
index 9077c97..faebe44 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java
@@ -16,6 +16,9 @@
 
   Value object();
 
+  boolean instructionInstanceCanThrow(
+      AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption);
+
   boolean instructionMayHaveSideEffects(
       AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption);
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 924d256..7dad747 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -123,16 +123,6 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    // instance-get can be dead code as long as it cannot have any of the following:
-    // * NoSuchFieldError (resolution failure)
-    // * IncompatibleClassChangeError (instance-* instruction for static fields)
-    // * IllegalAccessError (not visible from the access context)
-    // * NullPointerException (null receiver)
-    return !instructionMayHaveSideEffects(appView, code.context());
-  }
-
-  @Override
   public int maxInValueRegister() {
     return Constants.U4BIT_MAX;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index b8a5787..296b2f9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -142,20 +142,6 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    // instance-put can be dead as long as it cannot have any of the following:
-    // * NoSuchFieldError (resolution failure)
-    // * IncompatibleClassChangeError (static-* instruction for instance fields)
-    // * IllegalAccessError (not visible from the access context)
-    // * NullPointerException (null receiver)
-    // * not read at all
-    boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.context());
-    assert appView.enableWholeProgramOptimizations() || haveSideEffects
-        : "Expected instance-put instruction to have side effects in D8";
-    return !haveSideEffects;
-  }
-
-  @Override
   public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) {
     if (!super.identicalAfterRegisterAllocation(other, allocator)) {
       return false;
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 1400455..ee7d57e 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
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -32,9 +33,9 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Iterator;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Function;
@@ -45,7 +46,7 @@
   protected final List<Value> inValues = new ArrayList<>();
   private BasicBlock block = null;
   private int number = -1;
-  private LinkedHashSet<Value> debugValues = null;
+  private Set<Value> debugValues = null;
   private Position position = null;
 
   protected Instruction(Value outValue) {
@@ -154,11 +155,9 @@
   public void addDebugValue(Value value) {
     assert value.hasLocalInfo();
     if (debugValues == null) {
-      debugValues = new LinkedHashSet<>();
+      debugValues = Sets.newIdentityHashSet();
     }
-    if (debugValues.add(value)) {
-      value.addDebugUser(this);
-    }
+    debugValues.add(value);
   }
 
   public static void clearUserInfo(Instruction instruction) {
@@ -197,17 +196,19 @@
   }
 
   public void replaceDebugValue(Value oldValue, Value newValue) {
-    if (debugValues.remove(oldValue)) {
-      // TODO(mathiasr): Enable this assertion when BasicBlock has current position so trivial phi
-      // removal can take local info into account.
-      // assert newValue.getLocalInfo() == oldValue.getLocalInfo()
-      //     : "Replacing debug values with inconsistent locals " +
-      //       oldValue.getLocalInfo() + " and " + newValue.getLocalInfo() +
-      //       ". This is likely a code transformation bug " +
-      //       "that has not taken local information into account";
-      if (newValue.hasLocalInfo()) {
-        addDebugValue(newValue);
-      }
+    assert oldValue.hasLocalInfo();
+    assert newValue.hasLocalInfo();
+    assert newValue.getLocalInfo() == oldValue.getLocalInfo()
+        : "Replacing debug values with inconsistent locals "
+            + oldValue.getLocalInfo()
+            + " and "
+            + newValue.getLocalInfo()
+            + ". This is likely a code transformation bug "
+            + "that has not taken local information into account";
+    boolean removed = debugValues.remove(oldValue);
+    assert removed;
+    if (removed && newValue.hasLocalInfo()) {
+      newValue.addDebugLocalEnd(this);
     }
   }
 
@@ -221,12 +222,6 @@
     debugValues.clear();
   }
 
-  public void moveDebugValue(Value value, Instruction target) {
-    assert debugValues.contains(value);
-    value.replaceDebugUser(this, target);
-    debugValues.remove(value);
-  }
-
   public void removeDebugValue(Value value) {
     assert value.hasLocalInfo();
     if (debugValues != null) {
@@ -591,10 +586,10 @@
   }
 
   /** Returns true is this instruction can be treated as dead code if its outputs are not used. */
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    // TODO(b/129530569): instructions with fine-grained side effect analysis may use:
-    // return !instructionMayHaveSideEffects(appView, code.method.holder());
-    return !instructionInstanceCanThrow();
+  public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
+    return instructionMayHaveSideEffects(appView, code.context())
+        ? DeadInstructionResult.notDead()
+        : DeadInstructionResult.deadIfOutValueIsDead();
   }
 
   /**
@@ -717,12 +712,12 @@
     return null;
   }
 
-  public boolean isAssumeDynamicType() {
-    return false;
+  public final boolean isAssumeWithDynamicTypeAssumption() {
+    return isAssume() && asAssume().hasDynamicTypeAssumption();
   }
 
-  public boolean isAssumeNonNull() {
-    return false;
+  public final boolean isAssumeWithNonNullAssumption() {
+    return isAssume() && asAssume().hasNonNullAssumption();
   }
 
   public boolean isBinop() {
@@ -1466,17 +1461,70 @@
     return false;
   }
 
-  public enum SideEffectAssumption {
-    NONE,
-    CLASS_ALREADY_INITIALIZED,
-    RECEIVER_NOT_NULL;
+  public static class SideEffectAssumption {
 
-    boolean canAssumeClassIsAlreadyInitialized() {
-      return this == CLASS_ALREADY_INITIALIZED;
+    public static final SideEffectAssumption NONE = new SideEffectAssumption();
+
+    public static final SideEffectAssumption CLASS_ALREADY_INITIALIZED =
+        new SideEffectAssumption() {
+
+          @Override
+          public boolean canAssumeClassIsAlreadyInitialized() {
+            return true;
+          }
+        };
+
+    public static final SideEffectAssumption INVOKED_METHOD_DOES_NOT_HAVE_SIDE_EFFECTS =
+        new SideEffectAssumption() {
+
+          @Override
+          public boolean canAssumeInvokedMethodDoesNotHaveSideEffects() {
+            return true;
+          }
+        };
+
+    public static final SideEffectAssumption RECEIVER_NOT_NULL =
+        new SideEffectAssumption() {
+
+          @Override
+          public boolean canAssumeReceiverIsNotNull() {
+            return true;
+          }
+        };
+
+    public boolean canAssumeClassIsAlreadyInitialized() {
+      return false;
     }
 
-    boolean canAssumeReceiverIsNotNull() {
-      return this == RECEIVER_NOT_NULL;
+    public boolean canAssumeInvokedMethodDoesNotHaveSideEffects() {
+      return false;
+    }
+
+    public boolean canAssumeReceiverIsNotNull() {
+      return false;
+    }
+
+    public SideEffectAssumption join(SideEffectAssumption other) {
+      return new SideEffectAssumption() {
+
+        @Override
+        public boolean canAssumeClassIsAlreadyInitialized() {
+          return SideEffectAssumption.this.canAssumeClassIsAlreadyInitialized()
+              || other.canAssumeClassIsAlreadyInitialized();
+        }
+
+        @Override
+        public boolean canAssumeInvokedMethodDoesNotHaveSideEffects() {
+          return SideEffectAssumption.this.canAssumeInvokedMethodDoesNotHaveSideEffects()
+              || other.canAssumeInvokedMethodDoesNotHaveSideEffects();
+        }
+
+        @Override
+        public boolean canAssumeReceiverIsNotNull() {
+          return SideEffectAssumption.this.canAssumeInvokedMethodDoesNotHaveSideEffects()
+              || other.canAssumeInvokedMethodDoesNotHaveSideEffects();
+        }
+      };
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index a546ee3..2790e66 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -58,10 +58,18 @@
    */
   void removeOrReplaceByDebugLocalRead();
 
+  default boolean hasInsertionPosition() {
+    return false;
+  }
+
   default void setInsertionPosition(Position position) {
     // Intentionally empty.
   }
 
+  default void unsetInsertionPosition() {
+    // Intentionally empty.
+  }
+
   default Value insertConstNullInstruction(IRCode code, InternalOptions options) {
     return insertConstNumberInstruction(code, options, 0, TypeElement.getNull());
   }
@@ -104,20 +112,25 @@
 
   /**
    * Split the block into two blocks at the point of the {@link ListIterator} cursor. The existing
-   * block will have all the instructions before the cursor, and the new block all the
-   * instructions after the cursor.
+   * block will have all the instructions before the cursor, and the new block all the instructions
+   * after the cursor.
    *
-   * If the current block has catch handlers these catch handlers will be attached to the block
+   * <p>If the current block has catch handlers these catch handlers will be attached to the block
    * containing the throwing instruction after the split.
    *
    * @param code the IR code for the block this iterator originates from.
    * @param blockIterator basic block iterator used to iterate the blocks. This must be positioned
-   * just after the block for which this is the instruction iterator. After this method returns it
-   * will be positioned just after the basic block returned. Calling {@link #remove} without
-   * further navigation will remove that block.
+   *     just after the block for which this is the instruction iterator. After this method returns
+   *     it will be positioned just after the basic block returned. Calling {@link #remove} without
+   *     further navigation will remove that block.
+   * @param keepCatchHandlers whether to keep catch handlers on the original block.
    * @return Returns the new block with the instructions after the cursor.
    */
-  BasicBlock split(IRCode code, ListIterator<BasicBlock> blockIterator);
+  BasicBlock split(IRCode code, ListIterator<BasicBlock> blockIterator, boolean keepCatchHandlers);
+
+  default BasicBlock split(IRCode code, ListIterator<BasicBlock> blockIterator) {
+    return split(code, blockIterator, hasPrevious() && peekPrevious().instructionTypeCanThrow());
+  }
 
   default BasicBlock split(IRCode code) {
     return split(code, null);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index 0f736a0..020254f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -20,12 +20,11 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import java.util.ArrayList;
 import java.util.List;
-import java.util.function.Predicate;
 
 public class InvokeDirect extends InvokeMethodWithReceiver {
 
@@ -162,54 +161,20 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
+  public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
     ProgramMethod context = code.context();
     if (instructionMayHaveSideEffects(appView, context)) {
-      return false;
+      return DeadInstructionResult.notDead();
     }
-
-    if (appView.dexItemFactory().isConstructor(getInvokedMethod())) {
-      // If it is a constructor call that initializes an uninitialized object, then the
-      // uninitialized object must be dead. This is the case if all the constructor calls cannot
-      // have side effects and the instance is dead except for the constructor calls.
-      List<Instruction> otherInitCalls = null;
-      for (Instruction user : getReceiver().uniqueUsers()) {
-        if (user == this) {
-          continue;
-        }
-        if (user.isInvokeDirect()) {
-          InvokeDirect invoke = user.asInvokeDirect();
-          if (appView.dexItemFactory().isConstructor(invoke.getInvokedMethod())
-              && invoke.getReceiver() == getReceiver()) {
-            // If another constructor call than `this` is found, then it must not have side effects.
-            if (invoke.instructionMayHaveSideEffects(appView, context)) {
-              return false;
-            }
-            if (otherInitCalls == null) {
-              otherInitCalls = new ArrayList<>();
-            }
-            otherInitCalls.add(invoke);
-          }
-        }
-      }
-
-      // Now check that the instance is dead except for the constructor calls.
-      final List<Instruction> finalOtherInitCalls = otherInitCalls;
-      Predicate<Instruction> ignoreConstructorCalls =
-          instruction ->
-              instruction == this
-                  || (finalOtherInitCalls != null && finalOtherInitCalls.contains(instruction));
-      if (!getReceiver().isDead(appView, code, ignoreConstructorCalls)) {
-        return false;
-      }
-
-      // Verify that it is not a super-constructor call (these cannot be removed).
-      if (getReceiver().getAliasedValue() == code.getThis()) {
-        return false;
-      }
+    if (!getInvokedMethod().isInstanceInitializer(appView)) {
+      return DeadInstructionResult.deadIfOutValueIsDead();
     }
-
-    return true;
+    // Super-constructor calls cannot be removed.
+    if (getReceiver().getAliasedValue() == code.getThis()) {
+      return DeadInstructionResult.notDead();
+    }
+    // Constructor calls can only be removed if the receiver is dead.
+    return DeadInstructionResult.deadIfInValueIsDead(getReceiver());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index a2aa502..79f7152 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -150,7 +150,8 @@
       return false;
     }
     // Check that the receiver information comes from a dynamic type.
-    if (!getReceiver().definition.isAssumeDynamicType()) {
+    if (!getReceiver()
+        .isDefinedByInstructionSatisfying(Instruction::isAssumeWithDynamicTypeAssumption)) {
       return false;
     }
     // Now, it can be that the upper bound is more precise than the lower:
@@ -208,9 +209,12 @@
     assert appView.appInfo().hasLiveness();
     AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
 
-    ResolutionResult resolutionResult =
-        appViewWithLiveness.appInfo().resolveMethod(getInvokedMethod(), getInterfaceBit());
-    if (resolutionResult.isFailedResolution()) {
+    SingleResolutionResult resolutionResult =
+        appViewWithLiveness
+            .appInfo()
+            .resolveMethod(getInvokedMethod(), getInterfaceBit())
+            .asSingleResolution();
+    if (resolutionResult == null) {
       return true;
     }
 
@@ -221,6 +225,16 @@
       return true;
     }
 
+    if (assumption.canAssumeInvokedMethodDoesNotHaveSideEffects()) {
+      return false;
+    }
+
+    DexEncodedMethod resolvedMethod = resolutionResult.getResolvedMethod();
+    if (appViewWithLiveness.appInfo().noSideEffects.containsKey(getInvokedMethod())
+        || appViewWithLiveness.appInfo().noSideEffects.containsKey(resolvedMethod.getReference())) {
+      return false;
+    }
+
     // Find the target and check if the invoke may have side effects.
     DexEncodedMethod target = lookupSingleTarget(appViewWithLiveness, context);
     if (target == null) {
@@ -242,9 +256,4 @@
 
     return optimizationInfo.mayHaveSideEffects();
   }
-
-  @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.context());
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index cc83815..314317e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -182,11 +182,6 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.context());
-  }
-
-  @Override
   public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index fc6028f..2783471 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -195,11 +195,6 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.context());
-  }
-
-  @Override
   public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 7f5836a..00c88b8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -229,9 +229,4 @@
             type -> appInfoWithLiveness.isSubtype(context.getHolderType(), type),
             Sets.newIdentityHashSet());
   }
-
-  @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.context());
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
index 9536919..9f4a26f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.List;
@@ -32,8 +33,8 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return false;
+  public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
+    return DeadInstructionResult.notDead();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index d73f8df..d851eb9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -77,8 +77,9 @@
   }
 
   @Override
-  public BasicBlock split(IRCode code, ListIterator<BasicBlock> blockIterator) {
-    return currentBlockIterator.split(code, blockIterator);
+  public BasicBlock split(
+      IRCode code, ListIterator<BasicBlock> blockIterator, boolean keepCatchHandlers) {
+    return currentBlockIterator.split(code, blockIterator, keepCatchHandlers);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 4c87b70..dd74687 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
@@ -80,10 +81,14 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !(appView.options().debug
-            || code.method().getOptimizationInfo().isReachabilitySensitive())
-        && appView.options().isGeneratingDex();
+  public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
+    InternalOptions options = appView.options();
+    if (options.debug
+        || code.context().getDefinition().getOptimizationInfo().isReachabilitySensitive()
+        || options.isGeneratingClassFiles()) {
+      return DeadInstructionResult.notDead();
+    }
+    return DeadInstructionResult.deadIfOutValueIsDead();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index c730efc..eb12b5a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 
@@ -82,13 +83,16 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
+  public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
     if (instructionInstanceCanThrow()) {
-      return false;
+      return DeadInstructionResult.notDead();
     }
     // This would belong better in instructionInstanceCanThrow, but that is not passed an appInfo.
     DexType baseType = type.toBaseType(appView.dexItemFactory());
-    return baseType.isPrimitiveType() || appView.definitionFor(baseType) != null;
+    if (baseType.isPrimitiveType() || appView.definitionFor(baseType) != null) {
+      return DeadInstructionResult.deadIfOutValueIsDead();
+    }
+    return DeadInstructionResult.notDead();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index 54d1ad4..bc75936 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -138,9 +138,4 @@
   public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
     return false;
   }
-
-  @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.context());
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index c1b2bd7..81af63c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -198,11 +198,6 @@
     return false;
   }
 
-  @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.context());
-  }
-
   public void markNoSpilling() {
     allowSpilling = false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index a9365d8..cd5cb77 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -260,6 +260,18 @@
       // leave the phi in there and check at the end that there are no trivial phis.
       return false;
     }
+    if (getLocalInfo() != same.getLocalInfo()) {
+      if (getLocalInfo() == null) {
+        // The to-be replaced phi has no local info, so all is OK.
+      } else if (same.getLocalInfo() == null) {
+        // Move the local info to the replacement phi.
+        same.setLocalInfo(getLocalInfo());
+      } else {
+        // The phi's define distinct locals and are not trivially the same.
+        assert hasLocalInfo() && same.hasLocalInfo();
+        return false;
+      }
+    }
     // Ensure that the value that replaces this phi is constrained to the type of this phi.
     if (builder != null && type.isPreciseType() && !type.isBottom()) {
       builder.constrainType(same, ValueTypeConstraint.fromTypeLattice(type));
@@ -430,6 +442,9 @@
         }
       }
     }
+    if (getType().isReferenceType() && getType().isDefinitelyNotNull()) {
+      return result.asReferenceType().asMeetWithNotNull();
+    }
     return result;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Pop.java b/src/main/java/com/android/tools/r8/ir/code/Pop.java
index 0124c45..9ec7256 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Pop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 
@@ -91,9 +92,9 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
+  public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
     // Pop cannot be dead code as it modifies the stack height.
-    return false;
+    return DeadInstructionResult.notDead();
   }
 
   @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 4dc7c12..2a5ffbd 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
@@ -149,16 +149,6 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    // static-get can be dead as long as it cannot have any of the following:
-    // * NoSuchFieldError (resolution failure)
-    // * IncompatibleClassChangeError (static-* instruction for instance fields)
-    // * IllegalAccessError (not visible from the access context)
-    // * side-effects in <clinit>
-    return !instructionMayHaveSideEffects(appView, code.context());
-  }
-
-  @Override
   public int maxInValueRegister() {
     return Constants.U8BIT_MAX;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index 36b6520..e268cbe 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -138,20 +138,6 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    // static-put can be dead as long as it cannot have any of the following:
-    // * NoSuchFieldError (resolution failure)
-    // * IncompatibleClassChangeError (static-* instruction for instance fields)
-    // * IllegalAccessError (not visible from the access context)
-    // * side-effects in <clinit>
-    // * not read _globally_
-    boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.context());
-    assert appView.enableWholeProgramOptimizations() || haveSideEffects
-        : "Expected static-put instruction to have side effects in D8";
-    return !haveSideEffects;
-  }
-
-  @Override
   public int maxInValueRegister() {
     return Constants.U8BIT_MAX;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Store.java b/src/main/java/com/android/tools/r8/ir/code/Store.java
index ccd57ea..8100111 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Store.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 
@@ -100,8 +101,11 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !(outValue instanceof FixedLocalValue);
+  public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
+    if (outValue instanceof FixedLocalValue) {
+      return DeadInstructionResult.notDead();
+    }
+    return DeadInstructionResult.deadIfOutValueIsDead();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 53af0fc..c2a63d4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
@@ -37,14 +38,10 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.IntList;
-import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
-import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
@@ -153,63 +150,13 @@
   private static class DebugData {
 
     final DebugLocalInfo local;
-    Map<Instruction, DebugUse> users = new HashMap<>();
+    Set<Instruction> users = Sets.newIdentityHashSet();
 
     DebugData(DebugLocalInfo local) {
       this.local = local;
     }
   }
 
-  // A debug-value user represents a point where the value is live, ends or starts.
-  // If a point is marked as both ending and starting then it is simply live, but we maintain
-  // the marker so as not to unintentionally end it if marked again.
-  private enum DebugUse {
-    LIVE, START, END, LIVE_FINAL;
-
-    DebugUse start() {
-      switch (this) {
-        case LIVE:
-        case START:
-          return START;
-        case END:
-        case LIVE_FINAL:
-          return LIVE_FINAL;
-        default:
-          throw new Unreachable();
-      }
-    }
-
-    DebugUse end() {
-      switch (this) {
-        case LIVE:
-        case END:
-          return END;
-        case START:
-        case LIVE_FINAL:
-          return LIVE_FINAL;
-        default:
-          throw new Unreachable();
-      }
-    }
-
-    static DebugUse join(DebugUse a, DebugUse b) {
-      if (a == LIVE_FINAL || b == LIVE_FINAL) {
-        return LIVE_FINAL;
-      }
-      if (a == b) {
-        return a;
-      }
-      if (a == LIVE) {
-        return b;
-      }
-      if (b == LIVE) {
-        return a;
-      }
-      assert (a == START && b == END) || (a == END && b == START);
-      return LIVE_FINAL;
-    }
-  }
-
   public static final int UNDEFINED_NUMBER = -1;
 
   public static final Value UNDEFINED = new Value(UNDEFINED_NUMBER, TypeElement.getBottom(), null);
@@ -326,62 +273,16 @@
     debugData = new DebugData(local);
   }
 
-  public void clearLocalInfo() {
-    assert debugData.users.isEmpty();
-    debugData = null;
-  }
-
-  public boolean hasSameOrNoLocal(Value other) {
-    assert other != null;
-    return hasLocalInfo()
-        ? other.getLocalInfo() == this.getLocalInfo()
-        : !other.hasLocalInfo();
-  }
-
-  public List<Instruction> getDebugLocalStarts() {
-    if (debugData == null) {
-      return Collections.emptyList();
-    }
-    List<Instruction> starts = new ArrayList<>(debugData.users.size());
-    for (Entry<Instruction, DebugUse> entry : debugData.users.entrySet()) {
-      if (entry.getValue() == DebugUse.START) {
-        starts.add(entry.getKey());
-      }
-    }
-    return starts;
-  }
-
-  public List<Instruction> getDebugLocalEnds() {
-    if (debugData == null) {
-      return Collections.emptyList();
-    }
-    List<Instruction> ends = new ArrayList<>(debugData.users.size());
-    for (Entry<Instruction, DebugUse> entry : debugData.users.entrySet()) {
-      if (entry.getValue() == DebugUse.END) {
-        ends.add(entry.getKey());
-      }
-    }
-    return ends;
-  }
-
-  public void addDebugLocalStart(Instruction start) {
-    assert start != null;
-    debugData.users.put(start, markStart(debugData.users.get(start)));
-  }
-
-  private DebugUse markStart(DebugUse use) {
-    assert use != null;
-    return use == null ? DebugUse.START : use.start();
+  public Set<Instruction> getDebugLocalEnds() {
+    return debugData == null
+        ? Collections.emptySet()
+        : Collections.unmodifiableSet(debugData.users);
   }
 
   public void addDebugLocalEnd(Instruction end) {
     assert end != null;
-    debugData.users.put(end, markEnd(debugData.users.get(end)));
-  }
-
-  private DebugUse markEnd(DebugUse use) {
-    assert use != null;
-    return use == null ? DebugUse.END : use.end();
+    debugData.users.add(end);
+    end.addDebugValue(this);
   }
 
   public void linkTo(Value other) {
@@ -491,7 +392,7 @@
   }
 
   public Set<Instruction> debugUsers() {
-    return debugData == null ? null : Collections.unmodifiableSet(debugData.users.keySet());
+    return debugData == null ? null : Collections.unmodifiableSet(debugData.users);
   }
 
   public boolean hasAnyUsers() {
@@ -502,6 +403,10 @@
     return debugData != null && !debugData.users.isEmpty();
   }
 
+  public boolean hasNonDebugUsers() {
+    return hasUsers() || hasPhiUsers();
+  }
+
   public boolean hasPhiUsers() {
     return !phiUsers.isEmpty();
   }
@@ -618,11 +523,6 @@
     uniquePhiUsers = null;
   }
 
-  public void addDebugUser(Instruction user) {
-    assert hasLocalInfo();
-    debugData.users.putIfAbsent(user, DebugUse.LIVE);
-  }
-
   public boolean isUninitializedLocal() {
     return definition != null && definition.isDebugLocalUninitialized();
   }
@@ -684,7 +584,7 @@
       user.replaceOperand(this, newValue);
     }
     if (debugData != null) {
-      for (Entry<Instruction, DebugUse> user : debugData.users.entrySet()) {
+      for (Instruction user : debugData.users) {
         replaceUserInDebugData(user, newValue);
       }
       debugData.users.clear();
@@ -746,10 +646,10 @@
       }
     }
     if (debugData != null) {
-      Iterator<Entry<Instruction, DebugUse>> users = debugData.users.entrySet().iterator();
+      Iterator<Instruction> users = debugData.users.iterator();
       while (users.hasNext()) {
-        Entry<Instruction, DebugUse> user = users.next();
-        if (selectedInstructions.contains(user.getKey())) {
+        Instruction user = users.next();
+        if (selectedInstructions.contains(user)) {
           replaceUserInDebugData(user, newValue);
           users.remove();
         }
@@ -757,30 +657,18 @@
     }
   }
 
-  private void replaceUserInDebugData(Entry<Instruction, DebugUse> user, Value newValue) {
-    Instruction instruction = user.getKey();
-    DebugUse debugUse = user.getValue();
-    instruction.replaceDebugValue(this, newValue);
+  private void replaceUserInDebugData(Instruction user, Value newValue) {
+    user.replaceDebugValue(this, newValue);
     // If user is a DebugLocalRead and now has no debug values, we would like to remove it.
     // However, replaceUserInDebugData() is called in contexts where the instruction list is being
     // iterated, so we cannot remove user from the instruction list at this point.
-    if (newValue.hasLocalInfo()) {
-      DebugUse existing = newValue.debugData.users.get(instruction);
-      assert existing != null;
-      newValue.debugData.users.put(instruction, DebugUse.join(debugUse, existing));
-    }
   }
 
   public void replaceDebugUser(Instruction oldUser, Instruction newUser) {
-    DebugUse use = debugData.users.remove(oldUser);
-    if (use == DebugUse.START && newUser.outValue == this) {
-      // Register allocation requires that debug values are live at the entry to the instruction.
-      // Remove this debug use since it is starting at the instruction that defines it.
-      return;
-    }
-    if (use != null) {
-      newUser.addDebugValue(this);
-      debugData.users.put(newUser, use);
+    boolean removed = debugData.users.remove(oldUser);
+    assert removed;
+    if (removed) {
+      addDebugLocalEnd(newUser);
     }
   }
 
@@ -948,8 +836,8 @@
    */
   public boolean isNeverNull() {
     assert type.isReferenceType();
-    return (definition != null && definition.isAssumeNonNull())
-        || type.nullability().isDefinitelyNotNull();
+    return isDefinedByInstructionSatisfying(Instruction::isAssumeWithNonNullAssumption)
+        || type.isDefinitelyNotNull();
   }
 
   public boolean isArgument() {
@@ -1093,9 +981,18 @@
       if (ignoreUser.test(instruction)) {
         continue;
       }
-      if (!instruction.canBeDeadCode(appView, code)) {
+      DeadInstructionResult result = instruction.canBeDeadCode(appView, code);
+      if (result.isNotDead()) {
         return false;
       }
+      if (result.isMaybeDead()) {
+        for (Value valueRequiredToBeDead : result.getValuesRequiredToBeDead()) {
+          if (!active.contains(valueRequiredToBeDead)
+              && !valueRequiredToBeDead.isDead(appView, code, ignoreUser, active)) {
+            return false;
+          }
+        }
+      }
       Value outValue = instruction.outValue();
       if (outValue != null
           && !active.contains(outValue)
@@ -1168,14 +1065,23 @@
     Value root = getAliasedValue();
     if (root.isPhi()) {
       assert getSpecificAliasedValue(
-              value -> !value.isPhi() && value.definition.isAssumeDynamicType())
+              value ->
+                  value.isDefinedByInstructionSatisfying(
+                      Instruction::isAssumeWithDynamicTypeAssumption))
           == null;
-      return root.getDynamicUpperBoundType(appView);
+      TypeElement result = root.getDynamicUpperBoundType(appView);
+      if (getType().isReferenceType() && getType().isDefinitelyNotNull()) {
+        return result.asReferenceType().asMeetWithNotNull();
+      }
+      return result;
     }
 
     // Try to find an alias of the receiver, which is defined by an instruction of the type Assume.
     Value aliasedValue =
-        getSpecificAliasedValue(value -> !value.isPhi() && value.definition.isAssumeDynamicType());
+        getSpecificAliasedValue(
+            value ->
+                value.isDefinedByInstructionSatisfying(
+                    Instruction::isAssumeWithDynamicTypeAssumption));
     TypeElement lattice;
     if (aliasedValue != null) {
       // If there is an alias of the receiver, which is defined by an Assume instruction that
@@ -1228,7 +1134,8 @@
     }
 
     // Try to find an alias of the receiver, which is defined by an instruction of the type Assume.
-    Value aliasedValue = getSpecificAliasedValue(value -> value.definition.isAssumeDynamicType());
+    Value aliasedValue =
+        getSpecificAliasedValue(value -> value.definition.isAssumeWithDynamicTypeAssumption());
     if (aliasedValue != null) {
       ClassTypeElement lattice =
           aliasedValue.definition.asAssume().getDynamicTypeAssumption().getDynamicLowerBoundType();
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 0dd98e2..60fcec2 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
@@ -2377,12 +2377,10 @@
     // Add a use if this instruction is overwriting a previous value of the same local.
     if (previousLocalValue != null && previousLocalValue.getLocalInfo() == ir.getLocalInfo()) {
       assert ir.outValue() != null;
-      ir.addDebugValue(previousLocalValue);
       previousLocalValue.addDebugLocalEnd(ir);
     }
     // Add reads of locals if any are pending.
     for (Value value : debugLocalEnds) {
-      ir.addDebugValue(value);
       value.addDebugLocalEnd(ir);
     }
     previousLocalValue = null;
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 844ccce..35760b4 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
@@ -53,7 +53,6 @@
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.AssumeInserter;
-import com.android.tools.r8.ir.optimize.Assumer;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
@@ -70,7 +69,6 @@
 import com.android.tools.r8.ir.optimize.RedundantFieldLoadElimination;
 import com.android.tools.r8.ir.optimize.ReflectionOptimizer;
 import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
-import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
 import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
@@ -160,15 +158,13 @@
   private final Devirtualizer devirtualizer;
   private final CovariantReturnTypeAnnotationTransformer covariantReturnTypeAnnotationTransformer;
   private final StringSwitchRemover stringSwitchRemover;
-  private final UninstantiatedTypeOptimization uninstantiatedTypeOptimization;
   private final TypeChecker typeChecker;
   private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
   private final ServiceLoaderRewriter serviceLoaderRewriter;
   private final EnumValueOptimizer enumValueOptimizer;
   private final EnumUnboxer enumUnboxer;
 
-  // Assumers that will insert Assume instructions.
-  public final Collection<Assumer> assumers = new ArrayList<>();
+  public final AssumeInserter assumeInserter;
   private final DynamicTypeOptimization dynamicTypeOptimization;
 
   final AssertionsRewriter assertionsRewriter;
@@ -256,7 +252,6 @@
       this.lensCodeRewriter = null;
       this.identifierNameStringMarker = null;
       this.devirtualizer = null;
-      this.uninstantiatedTypeOptimization = null;
       this.typeChecker = null;
       this.d8NestBasedAccessDesugaring = null;
       this.stringSwitchRemover = null;
@@ -264,6 +259,7 @@
       this.methodOptimizationInfoCollector = null;
       this.enumValueOptimizer = null;
       this.enumUnboxer = null;
+      this.assumeInserter = null;
       return;
     }
     this.lambdaRewriter =
@@ -291,20 +287,12 @@
       assert appView.appInfo().hasLiveness();
       assert appView.rootSet() != null;
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-      if (options.enableNonNullTracking) {
-        assumers.add(new AssumeInserter(appViewWithLiveness));
-      }
+      assumeInserter = new AssumeInserter(appViewWithLiveness);
       this.classInliner =
           options.enableClassInlining && options.enableInlining ? new ClassInliner() : null;
       this.classStaticizer =
           options.enableClassStaticizer ? new ClassStaticizer(appViewWithLiveness, this) : null;
-      this.dynamicTypeOptimization =
-          options.enableDynamicTypeOptimization
-              ? new DynamicTypeOptimization(appViewWithLiveness)
-              : null;
-      if (dynamicTypeOptimization != null) {
-        assumers.add(dynamicTypeOptimization);
-      }
+      this.dynamicTypeOptimization = new DynamicTypeOptimization(appViewWithLiveness);
       this.fieldAccessAnalysis =
           FieldAccessAnalysis.enable(options) ? new FieldAccessAnalysis(appViewWithLiveness) : null;
       this.libraryMethodOverrideAnalysis =
@@ -329,10 +317,6 @@
       }
       this.devirtualizer =
           options.enableDevirtualization ? new Devirtualizer(appViewWithLiveness) : null;
-      this.uninstantiatedTypeOptimization =
-          options.enableUninstantiatedTypeOptimization
-              ? new UninstantiatedTypeOptimization(appViewWithLiveness)
-              : null;
       this.typeChecker = new TypeChecker(appViewWithLiveness);
       this.d8NestBasedAccessDesugaring = null;
       this.serviceLoaderRewriter =
@@ -347,6 +331,7 @@
       this.enumValueOptimizer =
           options.enableEnumValueOptimization ? new EnumValueOptimizer(appViewWithLiveness) : null;
     } else {
+      this.assumeInserter = null;
       this.classInliner = null;
       this.classStaticizer = null;
       this.dynamicTypeOptimization = null;
@@ -359,7 +344,6 @@
       this.lensCodeRewriter = null;
       this.identifierNameStringMarker = null;
       this.devirtualizer = null;
-      this.uninstantiatedTypeOptimization = null;
       this.typeChecker = null;
       this.d8NestBasedAccessDesugaring =
           options.shouldDesugarNests() ? new D8NestBasedAccessDesugaring(appView) : null;
@@ -489,6 +473,8 @@
   public DexApplication convert(DexApplication application, ExecutorService executor)
       throws ExecutionException {
     removeLambdaDeserializationMethods();
+    workaroundAbstractMethodOnNonAbstractClassVerificationBug(
+        executor, OptimizationFeedbackIgnore.getInstance());
 
     timing.begin("IR conversion");
     ThreadUtils.processItems(application.classes(), this::convertMethods, executor);
@@ -652,6 +638,28 @@
     }
   }
 
+  private void workaroundAbstractMethodOnNonAbstractClassVerificationBug(
+      ExecutorService executorService, OptimizationFeedback feedback) throws ExecutionException {
+    if (!options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()) {
+      return;
+    }
+    assert delayedOptimizationFeedback.noUpdatesLeft();
+    ThreadUtils.processItems(
+        appView.appInfo().classes(),
+        clazz -> {
+          if (!clazz.isAbstract()) {
+            clazz.forEachMethod(
+                method -> {
+                  if (method.isAbstract()) {
+                    method.accessFlags.unsetAbstract();
+                    finalizeEmptyThrowingCode(method, feedback);
+                  }
+                });
+          }
+        },
+        executorService);
+  }
+
   public DexApplication optimize() throws ExecutionException {
     ExecutorService executor = Executors.newSingleThreadExecutor();
     try {
@@ -667,6 +675,8 @@
     computeReachabilitySensitivity(application);
     collectLambdaMergingCandidates(application);
     collectStaticizerCandidates(application);
+    workaroundAbstractMethodOnNonAbstractClassVerificationBug(
+        executorService, simpleOptimizationFeedback);
 
     // The process is in two phases in general.
     // 1) Subject all DexEncodedMethods to optimization, except some optimizations that require
@@ -675,6 +685,9 @@
     // 2) Revisit DexEncodedMethods for the collected candidates.
 
     printPhase("Primary optimization pass");
+    if (fieldAccessAnalysis != null) {
+      fieldAccessAnalysis.fieldAssignmentTracker().initialize();
+    }
 
     // Process the application identifying outlining candidates.
     GraphLense graphLenseForIR = appView.graphLense();
@@ -1259,9 +1272,9 @@
 
     previous = printMethod(code, "IR after disable assertions (SSA)", previous);
 
-    timing.begin("Insert assume instructions");
-    CodeRewriter.insertAssumeInstructions(code, assumers, timing);
-    timing.end();
+    if (assumeInserter != null) {
+      assumeInserter.insertAssumeInstructions(code, timing);
+    }
 
     previous = printMethod(code, "IR after inserting assume instructions (SSA)", previous);
 
@@ -1323,14 +1336,6 @@
 
     assert code.verifyTypes(appView);
 
-    if (uninstantiatedTypeOptimization != null) {
-      timing.begin("Rewrite uninstantiated types");
-      uninstantiatedTypeOptimization.rewrite(code);
-      timing.end();
-    }
-
-    assert code.verifyTypes(appView);
-
     timing.begin("Remove trivial type checks/casts");
     codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code);
     timing.end();
@@ -1370,10 +1375,10 @@
     codeRewriter.splitRangeInvokeConstants(code);
     timing.end();
     timing.begin("Propogate sparse conditionals");
-    new SparseConditionalConstantPropagation(code).run();
+    new SparseConditionalConstantPropagation(appView, code).run();
     timing.end();
-    timing.begin("Rewrite always throwing invokes");
-    codeRewriter.processMethodsNeverReturningNormally(code);
+    timing.begin("Rewrite always throwing instructions");
+    codeRewriter.optimizeAlwaysThrowingInstructions(code);
     timing.end();
     timing.begin("Simplify control flow");
     if (codeRewriter.simplifyControlFlow(code)) {
@@ -1597,7 +1602,7 @@
       timing.end();
     }
 
-    if (!assumers.isEmpty()) {
+    if (assumeInserter != null) {
       timing.begin("Remove assume instructions");
       CodeRewriter.removeAssumeInstructions(appView, code);
       timing.end();
@@ -1702,10 +1707,7 @@
 
   private void finalizeEmptyThrowingCode(DexEncodedMethod method, OptimizationFeedback feedback) {
     assert options.isGeneratingClassFiles() || options.isGeneratingDex();
-    Code emptyThrowingCode =
-        options.isGeneratingClassFiles()
-            ? method.buildEmptyThrowingCfCode()
-            : method.buildEmptyThrowingDexCode();
+    Code emptyThrowingCode = method.buildEmptyThrowingCode(options);
     method.setCode(emptyThrowingCode, appView);
     feedback.markProcessed(method, ConstraintWithTarget.ALWAYS);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 7211439..e54ec6c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -44,8 +44,6 @@
 
   void methodReturnValueOnlyDependsOnArguments(DexEncodedMethod method);
 
-  void methodNeverReturnsNull(DexEncodedMethod method);
-
   void methodNeverReturnsNormally(DexEncodedMethod method);
 
   void markProcessed(DexEncodedMethod method, ConstraintWithTarget state);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
index e66d342..ba49fc0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -84,16 +85,20 @@
             }
           }
         } else if (instruction.isFieldInstruction()) {
-          DexEncodedField encodedField =
-              appView.definitionFor(instruction.asFieldInstruction().getField());
-          if (encodedField != null && fieldAccessRequiresRewriting(encodedField, method)) {
+          // Since we only need to desugar accesses to private fields, and all accesses to private
+          // fields must be accessing the private field directly on its holder, we can lookup the
+          // field on the holder instead of resolving the field.
+          FieldInstruction fieldInstruction = instruction.asFieldInstruction();
+          DexClass holder = appView.definitionForHolder(fieldInstruction.getField());
+          DexEncodedField field = fieldInstruction.getField().lookupOnClass(holder);
+          if (field != null && fieldAccessRequiresRewriting(field, method)) {
             if (instruction.isInstanceGet() || instruction.isStaticGet()) {
-              DexMethod bridge = ensureFieldAccessBridge(encodedField, true);
+              DexMethod bridge = ensureFieldAccessBridge(field, true);
               instructions.replaceCurrentInstruction(
                   new InvokeStatic(bridge, instruction.outValue(), instruction.inValues()));
             } else {
               assert instruction.isInstancePut() || instruction.isStaticPut();
-              DexMethod bridge = ensureFieldAccessBridge(encodedField, false);
+              DexMethod bridge = ensureFieldAccessBridge(field, false);
               instructions.replaceCurrentInstruction(
                   new InvokeStatic(bridge, instruction.outValue(), instruction.inValues()));
             }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index 88108f9..a91c1aa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClasspathClass;
@@ -529,15 +530,16 @@
     DexMethod conversionMethod = createConversionMethod(argType, argType, argVivifiedType);
     // The value is null only if the input is null.
     Value convertedValue =
-        createConversionValue(code, inValue.getType().nullability(), argVivifiedType);
+        createConversionValue(code, inValue.getType().nullability(), argVivifiedType, null);
     return new InvokeStatic(conversionMethod, convertedValue, Collections.singletonList(inValue));
   }
 
   private Instruction createReturnConversionAndReplaceUses(
       IRCode code, InvokeMethod invokeMethod, DexType returnType, DexType returnVivifiedType) {
     DexMethod conversionMethod = createConversionMethod(returnType, returnVivifiedType, returnType);
-    Value convertedValue = createConversionValue(code, Nullability.maybeNull(), returnType);
     Value outValue = invokeMethod.outValue();
+    Value convertedValue =
+        createConversionValue(code, Nullability.maybeNull(), returnType, outValue.getLocalInfo());
     outValue.replaceUsers(convertedValue);
     // The only user of out value is now the new invoke static, so no type propagation is required.
     outValue.setType(
@@ -571,8 +573,9 @@
         conversionHolder, factory.createProto(destType, srcType), factory.convertMethodName);
   }
 
-  private Value createConversionValue(IRCode code, Nullability nullability, DexType valueType) {
-    return code.createValue(TypeElement.fromDexType(valueType, nullability, appView));
+  private Value createConversionValue(
+      IRCode code, Nullability nullability, DexType valueType, DebugLocalInfo localInfo) {
+    return code.createValue(TypeElement.fromDexType(valueType, nullability, appView), localInfo);
   }
 
   public boolean canConvert(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
index 7da225c..2586141 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
@@ -84,14 +84,15 @@
     return appView.definitionFor(appView.graphLense().lookupType(type));
   }
 
-  private DexEncodedMethod definitionFor(
+  private DexEncodedMethod lookupOnHolder(
       DexMethod method, DexClassAndMethod context, Invoke.Type invokeType) {
     return appView.definitionFor(
         appView.graphLense().lookupMethod(method, context.getReference(), invokeType).getMethod());
   }
 
-  private DexEncodedField definitionFor(DexField field) {
-    return appView.definitionFor(appView.graphLense().lookupField(field));
+  private DexEncodedField lookupOnHolder(DexField field) {
+    DexField rewritten = appView.graphLense().lookupField(field);
+    return rewritten.lookupOnClass(appView.definitionForHolder(rewritten));
   }
 
   // Extract the list of types in the programClass' nest, of host hostClass
@@ -365,7 +366,7 @@
       if (!method.holder.isClassType()) {
         return false;
       }
-      DexEncodedMethod encodedMethod = definitionFor(method, context, invokeType);
+      DexEncodedMethod encodedMethod = lookupOnHolder(method, context, invokeType);
       if (encodedMethod != null && invokeRequiresRewriting(encodedMethod, context)) {
         ensureInvokeBridge(encodedMethod);
         return true;
@@ -374,7 +375,10 @@
     }
 
     private boolean registerFieldAccess(DexField field, boolean isGet) {
-      DexEncodedField encodedField = definitionFor(field);
+      // Since we only need to desugar accesses to private fields, and all accesses to private
+      // fields must be accessing the private field directly on its holder, we can lookup the field
+      // on the holder instead of resolving the field.
+      DexEncodedField encodedField = lookupOnHolder(field);
       if (encodedField != null && fieldAccessRequiresRewriting(encodedField, context)) {
         ensureFieldAccessBridge(encodedField, isGet);
         return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
index 81b43ef..e4b35af 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
@@ -340,7 +340,7 @@
           if (method.holder() == dexItemFactory.kotlin.assertions.type) {
             rewriteKotlinAssertionEnable(code, transformation, iterator, invoke);
           } else {
-            iterator.replaceCurrentInstruction(code.createIntConstant(0));
+            iterator.replaceCurrentInstruction(code.createIntConstant(0, current.getLocalInfo()));
           }
         }
       } else if (current.isStaticPut()) {
@@ -355,13 +355,16 @@
         if (isInitializerEnablingJavaVmAssertions
             && staticGet.getField().name == dexItemFactory.assertionsDisabled) {
           iterator.replaceCurrentInstruction(
-              code.createIntConstant(transformation == AssertionTransformation.DISABLE ? 1 : 0));
+              code.createIntConstant(
+                  transformation == AssertionTransformation.DISABLE ? 1 : 0,
+                  current.getLocalInfo()));
         }
         // Rewrite kotlin._Assertions.ENABLED getter.
         if (staticGet.getField() == dexItemFactory.kotlin.assertions.enabledField) {
           iterator.replaceCurrentInstruction(
               code.createIntConstant(
-                  kotlinTransformation == AssertionTransformation.DISABLE ? 0 : 1));
+                  kotlinTransformation == AssertionTransformation.DISABLE ? 0 : 1,
+                  current.getLocalInfo()));
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
index 611a0a1..0008896 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
@@ -3,19 +3,23 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.google.common.base.Predicates.alwaysTrue;
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 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.Assume;
+import com.android.tools.r8.ir.code.Assume.DynamicTypeAssumption;
 import com.android.tools.r8.ir.code.Assume.NonNullAssumption;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.DominatorTree;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
@@ -29,8 +33,11 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.TriConsumer;
+import com.android.tools.r8.utils.TriFunction;
+import com.android.tools.r8.utils.TriPredicate;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
@@ -44,24 +51,23 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
-import java.util.function.BiFunction;
-import java.util.function.BiPredicate;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
-public class AssumeInserter implements Assumer {
+public class AssumeInserter {
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final InternalOptions options;
 
   public AssumeInserter(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.appView = appView;
+    this.options = appView.options();
   }
 
-  @Override
   public void insertAssumeInstructions(IRCode code, Timing timing) {
     insertAssumeInstructionsInBlocks(code, code.listIterator(), alwaysTrue(), timing);
   }
 
-  @Override
   public void insertAssumeInstructionsInBlocks(
       IRCode code,
       BasicBlockIterator blockIterator,
@@ -89,7 +95,7 @@
     timing.end();
 
     timing.begin("Part 3: Compute dominated users");
-    Map<Instruction, Set<Value>> redundantKeys =
+    Map<Instruction, Map<Value, AssumedValueInfo>> redundantAssumedValues =
         computeDominanceForAssumedValues(code, assumedValues);
     timing.end();
     if (assumedValues.isEmpty()) {
@@ -97,7 +103,7 @@
     }
 
     timing.begin("Part 4: Remove redundant dominated assume instructions");
-    removeRedundantDominatedAssumeInstructions(assumedValues, redundantKeys);
+    removeRedundantDominatedAssumeInstructions(assumedValues, redundantAssumedValues);
     timing.end();
     if (assumedValues.isEmpty()) {
       return;
@@ -145,29 +151,15 @@
         }
       }
 
-      Value outValue = current.outValue();
       if (current.isInvokeMethod()) {
-        InvokeMethod invoke = current.asInvokeMethod();
-        if (invoke.hasOutValue() || !invoke.getInvokedMethod().proto.parameters.isEmpty()) {
-          // Case (2) and (3).
-          needsAssumeInstruction |=
-              computeAssumedValuesFromSingleTarget(code, invoke, assumedValuesBuilder);
-        }
+        // Case (2) and (3).
+        needsAssumeInstruction |=
+            computeAssumedValuesForInvokeMethod(
+                code, current.asInvokeMethod(), assumedValuesBuilder);
       } else if (current.isFieldGet()) {
         // Case (4), field-get instructions that are guaranteed to read a non-null value.
-        FieldInstruction fieldInstruction = current.asFieldInstruction();
-        DexField field = fieldInstruction.getField();
-        if (isNullableReferenceTypeWithNonDebugUsers(outValue)) {
-          DexEncodedField encodedField = appView.appInfo().resolveField(field).getResolvedField();
-          if (encodedField != null) {
-            FieldOptimizationInfo optimizationInfo = encodedField.getOptimizationInfo();
-            if (optimizationInfo.getDynamicUpperBoundType() != null
-                && optimizationInfo.getDynamicUpperBoundType().isDefinitelyNotNull()) {
-              assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(current, outValue);
-              needsAssumeInstruction = true;
-            }
-          }
-        }
+        needsAssumeInstruction |=
+            computeAssumedValuesForFieldGet(current.asFieldInstruction(), assumedValuesBuilder);
       }
 
       // If we need to insert an assume instruction into a block with catch handlers, we split the
@@ -202,6 +194,35 @@
     }
   }
 
+  private boolean computeAssumedValuesForInvokeMethod(
+      IRCode code, InvokeMethod invoke, AssumedValues.Builder assumedValuesBuilder) {
+    if (!invoke.hasOutValue() && invoke.getInvokedMethod().proto.parameters.isEmpty()) {
+      return false;
+    }
+
+    DexMethod invokedMethod = invoke.getInvokedMethod();
+    if (invokedMethod.holder.isArrayType()
+        && invokedMethod.match(appView.dexItemFactory().objectMembers.clone)) {
+      return computeAssumedValuesFromArrayClone(invoke, assumedValuesBuilder);
+    }
+
+    return computeAssumedValuesFromSingleTarget(code, invoke, assumedValuesBuilder);
+  }
+
+  private boolean computeAssumedValuesFromArrayClone(
+      InvokeMethod invoke, AssumedValues.Builder assumedValuesBuilder) {
+    Value outValue = invoke.outValue();
+    if (outValue == null || !outValue.hasNonDebugUsers()) {
+      return false;
+    }
+
+    TypeElement dynamicUpperBoundType =
+        TypeElement.fromDexType(invoke.getInvokedMethod().holder, definitelyNotNull(), appView);
+    assumedValuesBuilder.addAssumedValueKnownToDominateAllUsers(
+        invoke, outValue, dynamicUpperBoundType, null);
+    return true;
+  }
+
   private boolean computeAssumedValuesFromSingleTarget(
       IRCode code, InvokeMethod invoke, AssumedValues.Builder assumedValuesBuilder) {
     DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
@@ -214,11 +235,13 @@
 
     // Case (2), invocations that are guaranteed to return a non-null value.
     Value outValue = invoke.outValue();
-    if (outValue != null
-        && optimizationInfo.neverReturnsNull()
-        && isNullableReferenceTypeWithNonDebugUsers(outValue)) {
-      assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(invoke, outValue);
-      needsAssumeInstruction = true;
+    if (outValue != null && outValue.hasNonDebugUsers()) {
+      needsAssumeInstruction =
+          computeAssumedValuesForOutValue(
+              invoke,
+              optimizationInfo.getDynamicUpperBoundTypeOrElse(outValue.getType()),
+              optimizationInfo.getDynamicLowerBoundType(),
+              assumedValuesBuilder);
     }
 
     // Case (3), parameters that are not null after the invocation.
@@ -239,31 +262,116 @@
     return needsAssumeInstruction;
   }
 
+  private boolean computeAssumedValuesForFieldGet(
+      FieldInstruction fieldGet, AssumedValues.Builder assumedValuesBuilder) {
+    Value outValue = fieldGet.outValue();
+    if (!outValue.hasNonDebugUsers()) {
+      return false;
+    }
+
+    DexEncodedField field = appView.appInfo().resolveField(fieldGet.getField()).getResolvedField();
+    if (field == null) {
+      return false;
+    }
+
+    FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo();
+    return computeAssumedValuesForOutValue(
+        fieldGet,
+        optimizationInfo.getDynamicUpperBoundTypeOrElse(outValue.getType()),
+        optimizationInfo.getDynamicLowerBoundType(),
+        assumedValuesBuilder);
+  }
+
+  private boolean computeAssumedValuesForOutValue(
+      Instruction instruction,
+      TypeElement dynamicUpperBoundType,
+      ClassTypeElement dynamicLowerBoundType,
+      AssumedValues.Builder assumedValuesBuilder) {
+    Value outValue = instruction.outValue();
+
+    // Do not insert dynamic type information if it does not refine the static type.
+    boolean isRedundant =
+        !dynamicUpperBoundType.strictlyLessThan(outValue.getType(), appView)
+            && dynamicLowerBoundType == null;
+    if (isRedundant) {
+      return false;
+    }
+
+    // Do not insert dynamic type information if the dynamic type only refines the nullability.
+    if (dynamicUpperBoundType.equalUpToNullability(outValue.getType())
+        && dynamicLowerBoundType == null) {
+      assert dynamicUpperBoundType.isDefinitelyNotNull();
+      assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(instruction, outValue);
+    } else {
+      assumedValuesBuilder.addAssumedValueKnownToDominateAllUsers(
+          instruction, outValue, dynamicUpperBoundType, dynamicLowerBoundType);
+    }
+    return true;
+  }
+
   private void removeRedundantAssumeInstructions(AssumedValues assumedValues) {
     assumedValues.removeIf(
-        (instruction, assumedValue) -> {
+        (instruction, assumedValue, assumedValueInfo) -> {
+          // Assumed values with dynamic type information are never redundant.
+          if (assumedValueInfo.hasDynamicTypeInfo()) {
+            return false;
+          }
+
+          assert assumedValueInfo.isNonNull();
+
+          // Otherwise, it is redundant if it is defined by another instruction that guarantees its
+          // non-nullness.
           if (assumedValue.isPhi()) {
             return false;
           }
+
           Instruction definition = assumedValue.definition;
-          return definition != instruction && assumedValues.contains(definition, assumedValue);
+          if (definition == instruction) {
+            return false;
+          }
+
+          AssumedValueInfo otherAssumedValueInfo =
+              assumedValues.getAssumedValueInfo(definition, assumedValue);
+          if (otherAssumedValueInfo == null) {
+            return false;
+          }
+
+          if (!otherAssumedValueInfo.isNonNull()) {
+            // This is not redundant, but we can strenghten it with the dynamic type information
+            // from the other assume instruction.
+            assumedValueInfo.setDynamicTypeAssumption(
+                otherAssumedValueInfo.getDynamicTypeAssumption());
+            return false;
+          }
+
+          return true;
         });
   }
 
-  private Map<Instruction, Set<Value>> computeDominanceForAssumedValues(
+  private Map<Instruction, Map<Value, AssumedValueInfo>> computeDominanceForAssumedValues(
       IRCode code, AssumedValues assumedValues) {
-    Map<Instruction, Set<Value>> redundantKeys = new IdentityHashMap<>();
+    Map<Instruction, Map<Value, AssumedValueInfo>> redundantAssumedValues = new IdentityHashMap<>();
     LazyDominatorTree lazyDominatorTree = new LazyDominatorTree(code);
     Map<BasicBlock, Set<BasicBlock>> dominatedBlocksCache = new IdentityHashMap<>();
     assumedValues.computeDominance(
-        (instruction, assumedValue) -> {
-          Set<Value> alreadyAssumedValues = redundantKeys.get(instruction);
-          if (alreadyAssumedValues != null && alreadyAssumedValues.contains(assumedValue)) {
-            // Returning redundant() will cause the entry (instruction, assumedValue) to be removed.
-            return AssumedDominance.redundant();
+        (instruction, assumedValue, assumedValueInfo) -> {
+          Map<Value, AssumedValueInfo> alreadyAssumedValues =
+              redundantAssumedValues.get(instruction);
+          if (alreadyAssumedValues != null) {
+            AssumedValueInfo alreadyAssumedValueInfo = alreadyAssumedValues.get(assumedValue);
+            if (alreadyAssumedValueInfo != null) {
+              if (assumedValueInfo.isSubsumedBy(alreadyAssumedValueInfo)) {
+                // Returning redundant() will cause the entry (instruction, assumedValue) to be
+                // removed.
+                return AssumedDominance.redundant();
+              }
+
+              // This assume value is dominated by the other assume value, so strengthen this one.
+              assumedValueInfo.strengthenWith(alreadyAssumedValueInfo);
+            }
           }
 
-          // If this value is non-null since its definition, then it is known to dominate all users.
+          // If this value is the out-value of some instruction it is known to dominate all users.
           if (assumedValue == instruction.outValue()) {
             return AssumedDominance.everything();
           }
@@ -327,9 +435,9 @@
 
               // Record that there is no need to insert an assume instruction for the non-null-value
               // after the given user in case the user is also a null check for the non-null-value.
-              redundantKeys
-                  .computeIfAbsent(user, ignore -> Sets.newIdentityHashSet())
-                  .add(assumedValue);
+              redundantAssumedValues
+                  .computeIfAbsent(user, ignore -> new IdentityHashMap<>())
+                  .put(assumedValue, assumedValueInfo);
             }
           }
           for (Phi user : assumedValue.uniquePhiUsers()) {
@@ -341,68 +449,54 @@
           }
           return dominance.build();
         });
-    return redundantKeys;
+    return redundantAssumedValues;
   }
 
   private void removeRedundantDominatedAssumeInstructions(
-      AssumedValues assumedValues, Map<Instruction, Set<Value>> redundantKeys) {
-    assumedValues.removeAll(redundantKeys);
+      AssumedValues assumedValues,
+      Map<Instruction, Map<Value, AssumedValueInfo>> redundantAssumedValues) {
+    assumedValues.removeAll(redundantAssumedValues);
   }
 
   private void materializeAssumeInstructions(IRCode code, AssumedValues assumedValues) {
     Set<Value> affectedValues = Sets.newIdentityHashSet();
     Map<BasicBlock, Map<Instruction, List<Instruction>>> pendingInsertions =
         new IdentityHashMap<>();
-    assumedValues.forEach(
-        (instruction, assumedValue, assumedValueInfo) -> {
-          BasicBlock block = instruction.getBlock();
-          BasicBlock insertionBlock = getInsertionBlock(instruction);
 
-          AssumedDominance dominance = assumedValueInfo.getDominance();
-          Value newValue =
-              code.createValue(
-                  assumedValue.getType().asReferenceType().asMeetWithNotNull(),
-                  assumedValue.getLocalInfo());
-          if (dominance.isEverything()) {
-            assumedValue.replaceUsers(newValue);
-          } else if (dominance.isEverythingElse()) {
-            assumedValue.replaceSelectiveInstructionUsers(newValue, user -> user != instruction);
-            assumedValue.replacePhiUsers(newValue);
-          } else if (dominance.isSomething()) {
-            SomethingAssumedDominance somethingDominance = dominance.asSomething();
-            somethingDominance
-                .getDominatedPhiUsers()
-                .forEach(
-                    (user, indices) -> {
-                      IntListIterator iterator = indices.iterator();
-                      while (iterator.hasNext()) {
-                        Value operand = user.getOperand(iterator.nextInt());
-                        if (operand != assumedValue) {
-                          assert operand.isDefinedByInstructionSatisfying(
-                              Instruction::isAssumeNonNull);
-                          iterator.remove();
-                        }
-                      }
-                    });
-            assumedValue.replaceSelectiveUsers(
-                newValue,
-                somethingDominance.getDominatedUsers(),
-                somethingDominance.getDominatedPhiUsers());
-          }
-          affectedValues.addAll(newValue.affectedValues());
-
-          Assume assumeInstruction =
-              Assume.createAssumeNonNullInstruction(newValue, assumedValue, instruction, appView);
-          assumeInstruction.setPosition(instruction.getPosition());
-          if (insertionBlock != block) {
-            insertionBlock.listIterator(code).add(assumeInstruction);
-          } else {
-            pendingInsertions
-                .computeIfAbsent(block, ignore -> new IdentityHashMap<>())
-                .computeIfAbsent(instruction, ignore -> new ArrayList<>())
-                .add(assumeInstruction);
-          }
-        });
+    // We materialize the assume instructions in two steps. First, we materialize all the assume
+    // instructions that do not dominate everything. These assume instructions can refine previous
+    // assume instructions, so we materialize those first as they are "stronger".
+    //
+    // Example:
+    //   1. Object value = getNullableValueWithDynamicType();
+    //   2. Object nullableValueWithDynamicType = assume(value, ...)
+    //   3. checkNotNull(value);
+    //   4. Object nonNullValueWithDynamicType = assume(value, ...)
+    //   5. return value;
+    //
+    // In this example, we first materialize the assume instruction in line 4, and replace the
+    // dominated use of `value` in line 5 by the new assumed value `nonNullValueWithDynamicType`.
+    // Afterwards, we materialize the assume instruction in line 2, and replace all remaining users
+    // of `value` by `nullableValueWithDynamicType`.
+    //
+    // Result:
+    //   1. Object value = getNullableValueWithDynamicType();
+    //   2. Object nullableValueWithDynamicType = assume(value, ...)
+    //   3. checkNotNull(nullableValueWithDynamicType);
+    //   4. Object nonNullValueWithDynamicType = assume(value, ...)
+    //   5. return nonNullValueWithDynamicType;
+    materializeSelectedAssumeInstructions(
+        code,
+        assumedValues,
+        affectedValues,
+        pendingInsertions,
+        assumedValueInfo -> !assumedValueInfo.dominance.isEverything());
+    materializeSelectedAssumeInstructions(
+        code,
+        assumedValues,
+        affectedValues,
+        pendingInsertions,
+        assumedValueInfo -> assumedValueInfo.dominance.isEverything());
     pendingInsertions.forEach(
         (block, pendingInsertionsPerInstruction) -> {
           InstructionListIterator instructionIterator = block.listIterator(code);
@@ -420,6 +514,83 @@
     }
   }
 
+  private void materializeSelectedAssumeInstructions(
+      IRCode code,
+      AssumedValues assumedValues,
+      Set<Value> affectedValues,
+      Map<BasicBlock, Map<Instruction, List<Instruction>>> pendingInsertions,
+      Predicate<AssumedValueInfo> predicate) {
+    assumedValues.removeIf(
+        (instruction, assumedValue, assumedValueInfo) -> {
+          if (!predicate.test(assumedValueInfo)) {
+            return false;
+          }
+
+          BasicBlock block = instruction.getBlock();
+          BasicBlock insertionBlock = getInsertionBlock(instruction);
+
+          AssumedDominance dominance = assumedValueInfo.getDominance();
+          Value newValue =
+              assumedValueInfo.isNull()
+                  ? code.createValue(TypeElement.getNull())
+                  : code.createValue(
+                      assumedValueInfo.isNonNull()
+                          ? assumedValue.getType().asReferenceType().asMeetWithNotNull()
+                          : assumedValue.getType(),
+                      assumedValue.getLocalInfo());
+          if (dominance.isEverything()) {
+            assumedValue.replaceUsers(newValue);
+          } else if (dominance.isEverythingElse()) {
+            assumedValue.replaceSelectiveInstructionUsers(newValue, user -> user != instruction);
+            assumedValue.replacePhiUsers(newValue);
+          } else if (dominance.isSomething()) {
+            SomethingAssumedDominance somethingDominance = dominance.asSomething();
+            somethingDominance
+                .getDominatedPhiUsers()
+                .forEach(
+                    (user, indices) -> {
+                      IntListIterator iterator = indices.iterator();
+                      while (iterator.hasNext()) {
+                        Value operand = user.getOperand(iterator.nextInt());
+                        if (operand != assumedValue) {
+                          assert operand.isDefinedByInstructionSatisfying(Instruction::isAssume);
+                          iterator.remove();
+                        }
+                      }
+                    });
+            assumedValue.replaceSelectiveUsers(
+                newValue,
+                somethingDominance.getDominatedUsers(),
+                somethingDominance.getDominatedPhiUsers());
+          }
+          affectedValues.addAll(newValue.affectedValues());
+
+          Instruction assumeInstruction;
+          if (assumedValueInfo.isNull()) {
+            assumeInstruction = new ConstNumber(newValue, 0);
+          } else {
+            assumeInstruction =
+                new Assume(
+                    assumedValueInfo.dynamicTypeAssumption,
+                    assumedValueInfo.nonNullAssumption,
+                    newValue,
+                    assumedValue,
+                    instruction,
+                    appView);
+          }
+          assumeInstruction.setPosition(instruction.getPosition());
+          if (insertionBlock != block) {
+            insertionBlock.listIterator(code).add(assumeInstruction);
+          } else {
+            pendingInsertions
+                .computeIfAbsent(block, ignore -> new IdentityHashMap<>())
+                .computeIfAbsent(instruction, ignore -> new ArrayList<>())
+                .add(assumeInstruction);
+          }
+          return true;
+        });
+  }
+
   private BasicBlock getInsertionBlock(Instruction instruction) {
     if (instruction.isIf()) {
       return instruction.asIf().targetFromNonNullObject();
@@ -461,10 +632,6 @@
     return type.isReferenceType() && type.asReferenceType().isNullable();
   }
 
-  private static boolean isNullableReferenceTypeWithNonDebugUsers(Value value) {
-    return isNullableReferenceType(value) && value.numberOfAllNonDebugUsers() > 0;
-  }
-
   private static boolean isNullableReferenceTypeWithOtherNonDebugUsers(
       Value value, Instruction ignore) {
     if (isNullableReferenceType(value)) {
@@ -483,6 +650,7 @@
   static class AssumedValueInfo {
 
     AssumedDominance dominance;
+    DynamicTypeAssumption dynamicTypeAssumption;
     NonNullAssumption nonNullAssumption;
 
     AssumedValueInfo(AssumedDominance dominance) {
@@ -497,9 +665,55 @@
       this.dominance = dominance;
     }
 
+    boolean hasDynamicTypeInfo() {
+      return dynamicTypeAssumption != null;
+    }
+
+    DynamicTypeAssumption getDynamicTypeAssumption() {
+      return dynamicTypeAssumption;
+    }
+
+    void setDynamicTypeAssumption(DynamicTypeAssumption dynamicTypeAssumption) {
+      this.dynamicTypeAssumption = dynamicTypeAssumption;
+    }
+
+    void setDynamicTypeAssumption(
+        TypeElement dynamicUpperBoundType, ClassTypeElement dynamicLowerBoundType) {
+      dynamicTypeAssumption =
+          new DynamicTypeAssumption(dynamicUpperBoundType, dynamicLowerBoundType);
+      if (dynamicUpperBoundType.isDefinitelyNotNull()) {
+        setNotNull();
+      }
+      if (dynamicLowerBoundType != null && dynamicLowerBoundType.isDefinitelyNotNull()) {
+        setNotNull();
+      }
+    }
+
+    boolean isNull() {
+      return dynamicTypeAssumption != null
+          && dynamicTypeAssumption.getDynamicUpperBoundType().isDefinitelyNull();
+    }
+
+    boolean isNonNull() {
+      return nonNullAssumption != null;
+    }
+
     void setNotNull() {
       nonNullAssumption = NonNullAssumption.get();
     }
+
+    boolean isSubsumedBy(AssumedValueInfo other) {
+      return !hasDynamicTypeInfo() && other.isNonNull();
+    }
+
+    void strengthenWith(AssumedValueInfo info) {
+      if (info.isNonNull()) {
+        setNotNull();
+      }
+      if (!hasDynamicTypeInfo() && info.hasDynamicTypeInfo()) {
+        setDynamicTypeAssumption(info.getDynamicTypeAssumption());
+      }
+    }
   }
 
   static class AssumedValues {
@@ -519,7 +733,8 @@
       return new Builder();
     }
 
-    void computeDominance(BiFunction<Instruction, Value, AssumedDominance> function) {
+    void computeDominance(
+        TriFunction<Instruction, Value, AssumedValueInfo, AssumedDominance> function) {
       Iterator<Entry<Instruction, Map<Value, AssumedValueInfo>>> outerIterator =
           assumedValues.entrySet().iterator();
       while (outerIterator.hasNext()) {
@@ -539,7 +754,7 @@
             continue;
           }
           assert dominance.isUnknown();
-          dominance = function.apply(instruction, assumedValue);
+          dominance = function.apply(instruction, assumedValue, assumedValueInfo);
           if ((dominance.isNothing() && !assumedValue.isArgument()) || dominance.isUnknown()) {
             innerIterator.remove();
           } else {
@@ -552,9 +767,9 @@
       }
     }
 
-    boolean contains(Instruction instruction, Value assumedValue) {
+    AssumedValueInfo getAssumedValueInfo(Instruction instruction, Value assumedValue) {
       Map<Value, AssumedValueInfo> dominancePerValue = assumedValues.get(instruction);
-      return dominancePerValue != null && dominancePerValue.containsKey(assumedValue);
+      return dominancePerValue != null ? dominancePerValue.get(assumedValue) : null;
     }
 
     boolean isEmpty() {
@@ -569,12 +784,12 @@
                       consumer.accept(instruction, assumedValue, assumedValueInfo)));
     }
 
-    void removeAll(Map<Instruction, Set<Value>> keys) {
+    void removeAll(Map<Instruction, Map<Value, AssumedValueInfo>> keys) {
       keys.forEach(
-          (instruction, values) -> {
+          (instruction, redundantAssumedValues) -> {
             Map<Value, AssumedValueInfo> dominancePerValue = assumedValues.get(instruction);
             if (dominancePerValue != null) {
-              values.forEach(dominancePerValue::remove);
+              redundantAssumedValues.keySet().forEach(dominancePerValue::remove);
               if (dominancePerValue.isEmpty()) {
                 assumedValues.remove(instruction);
               }
@@ -582,7 +797,7 @@
           });
     }
 
-    void removeIf(BiPredicate<Instruction, Value> predicate) {
+    void removeIf(TriPredicate<Instruction, Value, AssumedValueInfo> predicate) {
       Iterator<Entry<Instruction, Map<Value, AssumedValueInfo>>> outerIterator =
           assumedValues.entrySet().iterator();
       while (outerIterator.hasNext()) {
@@ -592,8 +807,10 @@
         Iterator<Entry<Value, AssumedValueInfo>> innerIterator =
             dominancePerValue.entrySet().iterator();
         while (innerIterator.hasNext()) {
-          Value assumedValue = innerIterator.next().getKey();
-          if (predicate.test(instruction, assumedValue)) {
+          Entry<Value, AssumedValueInfo> innerEntry = innerIterator.next();
+          Value assumedValue = innerEntry.getKey();
+          AssumedValueInfo assumedValueInfo = innerEntry.getValue();
+          if (predicate.test(instruction, assumedValue, assumedValueInfo)) {
             innerIterator.remove();
           }
         }
@@ -609,29 +826,49 @@
           new LinkedHashMap<>();
 
       // Used to avoid unnecessary block splitting during phase 1.
-      private final Set<Value> assumedValuesKnownToDominateAllUsers = Sets.newIdentityHashSet();
+      private final Set<Value> nonNullValuesKnownToDominateAllUsers = Sets.newIdentityHashSet();
 
-      private void addNonNullValue(
-          Instruction instruction, Value nonNullValue, AssumedDominance dominance) {
-        assumedValues
-            .computeIfAbsent(instruction, ignore -> new LinkedHashMap<>())
-            .computeIfAbsent(nonNullValue, ignore -> new AssumedValueInfo(dominance))
-            .setNotNull();
-        if (dominance.isEverything()) {
-          assumedValuesKnownToDominateAllUsers.add(nonNullValue);
+      private void updateAssumedValueInfo(
+          Instruction instruction,
+          Value assumedValue,
+          AssumedDominance dominance,
+          Consumer<AssumedValueInfo> consumer) {
+        AssumedValueInfo assumedValueInfo =
+            assumedValues
+                .computeIfAbsent(instruction, ignore -> new LinkedHashMap<>())
+                .computeIfAbsent(assumedValue, ignore -> new AssumedValueInfo(dominance));
+        consumer.accept(assumedValueInfo);
+        if (dominance.isEverything() && assumedValueInfo.isNonNull()) {
+          nonNullValuesKnownToDominateAllUsers.add(assumedValue);
         }
       }
 
+      void addAssumedValueKnownToDominateAllUsers(
+          Instruction instruction,
+          Value assumedValue,
+          TypeElement dynamicUpperBoundType,
+          ClassTypeElement dynamicLowerBoundType) {
+        updateAssumedValueInfo(
+            instruction,
+            assumedValue,
+            AssumedDominance.everything(),
+            assumedValueInfo ->
+                assumedValueInfo.setDynamicTypeAssumption(
+                    dynamicUpperBoundType, dynamicLowerBoundType));
+      }
+
       void addNonNullValueKnownToDominateAllUsers(Instruction instruction, Value nonNullValue) {
-        addNonNullValue(instruction, nonNullValue, AssumedDominance.everything());
+        updateAssumedValueInfo(
+            instruction, nonNullValue, AssumedDominance.everything(), AssumedValueInfo::setNotNull);
       }
 
       void addNonNullValueWithUnknownDominance(Instruction instruction, Value nonNullValue) {
-        addNonNullValue(instruction, nonNullValue, AssumedDominance.unknown());
+        updateAssumedValueInfo(
+            instruction, nonNullValue, AssumedDominance.unknown(), AssumedValueInfo::setNotNull);
       }
 
       public boolean isMaybeNull(Value value) {
-        return !assumedValuesKnownToDominateAllUsers.contains(value);
+        return !nonNullValuesKnownToDominateAllUsers.contains(value);
       }
 
       public AssumedValues build() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java
similarity index 60%
rename from src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
rename to src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java
index 7f45db3..41f72cb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java
@@ -16,60 +16,68 @@
 import java.util.Set;
 
 /**
- * When we have Assume instructions with a DynamicTypeAssumption we generally verify that the
- * dynamic type in the Assume node is always at least as precise as the static type of the
- * corresponding value.
+ * When we have Assume instructions we generally verify that the Assume instructions contribute with
+ * non-trivial information to the IR (e.g., the dynamic type should be more precise than the static
+ * type).
  *
  * <p>Therefore, when this property may no longer hold for an Assume instruction, we need to remove
  * it.
  *
  * <p>This class is a helper class to remove these instructions. Unlike {@link
  * CodeRewriter#removeAssumeInstructions} this class does not unconditionally remove all Assume
- * instructions, not does it remove all Assume instructions with a DynamicTypeAssumption.
+ * instructions.
  */
-public class AssumeDynamicTypeRemover {
+public class AssumeRemover {
 
   private final AppView<?> appView;
   private final IRCode code;
 
   private final Set<Value> affectedValues = Sets.newIdentityHashSet();
-  private final Set<Assume> assumeDynamicTypeInstructionsToRemove = Sets.newIdentityHashSet();
+  private final Set<Assume> assumeInstructionsToRemove = Sets.newIdentityHashSet();
 
   private boolean mayHaveIntroducedTrivialPhi = false;
 
-  public AssumeDynamicTypeRemover(AppView<?> appView, IRCode code) {
+  public AssumeRemover(AppView<?> appView, IRCode code) {
     this.appView = appView;
     this.code = code;
   }
 
+  public Set<Value> getAffectedValues() {
+    return affectedValues;
+  }
+
   public boolean mayHaveIntroducedTrivialPhi() {
     return mayHaveIntroducedTrivialPhi;
   }
 
-  public void markForRemoval(Assume assumeDynamicTypeInstruction) {
-    assumeDynamicTypeInstructionsToRemove.add(assumeDynamicTypeInstruction);
-  }
-
-  public void markUsersForRemoval(Value value) {
+  public void markAssumeDynamicTypeUsersForRemoval(Value value) {
     for (Instruction user : value.aliasedUsers()) {
-      if (user.isAssumeDynamicType()) {
-        markForRemoval(user.asAssume());
+      if (user.isAssume()) {
+        Assume assumeInstruction = user.asAssume();
+        assumeInstruction.unsetDynamicTypeAssumption();
+        if (!assumeInstruction.hasNonNullAssumption()) {
+          assumeInstruction.unsetDynamicTypeAssumption();
+        }
       }
     }
   }
 
+  private void markForRemoval(Assume assumeInstruction) {
+    assumeInstructionsToRemove.add(assumeInstruction);
+  }
+
   public void removeIfMarked(
-      Assume assumeDynamicTypeInstruction, InstructionListIterator instructionIterator) {
-    if (assumeDynamicTypeInstructionsToRemove.remove(assumeDynamicTypeInstruction)) {
-      Value inValue = assumeDynamicTypeInstruction.src();
-      Value outValue = assumeDynamicTypeInstruction.outValue();
+      Assume assumeInstruction, InstructionListIterator instructionIterator) {
+    if (assumeInstructionsToRemove.remove(assumeInstruction)) {
+      Value inValue = assumeInstruction.src();
+      Value outValue = assumeInstruction.outValue();
 
       // Check if we need to run the type analysis for the affected values of the out-value.
       if (!outValue.getType().equals(inValue.getType())) {
         affectedValues.addAll(outValue.affectedValues());
       }
 
-      if (outValue.numberOfPhiUsers() > 0) {
+      if (outValue.hasPhiUsers()) {
         mayHaveIntroducedTrivialPhi = true;
       }
 
@@ -78,16 +86,20 @@
     }
   }
 
-  public AssumeDynamicTypeRemover removeMarkedInstructions(Set<BasicBlock> blocksToBeRemoved) {
-    if (!assumeDynamicTypeInstructionsToRemove.isEmpty()) {
+  public AssumeRemover removeMarkedInstructions() {
+    return removeMarkedInstructions(null);
+  }
+
+  public AssumeRemover removeMarkedInstructions(Set<BasicBlock> blocksToBeRemoved) {
+    if (!assumeInstructionsToRemove.isEmpty()) {
       for (BasicBlock block : code.blocks) {
-        if (blocksToBeRemoved.contains(block)) {
+        if (blocksToBeRemoved != null && blocksToBeRemoved.contains(block)) {
           continue;
         }
         InstructionListIterator instructionIterator = block.listIterator(code);
         while (instructionIterator.hasNext()) {
           Instruction instruction = instructionIterator.next();
-          if (instruction.isAssumeDynamicType()) {
+          if (instruction.isAssume()) {
             removeIfMarked(instruction.asAssume(), instructionIterator);
           }
         }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index 9708120..5eb23bb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -35,6 +35,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.ForEachable;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
 import com.android.tools.r8.utils.LazyBox;
 import com.android.tools.r8.utils.Timing;
@@ -276,7 +277,13 @@
   }
 
   private void abandonCallSitePropagation(ForEachable<ProgramMethod> methods) {
-    methods.forEach(method -> method.getDefinition().abandonCallSiteOptimizationInfo());
+    if (InternalOptions.assertionsEnabled()) {
+      synchronized (this) {
+        methods.forEach(method -> method.getDefinition().abandonCallSiteOptimizationInfo());
+      }
+    } else {
+      methods.forEach(method -> method.getDefinition().abandonCallSiteOptimizationInfo());
+    }
   }
 
   private CallSiteOptimizationInfo computeCallSiteOptimizationInfoFromArguments(
@@ -435,12 +442,19 @@
     return null;
   }
 
-  private boolean verifyAllProgramDispatchTargetsHaveBeenAbandoned(
+  private synchronized boolean verifyAllProgramDispatchTargetsHaveBeenAbandoned(
       InvokeMethod invoke, ProgramMethod context) {
     ProgramMethodSet targets = invoke.lookupProgramDispatchTargets(appView, context);
     if (targets != null) {
       for (ProgramMethod target : targets) {
-        assert target.getDefinition().getCallSiteOptimizationInfo().isAbandoned();
+        assert target.getDefinition().getCallSiteOptimizationInfo().isAbandoned()
+            : "Expected method `"
+                + target.toSourceString()
+                + "` to be marked as abandoned (called from `"
+                + invoke.toString()
+                + "` in `"
+                + context.toSourceString()
+                + "`)";
       }
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index e769dc9..7272948 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -30,7 +30,6 @@
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.ValueMayDependOnEnvironmentAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -361,8 +360,6 @@
 
   private Map<DexEncodedField, StaticPut> findFinalFieldPutsWhileCollectingUnnecessaryStaticPuts(
       IRCode code, ProgramMethod context, Set<StaticPut> unnecessaryStaticPuts) {
-    ValueMayDependOnEnvironmentAnalysis environmentAnalysis =
-        new ValueMayDependOnEnvironmentAnalysis(appView, code);
     Map<DexEncodedField, StaticPut> finalFieldPuts = Maps.newIdentityHashMap();
     Map<DexField, Set<StaticPut>> isWrittenBefore = Maps.newIdentityHashMap();
     Set<DexEncodedField> isReadBefore = Sets.newIdentityHashSet();
@@ -471,12 +468,7 @@
             // the value of one of the fields in the enclosing class.
             if (instruction.isInvoke() && instruction.hasOutValue()) {
               Value outValue = instruction.outValue();
-              if (outValue.numberOfAllUsers() > 0) {
-                if (instruction.isInvokeNewArray()
-                    && environmentAnalysis.isConstantArrayThroughoutMethod(outValue)) {
-                  // OK, this value is technically a constant.
-                  continue;
-                }
+              if (outValue.hasNonDebugUsers()) {
                 return validateFinalFieldPuts(finalFieldPuts, isWrittenBefore);
               }
             }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 172a6b3..c6447c1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-import static com.google.common.base.Predicates.alwaysTrue;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
@@ -52,8 +51,10 @@
 import com.android.tools.r8.ir.code.IRMetadata;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.If.Type;
+import com.android.tools.r8.ir.code.InstanceFieldInstruction;
 import com.android.tools.r8.ir.code.InstanceOf;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InstructionOrPhi;
@@ -61,6 +62,7 @@
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
@@ -79,13 +81,14 @@
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOutputMode;
 import com.android.tools.r8.utils.LongInterval;
 import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.base.Suppliers;
@@ -117,6 +120,7 @@
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.Collection;
 import java.util.Deque;
 import java.util.HashMap;
@@ -159,21 +163,6 @@
     this.dexItemFactory = appView.dexItemFactory();
   }
 
-  public static void insertAssumeInstructions(
-      IRCode code, Collection<Assumer> assumers, Timing timing) {
-    insertAssumeInstructionsInBlocks(code, assumers, alwaysTrue(), timing);
-  }
-
-  public static void insertAssumeInstructionsInBlocks(
-      IRCode code, Collection<Assumer> assumers, Predicate<BasicBlock> blockTester, Timing timing) {
-    timing.begin("Insert assume instructions");
-    for (Assumer assumer : assumers) {
-      assumer.insertAssumeInstructionsInBlocks(code, code.listIterator(), blockTester, timing);
-      assert code.isConsistentSSA();
-    }
-    timing.end();
-  }
-
   public static void removeAssumeInstructions(AppView<?> appView, IRCode code) {
     // We need to update the types of all values whose definitions depend on a non-null value.
     // This is needed to preserve soundness of the types after the Assume instructions have been
@@ -1249,7 +1238,7 @@
       return false;
     }
 
-    AssumeDynamicTypeRemover assumeDynamicTypeRemover = new AssumeDynamicTypeRemover(appView, code);
+    AssumeRemover assumeRemover = new AssumeRemover(appView, code);
     boolean changed = false;
     boolean mayHaveRemovedTrivialPhi = false;
     Set<Value> affectedValues = Sets.newIdentityHashSet();
@@ -1282,7 +1271,7 @@
             // return false unless it is object.
             if (argument.getType().lessThanOrEqual(outValue.getType(), appView)) {
               affectedValues.addAll(outValue.affectedValues());
-              assumeDynamicTypeRemover.markUsersForRemoval(outValue);
+              assumeRemover.markAssumeDynamicTypeUsersForRemoval(outValue);
               mayHaveRemovedTrivialPhi |= outValue.numberOfPhiUsers() > 0;
               outValue.replaceUsers(argument);
               invoke.setOutValue(null);
@@ -1292,12 +1281,12 @@
         }
       }
     }
-    assumeDynamicTypeRemover.removeMarkedInstructions(blocksToBeRemoved).finish();
+    assumeRemover.removeMarkedInstructions(blocksToBeRemoved).finish();
     if (!blocksToBeRemoved.isEmpty()) {
       code.removeBlocks(blocksToBeRemoved);
       code.removeAllDeadAndTrivialPhis(affectedValues);
       assert code.getUnreachableBlocks().isEmpty();
-    } else if (mayHaveRemovedTrivialPhi || assumeDynamicTypeRemover.mayHaveIntroducedTrivialPhi()) {
+    } else if (mayHaveRemovedTrivialPhi || assumeRemover.mayHaveIntroducedTrivialPhi()) {
       code.removeAllDeadAndTrivialPhis(affectedValues);
     }
     if (!affectedValues.isEmpty()) {
@@ -1502,7 +1491,9 @@
       if (result == InstanceOfResult.UNKNOWN) {
         Value aliasedValue =
             inValue.getSpecificAliasedValue(
-                value -> !value.isPhi() && value.definition.isAssumeDynamicType());
+                value ->
+                    value.isDefinedByInstructionSatisfying(
+                        Instruction::isAssumeWithDynamicTypeAssumption));
         if (aliasedValue != null) {
           TypeElement dynamicType =
               aliasedValue
@@ -2232,7 +2223,6 @@
             instruction.outValue().replaceUsers(inValue);
             Value overwrittenLocal = instruction.removeDebugValue(localInfo);
             if (overwrittenLocal != null) {
-              inValue.definition.addDebugValue(overwrittenLocal);
               overwrittenLocal.addDebugLocalEnd(inValue.definition);
             }
             if (prevInstruction != null &&
@@ -2553,10 +2543,13 @@
                 if (singleFieldValue.getField() == otherSingleFieldValue.getField()) {
                   simplifyIfWithKnownCondition(code, block, theIf, 0);
                 } else {
-                  DexEncodedField field = appView.definitionFor(singleFieldValue.getField());
+                  DexClass holder = appView.definitionForHolder(singleFieldValue.getField());
+                  DexEncodedField field = singleFieldValue.getField().lookupOnClass(holder);
                   if (field != null && field.isEnum()) {
+                    DexClass otherHolder =
+                        appView.definitionForHolder(otherSingleFieldValue.getField());
                     DexEncodedField otherField =
-                        appView.definitionFor(otherSingleFieldValue.getField());
+                        otherSingleFieldValue.getField().lookupOnClass(otherHolder);
                     if (otherField != null && otherField.isEnum()) {
                       simplifyIfWithKnownCondition(code, block, theIf, 1);
                     }
@@ -2822,69 +2815,94 @@
     return changed;
   }
 
-  // Find all method invocations that never returns normally, split the block
-  // after each such invoke instruction and follow it with a block throwing a
-  // null value (which should result in NPE). Note that this throw is not
+  // Find all instructions that always throw, split the block after each such instruction and follow
+  // it with a block throwing a null value (which should result in NPE). Note that this throw is not
   // expected to be ever reached, but is intended to satisfy verifier.
-  public void processMethodsNeverReturningNormally(IRCode code) {
+  public void optimizeAlwaysThrowingInstructions(IRCode code) {
     if (!appView.appInfo().hasLiveness()) {
       return;
     }
 
+    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+    Set<Value> affectedValues = Sets.newIdentityHashSet();
+    Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
     ListIterator<BasicBlock> blockIterator = code.listIterator();
+    ProgramMethod context = code.context();
     while (blockIterator.hasNext()) {
       BasicBlock block = blockIterator.next();
       if (block.getNumber() != 0 && block.getPredecessors().isEmpty()) {
         continue;
       }
-      InstructionListIterator insnIterator = block.listIterator(code);
-      while (insnIterator.hasNext()) {
-        Instruction insn = insnIterator.next();
-        if (!insn.isInvokeMethod()) {
+      if (blocksToRemove.contains(block)) {
+        continue;
+      }
+      InstructionListIterator instructionIterator = block.listIterator(code);
+      while (instructionIterator.hasNext()) {
+        Instruction instruction = instructionIterator.next();
+        if (instruction.throwsOnNullInput()) {
+          Value inValue = instruction.getNonNullInput();
+          if (inValue.isAlwaysNull(appView)) {
+            // Insert `throw null` after the instruction if it is not guaranteed to throw an NPE.
+            if (instruction.isInstanceFieldInstruction()) {
+              InstanceFieldInstruction instanceFieldInstruction =
+                  instruction.asInstanceFieldInstruction();
+              if (instanceFieldInstruction.instructionInstanceCanThrow(
+                  appView, context, SideEffectAssumption.RECEIVER_NOT_NULL)) {
+                instructionIterator.next();
+              }
+            } else if (instruction.isInvokeMethodWithReceiver()) {
+              InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
+              SideEffectAssumption assumption =
+                  SideEffectAssumption.RECEIVER_NOT_NULL.join(
+                      SideEffectAssumption.INVOKED_METHOD_DOES_NOT_HAVE_SIDE_EFFECTS);
+              if (invoke.instructionMayHaveSideEffects(appView, context, assumption)) {
+                instructionIterator.next();
+              }
+            }
+            instructionIterator.replaceCurrentInstructionWithThrowNull(
+                appViewWithLiveness, code, blockIterator, blocksToRemove, affectedValues);
+            continue;
+          }
+        }
+
+        if (!instruction.isInvokeMethod()) {
           continue;
         }
 
-        InvokeMethod invoke = insn.asInvokeMethod();
+        InvokeMethod invoke = instruction.asInvokeMethod();
         DexEncodedMethod singleTarget =
             invoke.lookupSingleTarget(appView.withLiveness(), code.context());
-        if (singleTarget == null || !singleTarget.getOptimizationInfo().neverReturnsNormally()) {
+        if (singleTarget == null) {
           continue;
         }
 
-        // Split the block.
-        {
-          BasicBlock newBlock = insnIterator.split(code, blockIterator);
-          assert !insnIterator.hasNext(); // must be pointing *after* inserted GoTo.
-          // Move block iterator back so current block is 'newBlock'.
-          blockIterator.previous();
+        MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
 
-          newBlock.unlinkSinglePredecessorSiblingsAllowed();
+        // If the invoke instruction is a null check, we can remove it.
+        boolean isNullCheck = false;
+        if (optimizationInfo.hasNonNullParamOrThrow()) {
+          BitSet nonNullParamOrThrow = optimizationInfo.getNonNullParamOrThrow();
+          for (int i = 0; i < invoke.arguments().size(); i++) {
+            Value argument = invoke.arguments().get(i);
+            if (argument.isAlwaysNull(appView) && nonNullParamOrThrow.get(i)) {
+              isNullCheck = true;
+              break;
+            }
+          }
         }
-
-        // We want to follow the invoke instruction with 'throw null', which should
-        // be unreachable but is needed to satisfy the verifier. Note that we have
-        // to put 'throw null' into a separate block to make sure we don't get two
-        // throwing instructions in the block having catch handler. This new block
-        // does not need catch handlers.
-        Instruction gotoInsn = insnIterator.previous();
-        assert gotoInsn.isGoto();
-        assert insnIterator.hasNext();
-        BasicBlock throwNullBlock = insnIterator.split(code, blockIterator);
-        InstructionListIterator throwNullInsnIterator = throwNullBlock.listIterator(code);
-
-        // Insert 'null' constant.
-        ConstNumber nullConstant = code.createConstNull(gotoInsn.getLocalInfo());
-        nullConstant.setPosition(invoke.getPosition());
-        throwNullInsnIterator.add(nullConstant);
-
-        // Replace Goto with Throw.
-        Throw notReachableThrow = new Throw(nullConstant.outValue());
-        Instruction insnGoto = throwNullInsnIterator.next();
-        assert insnGoto.isGoto();
-        throwNullInsnIterator.replaceCurrentInstruction(notReachableThrow);
+        // If the invoke instruction never returns normally, we can insert a throw null instruction
+        // after the invoke.
+        if (isNullCheck || optimizationInfo.neverReturnsNormally()) {
+          instructionIterator.setInsertionPosition(invoke.getPosition());
+          instructionIterator.next();
+          instructionIterator.replaceCurrentInstructionWithThrowNull(
+              appViewWithLiveness, code, blockIterator, blocksToRemove, affectedValues);
+          instructionIterator.unsetInsertionPosition();
+        }
       }
     }
-    Set<Value> affectedValues = code.removeUnreachableBlocks();
+    code.removeBlocks(blocksToRemove);
+    assert code.getUnreachableBlocks().isEmpty();
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index ef9c66c..71684b1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
@@ -19,6 +20,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterators;
 import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Deque;
@@ -68,7 +70,7 @@
             || !instruction.hasOutValue()
             || instruction.outValue().hasAnyUsers();
         // No dead instructions.
-        assert !instruction.canBeDeadCode(appView, code)
+        assert !instruction.canBeDeadCode(appView, code).isDeadIfOutValueIsDead()
             || (instruction.hasOutValue() && !instruction.outValue().isDead(appView, code));
       }
     }
@@ -117,14 +119,25 @@
     while (iterator.hasPrevious()) {
       Instruction current = iterator.previous();
       // Remove unused invoke results.
-      if (current.isInvoke()
-          && current.outValue() != null
-          && !current.outValue().isUsed()) {
+      if (current.isInvoke() && current.hasOutValue() && !current.outValue().isUsed()) {
         current.setOutValue(null);
       }
-      if (!current.canBeDeadCode(appView, code)) {
+      DeadInstructionResult deadInstructionResult = current.canBeDeadCode(appView, code);
+      if (deadInstructionResult.isNotDead()) {
         continue;
       }
+      if (deadInstructionResult.isMaybeDead()) {
+        boolean satisfied = true;
+        for (Value valueRequiredToBeDead : deadInstructionResult.getValuesRequiredToBeDead()) {
+          if (!valueRequiredToBeDead.isDead(appView, code)) {
+            satisfied = false;
+            break;
+          }
+        }
+        if (!satisfied) {
+          continue;
+        }
+      }
       Value outValue = current.outValue();
       if (outValue != null && !outValue.isDead(appView, code)) {
         continue;
@@ -207,4 +220,61 @@
     }
     return builder.build();
   }
+
+  public abstract static class DeadInstructionResult {
+
+    private static final DeadInstructionResult DEFINITELY_DEAD_INSTANCE =
+        new DeadInstructionResult() {
+          @Override
+          public boolean isDeadIfOutValueIsDead() {
+            return true;
+          }
+        };
+
+    private static final DeadInstructionResult DEFINITELY_NOT_DEAD_INSTANCE =
+        new DeadInstructionResult() {
+          @Override
+          public boolean isNotDead() {
+            return true;
+          }
+        };
+
+    public static DeadInstructionResult deadIfOutValueIsDead() {
+      return DEFINITELY_DEAD_INSTANCE;
+    }
+
+    public static DeadInstructionResult notDead() {
+      return DEFINITELY_NOT_DEAD_INSTANCE;
+    }
+
+    public static DeadInstructionResult deadIfInValueIsDead(Value inValueRequiredToBeDead) {
+      return new DeadInstructionResult() {
+        @Override
+        public boolean isMaybeDead() {
+          return true;
+        }
+
+        @Override
+        public Iterable<Value> getValuesRequiredToBeDead() {
+          return () -> Iterators.singletonIterator(inValueRequiredToBeDead);
+        }
+      };
+    }
+
+    public boolean isDeadIfOutValueIsDead() {
+      return false;
+    }
+
+    public boolean isNotDead() {
+      return false;
+    }
+
+    public boolean isMaybeDead() {
+      return false;
+    }
+
+    public Iterable<Value> getValuesRequiredToBeDead() {
+      throw new Unreachable();
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index c4bf62f..f1ed11b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -265,7 +265,8 @@
       return null;
     }
 
-    if (inliner.isBlacklisted(singleTarget, whyAreYouNotInliningReporter)) {
+    if (inliner.isBlacklisted(
+        invoke, resolutionResult, singleTarget, whyAreYouNotInliningReporter)) {
       return null;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 7752d6c..cb8f858 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -53,6 +53,7 @@
 
   public void devirtualizeInvokeInterface(IRCode code) {
     Set<Value> affectedValues = Sets.newIdentityHashSet();
+    AssumeRemover assumeRemover = new AssumeRemover(appView, code);
     ProgramMethod context = code.context();
     Map<InvokeInterface, InvokeVirtual> devirtualizedCall = new IdentityHashMap<>();
     DominatorTree dominatorTree = new DominatorTree(code);
@@ -76,7 +77,7 @@
         // (out <-) invoke-virtual rcv_c, ... C#foo
         // ...
         // non_null_rcv <- non-null rcv_c  // <- Update the input rcv to the non-null, too.
-        if (current.isAssumeNonNull()) {
+        if (current.isAssumeWithNonNullAssumption()) {
           Assume nonNull = current.asAssume();
           Instruction origin = nonNull.origin();
           if (origin.isInvokeInterface()
@@ -253,8 +254,8 @@
                 it.next();
               }
             }
-
             affectedValues.addAll(receiver.affectedValues());
+            assumeRemover.markAssumeDynamicTypeUsersForRemoval(receiver);
             if (!receiver.hasLocalInfo()) {
               receiver.replaceSelectiveUsers(
                   newReceiver, ImmutableSet.of(devirtualizedInvoke), ImmutableMap.of());
@@ -266,6 +267,8 @@
         }
       }
     }
+    assumeRemover.removeMarkedInstructions();
+    affectedValues.addAll(assumeRemover.getAffectedValues());
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
index 6e2d9c7..20e0515 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
@@ -4,36 +4,19 @@
 
 package com.android.tools.r8.ir.optimize;
 
-import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
-import static com.google.common.base.Predicates.alwaysTrue;
-
 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.DexMethod;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.JumpInstruction;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Timing;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.ListIterator;
-import java.util.function.Predicate;
 
-public class DynamicTypeOptimization implements Assumer {
+public class DynamicTypeOptimization {
 
   private final AppView<AppInfoWithLiveness> appView;
 
@@ -41,125 +24,6 @@
     this.appView = appView;
   }
 
-  @Override
-  public void insertAssumeInstructions(IRCode code, Timing timing) {
-    insertAssumeInstructionsInBlocks(code, code.listIterator(), alwaysTrue(), timing);
-  }
-
-  @Override
-  public void insertAssumeInstructionsInBlocks(
-      IRCode code,
-      BasicBlockIterator blockIterator,
-      Predicate<BasicBlock> blockTester,
-      Timing timing) {
-    timing.begin("Insert assume dynamic type instructions");
-    while (blockIterator.hasNext()) {
-      BasicBlock block = blockIterator.next();
-      if (blockTester.test(block)) {
-        insertAssumeDynamicTypeInstructionsInBlock(code, blockIterator, block);
-      }
-    }
-    timing.end();
-  }
-
-  // TODO(b/127461806): Should also insert AssumeDynamicType instructions after instanceof
-  //  instructions.
-  private void insertAssumeDynamicTypeInstructionsInBlock(
-      IRCode code, ListIterator<BasicBlock> blockIterator, BasicBlock block) {
-    InstructionListIterator instructionIterator = block.listIterator(code);
-    while (instructionIterator.hasNext()) {
-      Instruction current = instructionIterator.next();
-      if (!current.hasOutValue() || !current.outValue().isUsed()) {
-        continue;
-      }
-
-      TypeElement dynamicUpperBoundType;
-      ClassTypeElement dynamicLowerBoundType;
-      if (current.isInvokeMethod()) {
-        InvokeMethod invoke = current.asInvokeMethod();
-        DexMethod invokedMethod = invoke.getInvokedMethod();
-
-        DexType staticReturnTypeRaw = invokedMethod.proto.returnType;
-        if (!staticReturnTypeRaw.isReferenceType()) {
-          continue;
-        }
-
-        if (invokedMethod.holder.isArrayType()
-            && invokedMethod.match(appView.dexItemFactory().objectMembers.clone)) {
-          dynamicUpperBoundType =
-              TypeElement.fromDexType(invokedMethod.holder, definitelyNotNull(), appView);
-          dynamicLowerBoundType = null;
-        } else {
-          DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
-          if (singleTarget == null) {
-            continue;
-          }
-
-          MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
-          if (optimizationInfo.returnsArgument()) {
-            // Don't insert an assume-instruction since we will replace all usages of the out-value
-            // by the corresponding argument.
-            continue;
-          }
-
-          dynamicUpperBoundType = optimizationInfo.getDynamicUpperBoundType();
-          dynamicLowerBoundType = optimizationInfo.getDynamicLowerBoundType();
-        }
-      } else if (current.isStaticGet()) {
-        StaticGet staticGet = current.asStaticGet();
-        DexEncodedField encodedField =
-            appView.appInfo().resolveField(staticGet.getField()).getResolvedField();
-        if (encodedField == null) {
-          continue;
-        }
-
-        dynamicUpperBoundType = encodedField.getOptimizationInfo().getDynamicUpperBoundType();
-        dynamicLowerBoundType = encodedField.getOptimizationInfo().getDynamicLowerBoundType();
-      } else {
-        continue;
-      }
-
-      Value outValue = current.outValue();
-      boolean isTrivial =
-          (dynamicUpperBoundType == null
-                  || !dynamicUpperBoundType.strictlyLessThan(outValue.getType(), appView))
-              && dynamicLowerBoundType == null;
-      if (isTrivial) {
-        continue;
-      }
-
-      if (dynamicUpperBoundType == null) {
-        dynamicUpperBoundType = outValue.getType();
-      }
-
-      // Split block if needed (only debug instructions are allowed after the throwing
-      // instruction, if any).
-      BasicBlock insertionBlock =
-          block.hasCatchHandlers() ? instructionIterator.split(code, blockIterator) : block;
-
-      // Replace usages of out-value by the out-value of the AssumeDynamicType instruction.
-      Value specializedOutValue = code.createValue(outValue.getType(), outValue.getLocalInfo());
-      outValue.replaceUsers(specializedOutValue);
-
-      // Insert AssumeDynamicType instruction.
-      Assume assumeInstruction =
-          Assume.createAssumeDynamicTypeInstruction(
-              dynamicUpperBoundType,
-              dynamicLowerBoundType,
-              specializedOutValue,
-              outValue,
-              current,
-              appView);
-      assumeInstruction.setPosition(
-          appView.options().debug ? current.getPosition() : Position.none());
-      if (insertionBlock == block) {
-        instructionIterator.add(assumeInstruction);
-      } else {
-        insertionBlock.listIterator(code).add(assumeInstruction);
-      }
-    }
-  }
-
   /**
    * Computes the dynamic return type of the given method.
    *
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 17cea8b..73da99f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -118,29 +118,40 @@
   }
 
   boolean isBlacklisted(
-      ProgramMethod method, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
-    DexMethod reference = method.getReference();
-    if (method.getDefinition().getOptimizationInfo().forceInline()
-        && appView.appInfo().neverInline.contains(reference)) {
+      InvokeMethod invoke,
+      SingleResolutionResult resolutionResult,
+      ProgramMethod singleTarget,
+      WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexMethod singleTargetReference = singleTarget.getReference();
+    if (singleTarget.getDefinition().getOptimizationInfo().forceInline()
+        && appInfo.neverInline.contains(singleTargetReference)) {
       throw new Unreachable();
     }
 
-    if (appView.appInfo().isPinned(reference)) {
+    if (appInfo.isPinned(singleTargetReference)) {
       whyAreYouNotInliningReporter.reportPinned();
       return true;
     }
 
-    if (blacklist.contains(appView.graphLense().getOriginalMethodSignature(reference))
-        || TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(reference, appView)) {
+    if (blacklist.contains(appView.graphLense().getOriginalMethodSignature(singleTargetReference))
+        || TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(
+            singleTargetReference, appView)) {
       whyAreYouNotInliningReporter.reportBlacklisted();
       return true;
     }
 
-    if (appView.appInfo().neverInline.contains(reference)) {
+    if (appInfo.neverInline.contains(singleTargetReference)) {
       whyAreYouNotInliningReporter.reportMarkedAsNeverInline();
       return true;
     }
 
+    if (appInfo.noSideEffects.containsKey(invoke.getInvokedMethod())
+        || appInfo.noSideEffects.containsKey(resolutionResult.getResolvedMethod().getReference())
+        || appInfo.noSideEffects.containsKey(singleTargetReference)) {
+      return true;
+    }
+
     return false;
   }
 
@@ -932,7 +943,7 @@
       OptimizationFeedback feedback,
       InliningIRProvider inliningIRProvider,
       Timing timing) {
-    AssumeDynamicTypeRemover assumeDynamicTypeRemover = new AssumeDynamicTypeRemover(appView, code);
+    AssumeRemover assumeRemover = new AssumeRemover(appView, code);
     Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
     BasicBlockIterator blockIterator = code.listIterator();
     ClassInitializationAnalysis classInitializationAnalysis =
@@ -1025,7 +1036,7 @@
           // Mark AssumeDynamicType instruction for the out-value for removal, if any.
           Value outValue = invoke.outValue();
           if (outValue != null) {
-            assumeDynamicTypeRemover.markUsersForRemoval(outValue);
+            assumeRemover.markAssumeDynamicTypeUsersForRemoval(outValue);
           }
 
           boolean inlineeMayHaveInvokeMethod = inlinee.code.metadata().mayHaveInvokeMethod();
@@ -1073,14 +1084,14 @@
             IteratorUtils.previousUntil(blockIterator, previous -> previous == block);
             blockIterator.next();
           }
-        } else if (current.isAssumeDynamicType()) {
-          assumeDynamicTypeRemover.removeIfMarked(current.asAssume(), iterator);
+        } else if (current.isAssume()) {
+          assumeRemover.removeIfMarked(current.asAssume(), iterator);
         }
       }
     }
     assert inlineeStack.isEmpty();
-    assumeDynamicTypeRemover.removeMarkedInstructions(blocksToRemove);
-    assumeDynamicTypeRemover.finish();
+    assumeRemover.removeMarkedInstructions(blocksToRemove);
+    assumeRemover.finish();
     classInitializationAnalysis.finish();
     code.removeBlocks(blocksToRemove);
     code.removeAllDeadAndTrivialPhis();
@@ -1129,50 +1140,33 @@
       BasicBlockIterator blockIterator,
       BasicBlock block,
       Timing timing) {
-    InternalOptions options = appView.options();
-    boolean skip =
-        !(options.enableDynamicTypeOptimization
-            || options.enableNonNullTracking
-            || options.enableValuePropagation);
-    if (skip) {
-      return;
-    }
-
     BasicBlock state = IteratorUtils.peekNext(blockIterator);
 
     Set<BasicBlock> inlineeBlocks = SetUtils.newIdentityHashSet(inlinee.blocks);
 
     // Run member value propagation on the inlinee blocks.
-    if (options.enableValuePropagation) {
+    if (appView.options().enableValuePropagation) {
       rewindBlockIteratorToFirstInlineeBlock(blockIterator, block);
       applyMemberValuePropagationToInlinee(code, blockIterator, block, inlineeBlocks);
     }
 
     // Add non-null IRs only to the inlinee blocks.
-    if (options.enableNonNullTracking) {
-      Assumer nonNullTracker = new AssumeInserter(appView);
-      applyAssumerToInlinee(nonNullTracker, code, blockIterator, block, inlineeBlocks, timing);
-    }
+    insertAssumeInstructions(code, blockIterator, block, inlineeBlocks, timing);
 
-    // Add dynamic type assumptions only to the inlinee blocks.
-    if (options.enableDynamicTypeOptimization) {
-      applyAssumerToInlinee(
-          new DynamicTypeOptimization(appView), code, blockIterator, block, inlineeBlocks, timing);
-    }
     // Restore the old state of the iterator.
     rewindBlockIteratorToFirstInlineeBlock(blockIterator, state);
     // TODO(b/72693244): need a test where refined env in inlinee affects the caller.
   }
 
-  private void applyAssumerToInlinee(
-      Assumer assumer,
+  private void insertAssumeInstructions(
       IRCode code,
       BasicBlockIterator blockIterator,
       BasicBlock block,
       Set<BasicBlock> inlineeBlocks,
       Timing timing) {
     rewindBlockIteratorToFirstInlineeBlock(blockIterator, block);
-    assumer.insertAssumeInstructionsInBlocks(code, blockIterator, inlineeBlocks::contains, timing);
+    new AssumeInserter(appView)
+        .insertAssumeInstructionsInBlocks(code, blockIterator, inlineeBlocks::contains, timing);
     assert !blockIterator.hasNext();
   }
 
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 51c1c01..96fd58c 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
@@ -230,21 +230,23 @@
       Set<Value> affectedValues,
       ListIterator<BasicBlock> blocks,
       InstructionListIterator iterator,
-      InvokeMethod current) {
-    DexMethod invokedMethod = current.getInvokedMethod();
+      InvokeMethod invoke) {
+    DexMethod invokedMethod = invoke.getInvokedMethod();
+    if (invokedMethod.proto.returnType.isVoidType()) {
+      return;
+    }
+
+    if (!invoke.hasOutValue() || !invoke.outValue().hasNonDebugUsers()) {
+      return;
+    }
+
     DexType invokedHolder = invokedMethod.holder;
     if (!invokedHolder.isClassType()) {
       return;
     }
-    DexEncodedMethod target = current.lookupSingleTarget(appView, context);
-    if (target != null && target.isInstanceInitializer()) {
-      // Member value propagation does not apply to constructors. Removing a call to a constructor
-      // that is marked as having no side effects could lead to verification errors, due to
-      // uninitialized instances being used.
-      return;
-    }
 
-    ProguardMemberRuleLookup lookup = lookupMemberRule(target);
+    DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context);
+    ProguardMemberRuleLookup lookup = lookupMemberRule(singleTarget);
     if (lookup == null) {
       // -assumenosideeffects rules are applied to upward visible and overriding methods, but only
       // references that have actual definitions are marked by the root set builder. So, here, we
@@ -253,60 +255,51 @@
           appView.appInfo().unsafeResolveMethodDueToDexFormat(invokedMethod).getSingleTarget();
       lookup = lookupMemberRule(resolutionTarget);
     }
-    boolean invokeReplaced = false;
-    if (lookup != null) {
-      boolean hasUsedOutValue = current.hasOutValue() && current.outValue().isUsed();
-      if (!hasUsedOutValue) {
-        if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS) {
-          // Remove invoke if marked as having no side effects and the return value is not used.
-          iterator.removeOrReplaceByDebugLocalRead();
-        }
-        return;
-      }
 
+    if (lookup != null) {
       // Check to see if a constant value can be assumed.
       // But, if the current matched rule is -assumenosideeffects without the return value, it won't
       // be transformed into a replacement instruction. Check if there is -assumevalues rule bound
       // to the target.
-      if (target != null
+      if (singleTarget != null
           && lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS
           && !lookup.rule.hasReturnValue()) {
-        ProguardMemberRule rule = appView.appInfo().assumedValues.get(target.toReference());
+        ProguardMemberRule rule = appView.appInfo().assumedValues.get(singleTarget.toReference());
         if (rule != null) {
           lookup = new ProguardMemberRuleLookup(RuleType.ASSUME_VALUES, rule);
         }
       }
-      invokeReplaced =
-          tryConstantReplacementFromProguard(
-              code, affectedValues, blocks, iterator, current, lookup);
+      if (tryConstantReplacementFromProguard(
+          code, affectedValues, blocks, iterator, invoke, lookup)) {
+        return;
+      }
     }
-    if (invokeReplaced || !current.hasOutValue()) {
-      return;
-    }
+
     // No Proguard rule could replace the instruction check for knowledge about the return value.
-    if (target == null || !mayPropagateValueFor(target)) {
+    if (singleTarget == null || !mayPropagateValueFor(singleTarget)) {
       return;
     }
 
-    AbstractValue abstractReturnValue = target.getOptimizationInfo().getAbstractReturnValue();
+    AbstractValue abstractReturnValue = singleTarget.getOptimizationInfo().getAbstractReturnValue();
+
     if (abstractReturnValue.isSingleValue()) {
       SingleValue singleReturnValue = abstractReturnValue.asSingleValue();
       if (singleReturnValue.isMaterializableInContext(appView, context)) {
-        BasicBlock block = current.getBlock();
-        Position position = current.getPosition();
+        BasicBlock block = invoke.getBlock();
+        Position position = invoke.getPosition();
 
         Instruction replacement =
-            singleReturnValue.createMaterializingInstruction(appView, code, current);
-        affectedValues.addAll(current.outValue().affectedValues());
-        current.moveDebugValues(replacement);
-        current.outValue().replaceUsers(replacement.outValue());
-        current.setOutValue(null);
+            singleReturnValue.createMaterializingInstruction(appView, code, invoke);
+        affectedValues.addAll(invoke.outValue().affectedValues());
+        invoke.moveDebugValues(replacement);
+        invoke.outValue().replaceUsers(replacement.outValue());
+        invoke.setOutValue(null);
 
-        if (current.isInvokeMethodWithReceiver()) {
-          replaceInstructionByNullCheckIfPossible(current, iterator, context);
-        } else if (current.isInvokeStatic()) {
+        if (invoke.isInvokeMethodWithReceiver()) {
+          replaceInstructionByNullCheckIfPossible(invoke, iterator, context);
+        } else if (invoke.isInvokeStatic()) {
           replaceInstructionByInitClassIfPossible(
-              current, target.holder(), code, iterator, context);
+              invoke, singleTarget.holder(), code, iterator, context);
         }
 
         // Insert the definition of the replacement.
@@ -316,7 +309,7 @@
         } else {
           iterator.add(replacement);
         }
-        target.getMutableOptimizationInfo().markAsPropagated();
+        singleTarget.getMutableOptimizationInfo().markAsPropagated();
       }
     }
   }
@@ -358,9 +351,7 @@
     }
 
     AbstractValue abstractValue;
-    if (field.type.isAlwaysNull(appView)) {
-      abstractValue = appView.abstractValueFactory().createSingleNumberValue(0);
-    } else if (appView.appInfo().isFieldWrittenByFieldPutInstruction(target)) {
+    if (appView.appInfo().isFieldWrittenByFieldPutInstruction(target)) {
       abstractValue = target.getOptimizationInfo().getAbstractValue();
       if (abstractValue.isUnknown() && !target.isStatic()) {
         AbstractValue abstractReceiverValue =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index ef81097..5178020 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -155,7 +155,7 @@
       return appView.appInfo().withLiveness().resolveField(field).getResolvedField();
     }
     if (field.holder == method.getHolderType()) {
-      return appView.definitionFor(field);
+      return method.getHolder().lookupField(field);
     }
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index d0ad73a..263cadd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -35,8 +35,9 @@
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.IdentityHashMap;
 import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
@@ -72,8 +73,6 @@
   private static final String SERVICE_LOADER_METHOD_PREFIX_NAME = "$load";
 
   private AtomicReference<DexProgramClass> synthesizedClass = new AtomicReference<>();
-  private ConcurrentHashMap<DexType, DexEncodedMethod> synthesizedServiceLoaders =
-      new ConcurrentHashMap<>();
 
   private final AppView<? extends AppInfoWithLiveness> appView;
 
@@ -88,6 +87,9 @@
   public void rewrite(IRCode code, MethodProcessingId methodProcessingId) {
     DexItemFactory factory = appView.dexItemFactory();
     InstructionListIterator instructionIterator = code.instructionListIterator();
+    // Create a map from service type to loader methods local to this context since two
+    // service loader calls to the same type in different methods and in the same wave can race.
+    Map<DexType, DexEncodedMethod> synthesizedServiceLoaders = new IdentityHashMap<>();
     while (instructionIterator.hasNext()) {
       Instruction instruction = instructionIterator.next();
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index 85f0274..6a8b6a5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.Strategy.ALLOW_ARGUMENT_REMOVAL;
 import static com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.Strategy.DISALLOW_ARGUMENT_REMOVAL;
 
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -15,22 +14,17 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessInfo;
+import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
-import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
-import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.FieldInstruction;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.Timing;
@@ -40,11 +34,9 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
-import java.util.BitSet;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
-import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
@@ -104,6 +96,33 @@
     this.appView = appView;
   }
 
+  public UninstantiatedTypeOptimization strenghtenOptimizationInfo() {
+    OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
+    FieldAccessInfoCollection<?> fieldAccessInfoCollection =
+        appView.appInfo().getFieldAccessInfoCollection();
+    AbstractValue nullValue = appView.abstractValueFactory().createSingleNumberValue(0);
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      clazz.forEachField(
+          field -> {
+            if (field.type().isAlwaysNull(appView)) {
+              FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.field);
+              if (fieldAccessInfo != null) {
+                // Clear all writes since each write must write `null` to the field.
+                fieldAccessInfo.asMutable().clearWrites();
+              }
+              feedback.recordFieldHasAbstractValue(field, appView, nullValue);
+            }
+          });
+      clazz.forEachMethod(
+          method -> {
+            if (method.returnType().isAlwaysNull(appView)) {
+              feedback.methodReturnsAbstractValue(method, appView, nullValue);
+            }
+          });
+    }
+    return this;
+  }
+
   public UninstantiatedTypeOptimizationGraphLense run(
       MethodPoolCollection methodPoolCollection, ExecutorService executorService, Timing timing) {
     try {
@@ -324,124 +343,4 @@
 
     return dexItemFactory.createMethod(method.holder, newProto, method.name);
   }
-
-  public void rewrite(IRCode code) {
-    AssumeDynamicTypeRemover assumeDynamicTypeRemover = new AssumeDynamicTypeRemover(appView, code);
-    Set<BasicBlock> blocksToBeRemoved = Sets.newIdentityHashSet();
-    ListIterator<BasicBlock> blockIterator = code.listIterator();
-    Set<Value> valuesToNarrow = Sets.newIdentityHashSet();
-    while (blockIterator.hasNext()) {
-      BasicBlock block = blockIterator.next();
-      if (blocksToBeRemoved.contains(block)) {
-        continue;
-      }
-      InstructionListIterator instructionIterator = block.listIterator(code);
-      while (instructionIterator.hasNext()) {
-        Instruction instruction = instructionIterator.next();
-        if (instruction.throwsOnNullInput()) {
-          Value couldBeNullValue = instruction.getNonNullInput();
-          if (isThrowNullCandidate(couldBeNullValue, instruction, appView, code.context())) {
-            instructionIterator.replaceCurrentInstructionWithThrowNull(
-                appView, code, blockIterator, blocksToBeRemoved, valuesToNarrow);
-            continue;
-          }
-        }
-        if (instruction.isInvokeMethod()) {
-          rewriteInvoke(
-              instruction.asInvokeMethod(),
-              blockIterator,
-              instructionIterator,
-              code,
-              assumeDynamicTypeRemover,
-              blocksToBeRemoved,
-              valuesToNarrow);
-        }
-      }
-    }
-    assumeDynamicTypeRemover.removeMarkedInstructions(blocksToBeRemoved).finish();
-    code.removeBlocks(blocksToBeRemoved);
-    code.removeAllDeadAndTrivialPhis(valuesToNarrow);
-    code.removeUnreachableBlocks();
-    if (!valuesToNarrow.isEmpty()) {
-      new TypeAnalysis(appView).narrowing(valuesToNarrow);
-    }
-    assert code.isConsistentSSA();
-  }
-
-  private static boolean isThrowNullCandidate(
-      Value couldBeNullValue,
-      Instruction current,
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      ProgramMethod context) {
-    if (!couldBeNullValue.isAlwaysNull(appView)) {
-      return false;
-    }
-    if (current.isFieldInstruction()) {
-      // Other resolution-related errors come first.
-      FieldInstruction fieldInstruction = current.asFieldInstruction();
-      // We can't replace the current instruction with `throw null` if it may throw another
-      // exception than NullPointerException.
-      if (fieldInstruction.instructionInstanceCanThrow(
-          appView, context, SideEffectAssumption.RECEIVER_NOT_NULL)) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  // invoke instructions with a null receiver has already been rewritten to `throw null`.
-  // At this point, we attempt to explore non-null-param-or-throw optimization info and replace
-  // the invocation with `throw null` if an argument is known to be null and the method is going to
-  // throw for that null argument.
-  private void rewriteInvoke(
-      InvokeMethod invoke,
-      ListIterator<BasicBlock> blockIterator,
-      InstructionListIterator instructionIterator,
-      IRCode code,
-      AssumeDynamicTypeRemover assumeDynamicTypeRemover,
-      Set<BasicBlock> blocksToBeRemoved,
-      Set<Value> affectedValues) {
-    DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.context());
-    if (target == null) {
-      return;
-    }
-
-    BitSet facts = target.getOptimizationInfo().getNonNullParamOrThrow();
-    if (facts != null) {
-      for (int i = 0; i < invoke.arguments().size(); i++) {
-        Value argument = invoke.arguments().get(i);
-        if (argument.isAlwaysNull(appView) && facts.get(i)) {
-          instructionIterator.replaceCurrentInstructionWithThrowNull(
-              appView, code, blockIterator, blocksToBeRemoved, affectedValues);
-          return;
-        }
-      }
-    }
-
-    DexType returnType = target.method.proto.returnType;
-    if (returnType.isAlwaysNull(appView)) {
-      replaceOutValueByNull(
-          invoke, instructionIterator, code, assumeDynamicTypeRemover, affectedValues);
-    }
-  }
-
-  private void replaceOutValueByNull(
-      Instruction instruction,
-      InstructionListIterator instructionIterator,
-      IRCode code,
-      AssumeDynamicTypeRemover assumeDynamicTypeRemover,
-      Set<Value> affectedValues) {
-    assert instructionIterator.peekPrevious() == instruction;
-    if (instruction.hasOutValue()) {
-      Value outValue = instruction.outValue();
-      if (outValue.numberOfAllUsers() > 0) {
-        assumeDynamicTypeRemover.markUsersForRemoval(outValue);
-        instructionIterator.previous();
-        affectedValues.addAll(outValue.affectedValues());
-        outValue.replaceUsers(
-            instructionIterator.insertConstNullInstruction(code, appView.options()));
-        instructionIterator.next();
-      }
-    }
-  }
 }
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 379fc28..c5cd89d 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
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.enums;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 
 import com.android.tools.r8.graph.AppView;
@@ -38,6 +39,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.MemberType;
@@ -325,6 +327,8 @@
       return;
     }
     ImmutableSet<DexType> enumsToUnbox = ImmutableSet.copyOf(this.enumsUnboxingCandidates.keySet());
+    // Update keep info on any of the enum methods of the removed classes.
+    updatePinnedItems(enumsToUnbox);
     enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumsToUnbox);
     NestedGraphLense enumUnboxingLens = new TreeFixer(enumsToUnbox).fixupTypeReferences();
     appView.setUnboxedEnums(enumUnboxerRewriter.getEnumsToUnbox());
@@ -368,6 +372,21 @@
     postBuilder.rewrittenWithLens(appView, previousLens);
   }
 
+  private void updatePinnedItems(Set<DexType> enumsToUnbox) {
+    appView
+        .appInfo()
+        .getKeepInfo()
+        .mutate(
+            keepInfo -> {
+              for (DexType type : enumsToUnbox) {
+                DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+                assert !keepInfo.getClassInfo(clazz).isPinned();
+                clazz.forEachProgramMethod(keepInfo::unsafeUnpinMethod);
+                clazz.forEachField(field -> keepInfo.unsafeUnpinField(clazz, field));
+              }
+            });
+  }
+
   public void finishAnalysis() {
     for (DexType toUnbox : enumsUnboxingCandidates.keySet()) {
       DexProgramClass enumClass = appView.definitionForProgramType(toUnbox);
@@ -418,6 +437,7 @@
       DexMethod singleTarget = encodedSingleTarget.method;
       DexClass dexClass = appView.definitionFor(singleTarget.holder);
       if (dexClass == null) {
+        assert false;
         return Reason.INVALID_INVOKE;
       }
       if (dexClass.isProgramClass()) {
@@ -429,14 +449,22 @@
             return Reason.INVALID_INIT;
           }
         }
+        // Check that the enum-value only flows into parameters whose type exactly matches the
+        // enum's type.
         int offset = BooleanUtils.intValue(!encodedSingleTarget.isStatic());
         for (int i = 0; i < singleTarget.proto.parameters.size(); i++) {
-          if (invokeMethod.inValues().get(offset + i) == enumValue) {
+          if (invokeMethod.getArgument(offset + i) == enumValue) {
             if (singleTarget.proto.parameters.values[i].toBaseType(factory) != enumClass.type) {
               return Reason.GENERIC_INVOKE;
             }
           }
         }
+        if (invokeMethod.isInvokeMethodWithReceiver()) {
+          Value receiver = invokeMethod.asInvokeMethodWithReceiver().getReceiver();
+          if (receiver == enumValue && dexClass.isInterface()) {
+            return Reason.DEFAULT_METHOD_INVOKE;
+          }
+        }
         return Reason.ELIGIBLE;
       }
       if (dexClass.isClasspathClass()) {
@@ -457,6 +485,8 @@
         return Reason.ELIGIBLE;
       } else if (singleTarget == factory.enumMethods.ordinal) {
         return Reason.ELIGIBLE;
+      } else if (singleTarget == factory.enumMethods.hashCode) {
+        return Reason.ELIGIBLE;
       } else if (singleTarget == factory.enumMethods.constructor) {
         // Enum constructor call is allowed only if first call of an enum initializer.
         if (code.method().isInstanceInitializer()
@@ -645,10 +675,9 @@
     INTERFACE,
     INSTANCE_FIELD,
     GENERIC_INVOKE,
+    DEFAULT_METHOD_INVOKE,
     UNEXPECTED_STATIC_FIELD,
     UNRESOLVABLE_FIELD,
-    VIRTUAL_METHOD,
-    UNEXPECTED_DIRECT_METHOD,
     CONST_CLASS,
     INVALID_PHI,
     NO_INIT,
@@ -697,7 +726,6 @@
                     if (m.isInitializer()) {
                       clearEnumToUnboxMethod(m);
                     } else {
-                      assert m.isStatic();
                       unboxedEnumsMethods.add(
                           fixupEncodedMethodToUtility(m, factory.enumUnboxingUtilityType));
                       methodsToRemove.add(m);
@@ -717,7 +745,7 @@
           appView.definitionForProgramType(factory.enumUnboxingUtilityType);
       assert utilityClass != null : "Should have been synthesized upfront";
       utilityClass.addDirectMethods(unboxedEnumsMethods);
-      return lensBuilder.build(factory, appView.graphLense());
+      return lensBuilder.build(factory, appView.graphLense(), enumsToUnbox);
     }
 
     private void clearEnumToUnboxMethod(DexEncodedMethod enumMethod) {
@@ -726,11 +754,7 @@
       // enumUnboxerRewriter will generate invalid code.
       // To work around this problem we clear such methods, i.e., we replace the code object by
       // an empty throwing code object, so reprocessing won't take time and will be valid.
-      enumMethod.setCode(
-          appView.options().isGeneratingClassFiles()
-              ? enumMethod.buildEmptyThrowingCfCode()
-              : enumMethod.buildEmptyThrowingDexCode(),
-          appView);
+      enumMethod.setCode(enumMethod.buildEmptyThrowingCode(appView.options()), appView);
     }
 
     private DexEncodedMethod fixupEncodedMethodToUtility(
@@ -738,17 +762,29 @@
       DexMethod method = encodedMethod.method;
       DexString newMethodName =
           factory.createString(
-              enumUnboxerRewriter.compatibleName(method.holder) + "$$" + method.name.toString());
-      DexMethod newMethod = fixupMethod(method, newHolder, newMethodName);
-      lensBuilder.move(method, newMethod, encodedMethod.isStatic());
+              enumUnboxerRewriter.compatibleName(method.holder)
+                  + "$"
+                  + (encodedMethod.isDirectMethod() ? "d" : "v")
+                  + "$"
+                  + method.name.toString());
+      DexProto proto =
+          encodedMethod.isStatic()
+              ? method.proto
+              : factory.prependTypeToProto(method.holder, method.proto);
+      DexMethod newMethod = factory.createMethod(newHolder, fixupProto(proto), newMethodName);
+      lensBuilder.move(method, encodedMethod.isStatic(), newMethod, true);
       encodedMethod.accessFlags.promoteToPublic();
+      encodedMethod.accessFlags.promoteToStatic();
+      encodedMethod.clearAnnotations();
+      encodedMethod.clearParameterAnnotations();
       return encodedMethod.toTypeSubstitutedMethod(newMethod);
     }
 
     private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod encodedMethod) {
       DexMethod newMethod = fixupMethod(encodedMethod.method);
       if (newMethod != encodedMethod.method) {
-        lensBuilder.move(encodedMethod.method, newMethod, encodedMethod.isStatic());
+        boolean isStatic = encodedMethod.isStatic();
+        lensBuilder.move(encodedMethod.method, isStatic, newMethod, isStatic);
         return encodedMethod.toTypeSubstitutedMethod(newMethod);
       }
       return encodedMethod;
@@ -819,6 +855,7 @@
   private static class EnumUnboxingLens extends NestedGraphLense {
 
     private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges;
+    private final Set<DexType> unboxedEnums;
 
     EnumUnboxingLens(
         Map<DexType, DexType> typeMap,
@@ -828,7 +865,8 @@
         BiMap<DexMethod, DexMethod> originalMethodSignatures,
         GraphLense previousLense,
         DexItemFactory dexItemFactory,
-        Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges) {
+        Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges,
+        Set<DexType> unboxedEnums) {
       super(
           typeMap,
           methodMap,
@@ -838,6 +876,7 @@
           previousLense,
           dexItemFactory);
       this.prototypeChanges = prototypeChanges;
+      this.unboxedEnums = unboxedEnums;
     }
 
     @Override
@@ -849,6 +888,17 @@
       return prototypeChanges.getOrDefault(method, RewrittenPrototypeDescription.none());
     }
 
+    @Override
+    protected Invoke.Type mapInvocationType(
+        DexMethod newMethod, DexMethod originalMethod, Invoke.Type type) {
+      if (unboxedEnums.contains(originalMethod.holder)) {
+        // Methods moved from unboxed enums to the utility class are either static or statified.
+        assert newMethod != originalMethod;
+        return Invoke.Type.STATIC;
+      }
+      return type;
+    }
+
     public static Builder builder() {
       return new Builder();
     }
@@ -858,15 +908,23 @@
       private Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges =
           new IdentityHashMap<>();
 
-      public void move(DexMethod from, DexMethod to, boolean isStatic) {
+      public void move(DexMethod from, boolean fromStatic, DexMethod to, boolean toStatic) {
         super.move(from, to);
-        int offset = BooleanUtils.intValue(!isStatic);
+        int offsetDiff = 0;
+        int toOffset = BooleanUtils.intValue(!toStatic);
         ArgumentInfoCollection.Builder builder = ArgumentInfoCollection.builder();
+        if (fromStatic != toStatic) {
+          assert toStatic;
+          offsetDiff = 1;
+          builder.addArgumentInfo(
+              0, new RewrittenTypeInfo(from.holder, to.proto.parameters.values[0]));
+        }
         for (int i = 0; i < from.proto.parameters.size(); i++) {
           DexType fromType = from.proto.parameters.values[i];
-          DexType toType = to.proto.parameters.values[i];
+          DexType toType = to.proto.parameters.values[i + offsetDiff];
           if (fromType != toType) {
-            builder.addArgumentInfo(i + offset, new RewrittenTypeInfo(fromType, toType));
+            builder.addArgumentInfo(
+                i + offsetDiff + toOffset, new RewrittenTypeInfo(fromType, toType));
           }
         }
         RewrittenTypeInfo returnInfo =
@@ -877,8 +935,8 @@
             to, RewrittenPrototypeDescription.createForRewrittenTypes(returnInfo, builder.build()));
       }
 
-      @Override
-      public EnumUnboxingLens build(DexItemFactory dexItemFactory, GraphLense previousLense) {
+      public EnumUnboxingLens build(
+          DexItemFactory dexItemFactory, GraphLense previousLense, Set<DexType> unboxedEnums) {
         if (typeMap.isEmpty() && methodMap.isEmpty() && fieldMap.isEmpty()) {
           return null;
         }
@@ -890,7 +948,8 @@
             originalMethodSignatures,
             previousLense,
             dexItemFactory,
-            ImmutableMap.copyOf(prototypeChanges));
+            ImmutableMap.copyOf(prototypeChanges),
+            ImmutableSet.copyOf(unboxedEnums));
       }
     }
   }
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 b5530c2..08ba8b4 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
@@ -7,17 +7,15 @@
 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.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMember;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxer.Reason;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -57,12 +55,6 @@
       enumUnboxer.reportFailure(clazz.type, Reason.SUBTYPES);
       return false;
     }
-    // TODO(b/147860220): interfaces without default methods should be acceptable if the build setup
-    // is correct (all abstract methods are implemented).
-    if (!clazz.interfaces.isEmpty()) {
-      enumUnboxer.reportFailure(clazz.type, Reason.INTERFACE);
-      return false;
-    }
     if (!clazz.instanceFields().isEmpty()) {
       enumUnboxer.reportFailure(clazz.type, Reason.INSTANCE_FIELD);
       return false;
@@ -71,10 +63,6 @@
       enumUnboxer.reportFailure(clazz.type, Reason.UNEXPECTED_STATIC_FIELD);
       return false;
     }
-    if (clazz.getMethodCollection().hasVirtualMethods()) {
-      enumUnboxer.reportFailure(clazz.type, Reason.VIRTUAL_METHOD);
-      return false;
-    }
     EnumValueInfoMap enumValueInfoMap =
         appView.appInfo().withLiveness().getEnumValueInfoMap(clazz.type);
     if (enumValueInfoMap == null) {
@@ -85,8 +73,8 @@
     // TODO(b/155036467): Fail lazily when an unsupported method is not only present but also used.
     // Only Enums with default initializers and static methods can be unboxed at the moment.
     for (DexEncodedMethod directMethod : clazz.directMethods()) {
-      if (!(directMethod.isStatic() || isStandardEnumInitializer(directMethod))) {
-        enumUnboxer.reportFailure(clazz.type, Reason.UNEXPECTED_DIRECT_METHOD);
+      if (directMethod.isInstanceInitializer() && !isStandardEnumInitializer(directMethod)) {
+        enumUnboxer.reportFailure(clazz.type, Reason.INVALID_INIT);
         return false;
       }
     }
@@ -146,22 +134,17 @@
     // A holder type, for field or method, should block enum unboxing only if the enum type is
     // also kept. This is to allow the keep rule -keepclassmembers to be used on enums while
     // enum unboxing can still be performed.
-    for (DexReference item : appView.appInfo().getPinnedItems()) {
-      if (item.isDexType()) {
-        removePinnedCandidate(item.asDexType());
-      } else if (item.isDexField()) {
-        DexField field = item.asDexField();
-        removePinnedIfNotHolder(field, field.type);
-      } else {
-        assert item.isDexMethod();
-        DexMethod method = item.asDexMethod();
-        DexProto proto = method.proto;
-        removePinnedIfNotHolder(method, proto.returnType);
-        for (DexType parameterType : proto.parameters.values) {
-          removePinnedIfNotHolder(method, parameterType);
-        }
-      }
-    }
+    KeepInfoCollection keepInfo = appView.appInfo().getKeepInfo();
+    keepInfo.forEachPinnedType(this::removePinnedCandidate);
+    keepInfo.forEachPinnedField(field -> removePinnedIfNotHolder(field, field.type));
+    keepInfo.forEachPinnedMethod(
+        method -> {
+          DexProto proto = method.proto;
+          removePinnedIfNotHolder(method, proto.returnType);
+          for (DexType parameterType : proto.parameters.values) {
+            removePinnedIfNotHolder(method, parameterType);
+          }
+        });
   }
 
   private void removePinnedIfNotHolder(DexMember<?, ?> member, DexType type) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 541cd92..1832a2a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -129,7 +129,8 @@
         DexMethod invokedMethod = invokeMethod.getInvokedMethod();
         DexType enumType = getEnumTypeOrNull(invokeMethod.getReceiver(), convertedEnums);
         if (enumType != null) {
-          if (invokedMethod == factory.enumMethods.ordinal) {
+          if (invokedMethod == factory.enumMethods.ordinal
+              || invokedMethod == factory.enumMethods.hashCode) {
             replaceEnumInvoke(
                 iterator, invokeMethod, ordinalUtilityMethod, m -> synthesizeOrdinalMethod());
             continue;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
index 1d6668c..2cd8633 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.enums;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -15,6 +17,7 @@
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
 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.ArrayGet;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
@@ -61,6 +64,7 @@
       return;
     }
 
+    Set<Value> affectedValues = Sets.newIdentityHashSet();
     InstructionListIterator iterator = code.instructionListIterator();
     while (iterator.hasNext()) {
       Instruction current = iterator.next();
@@ -103,8 +107,12 @@
         if (isOrdinalInvoke) {
           iterator.replaceCurrentInstruction(new ConstNumber(outValue, valueInfo.ordinal));
         } else if (isNameInvoke) {
+          Value newValue =
+              code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
           iterator.replaceCurrentInstruction(
-              new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
+              new ConstString(
+                  newValue, enumField.name, ThrowingInfo.defaultForConstString(appView.options())));
+          newValue.addAffectedValuesTo(affectedValues);
         } else {
           assert isToStringInvoke;
           DexClass enumClazz = appView.appInfo().definitionFor(enumField.type);
@@ -119,12 +127,16 @@
           if (singleTarget != null && singleTarget.method != factory.enumMethods.toString) {
             continue;
           }
+          Value newValue =
+              code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
           iterator.replaceCurrentInstruction(
-              new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
+              new ConstString(
+                  newValue, enumField.name, ThrowingInfo.defaultForConstString(appView.options())));
+          newValue.addAffectedValuesTo(affectedValues);
         }
       } else if (current.isArrayLength()) {
         // Rewrites MyEnum.values().length to a constant int.
-        Instruction arrayDefinition = current.asArrayLength().array().definition;
+        Instruction arrayDefinition = current.asArrayLength().array().getAliasedValue().definition;
         if (arrayDefinition != null && arrayDefinition.isInvokeStatic()) {
           DexMethod invokedMethod = arrayDefinition.asInvokeStatic().getInvokedMethod();
           DexProgramClass enumClass = appView.definitionForProgramType(invokedMethod.holder);
@@ -140,6 +152,9 @@
         }
       }
     }
+    if (!affectedValues.isEmpty()) {
+      new TypeAnalysis(appView).narrowing(affectedValues);
+    }
     assert code.isConsistentSSA();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 59e24a6..0e3e339 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -18,7 +18,7 @@
 import java.util.BitSet;
 import java.util.Set;
 
-public class DefaultMethodOptimizationInfo implements MethodOptimizationInfo {
+public class DefaultMethodOptimizationInfo extends MethodOptimizationInfo {
 
   public static final MethodOptimizationInfo DEFAULT_INSTANCE = new DefaultMethodOptimizationInfo();
 
@@ -124,11 +124,6 @@
   }
 
   @Override
-  public boolean neverReturnsNull() {
-    return UNKNOWN_NEVER_RETURNS_NULL;
-  }
-
-  @Override
   public boolean neverReturnsNormally() {
     return UNKNOWN_NEVER_RETURNS_NORMALLY;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
index 09ad5e5..0365122 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
@@ -26,6 +26,11 @@
 
   public abstract TypeElement getDynamicUpperBoundType();
 
+  public final TypeElement getDynamicUpperBoundTypeOrElse(TypeElement orElse) {
+    TypeElement dynamicUpperBoundType = getDynamicUpperBoundType();
+    return dynamicUpperBoundType != null ? dynamicUpperBoundType : orElse;
+  }
+
   public abstract boolean isDead();
 
   public abstract boolean valueHasBeenPropagated();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index 16ce797..f919a1e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -15,7 +15,7 @@
 import java.util.BitSet;
 import java.util.Set;
 
-public interface MethodOptimizationInfo {
+public abstract class MethodOptimizationInfo {
 
   enum InlinePreference {
     NeverInline,
@@ -23,63 +23,70 @@
     Default
   }
 
-  boolean isDefaultMethodOptimizationInfo();
+  public abstract boolean isDefaultMethodOptimizationInfo();
 
-  boolean isUpdatableMethodOptimizationInfo();
+  public abstract boolean isUpdatableMethodOptimizationInfo();
 
-  UpdatableMethodOptimizationInfo asUpdatableMethodOptimizationInfo();
+  public abstract UpdatableMethodOptimizationInfo asUpdatableMethodOptimizationInfo();
 
-  boolean cannotBeKept();
+  public abstract boolean cannotBeKept();
 
-  boolean classInitializerMayBePostponed();
+  public abstract boolean classInitializerMayBePostponed();
 
-  TypeElement getDynamicUpperBoundType();
+  public abstract TypeElement getDynamicUpperBoundType();
 
-  ClassTypeElement getDynamicLowerBoundType();
+  public final TypeElement getDynamicUpperBoundTypeOrElse(TypeElement orElse) {
+    TypeElement dynamicUpperBoundType = getDynamicUpperBoundType();
+    return dynamicUpperBoundType != null ? dynamicUpperBoundType : orElse;
+  }
 
-  ParameterUsage getParameterUsages(int parameter);
+  public abstract ClassTypeElement getDynamicLowerBoundType();
 
-  BitSet getNonNullParamOrThrow();
+  public abstract ParameterUsage getParameterUsages(int parameter);
 
-  BitSet getNonNullParamOnNormalExits();
+  public final boolean hasNonNullParamOrThrow() {
+    return getNonNullParamOrThrow() != null;
+  }
 
-  boolean hasBeenInlinedIntoSingleCallSite();
+  public abstract BitSet getNonNullParamOrThrow();
 
-  boolean isReachabilitySensitive();
+  public abstract BitSet getNonNullParamOnNormalExits();
 
-  boolean returnsArgument();
+  public abstract boolean hasBeenInlinedIntoSingleCallSite();
 
-  int getReturnedArgument();
+  public abstract boolean isReachabilitySensitive();
 
-  boolean neverReturnsNull();
+  public abstract boolean returnsArgument();
 
-  boolean neverReturnsNormally();
+  public abstract int getReturnedArgument();
 
-  BridgeInfo getBridgeInfo();
+  public abstract boolean neverReturnsNormally();
 
-  ClassInlinerEligibilityInfo getClassInlinerEligibility();
+  public abstract BridgeInfo getBridgeInfo();
 
-  Set<DexType> getInitializedClassesOnNormalExit();
+  public abstract ClassInlinerEligibilityInfo getClassInlinerEligibility();
 
-  InstanceInitializerInfo getInstanceInitializerInfo();
+  public abstract Set<DexType> getInitializedClassesOnNormalExit();
 
-  boolean isInitializerEnablingJavaVmAssertions();
+  public abstract InstanceInitializerInfo getInstanceInitializerInfo();
 
-  AbstractValue getAbstractReturnValue();
+  public abstract boolean isInitializerEnablingJavaVmAssertions();
 
-  boolean forceInline();
+  public abstract AbstractValue getAbstractReturnValue();
 
-  boolean neverInline();
+  public abstract boolean forceInline();
 
-  boolean checksNullReceiverBeforeAnySideEffect();
+  public abstract boolean neverInline();
 
-  boolean triggersClassInitBeforeAnySideEffect();
+  public abstract boolean checksNullReceiverBeforeAnySideEffect();
 
-  boolean mayHaveSideEffects();
+  public abstract boolean triggersClassInitBeforeAnySideEffect();
 
-  boolean returnValueOnlyDependsOnArguments();
+  public abstract boolean mayHaveSideEffects();
 
-  boolean returnValueHasBeenPropagated();
+  public abstract boolean returnValueOnlyDependsOnArguments();
 
-  UpdatableMethodOptimizationInfo mutableCopy();
+  public abstract boolean returnValueHasBeenPropagated();
+
+  public abstract UpdatableMethodOptimizationInfo mutableCopy();
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index cac5801..9e270af 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -360,14 +360,12 @@
       return;
     }
     Value returnValue = firstExit.returnValue();
-    boolean isNeverNull = returnValue.getType().isReferenceType() && returnValue.isNeverNull();
     for (int i = 1; i < normalExits.size(); i++) {
       Return exit = normalExits.get(i).exit().asReturn();
       Value value = exit.returnValue();
       if (value != returnValue) {
         returnValue = null;
       }
-      isNeverNull &= value.getType().isReferenceType() && value.isNeverNull();
     }
     if (returnValue != null) {
       Value aliasedValue = returnValue.getAliasedValue();
@@ -382,9 +380,6 @@
         }
       }
     }
-    if (isNeverNull) {
-      feedback.methodNeverReturnsNull(method);
-    }
   }
 
   private void computeInstanceInitializerInfo(
@@ -958,21 +953,38 @@
       if (!staticReturnTypeRaw.isReferenceType()) {
         return;
       }
-      TypeElement dynamicReturnType =
+      TypeElement dynamicUpperBoundReturnType =
           dynamicTypeOptimization.computeDynamicReturnType(method, code);
-      if (dynamicReturnType != null) {
-        TypeElement staticReturnType =
-            TypeElement.fromDexType(staticReturnTypeRaw, Nullability.maybeNull(), appView);
-        // If the dynamic return type is not more precise than the static return type there is no
-        // need to record it.
-        if (dynamicReturnType.strictlyLessThan(staticReturnType, appView)) {
-          feedback.methodReturnsObjectWithUpperBoundType(method, appView, dynamicReturnType);
+      if (dynamicUpperBoundReturnType != null) {
+        if (dynamicUpperBoundReturnType.isReferenceType()
+            && dynamicUpperBoundReturnType.isDefinitelyNull()) {
+          feedback.methodReturnsAbstractValue(
+              method, appView, appView.abstractValueFactory().createSingleNumberValue(0));
+          feedback.methodReturnsObjectWithUpperBoundType(method, appView, TypeElement.getNull());
+        } else {
+          TypeElement staticReturnType =
+              TypeElement.fromDexType(staticReturnTypeRaw, Nullability.maybeNull(), appView);
+          // If the dynamic return type is not more precise than the static return type there is no
+          // need to record it.
+          if (dynamicUpperBoundReturnType.strictlyLessThan(staticReturnType, appView)) {
+            feedback.methodReturnsObjectWithUpperBoundType(
+                method, appView, dynamicUpperBoundReturnType);
+          }
         }
       }
-      ClassTypeElement exactReturnType =
+
+      if (dynamicUpperBoundReturnType != null && dynamicUpperBoundReturnType.isNullType()) {
+        return;
+      }
+
+      ClassTypeElement dynamicLowerBoundReturnType =
           dynamicTypeOptimization.computeDynamicLowerBoundType(method, code);
-      if (exactReturnType != null) {
-        feedback.methodReturnsObjectWithLowerBoundType(method, exactReturnType);
+      if (dynamicLowerBoundReturnType != null) {
+        assert dynamicUpperBoundReturnType == null
+            || dynamicUpperBoundReturnType
+                .nullability()
+                .lessThanOrEqual(dynamicLowerBoundReturnType.nullability());
+        feedback.methodReturnsObjectWithLowerBoundType(method, dynamicLowerBoundReturnType);
       }
     }
   }
@@ -1156,7 +1168,7 @@
       // Collect basic blocks that check nullability of the parameter.
       nullCheckedBlocks.clear();
       for (Instruction user : argument.uniqueUsers()) {
-        if (user.isAssumeNonNull()) {
+        if (user.isAssumeWithNonNullAssumption()) {
           nullCheckedBlocks.add(user.asAssume().getBlock());
         }
         if (user.isIf()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index d4eef20..d9db007 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -201,11 +201,6 @@
   }
 
   @Override
-  public synchronized void methodNeverReturnsNull(DexEncodedMethod method) {
-    getMethodOptimizationInfoForUpdating(method).markNeverReturnsNull();
-  }
-
-  @Override
   public synchronized void methodNeverReturnsNormally(DexEncodedMethod method) {
     getMethodOptimizationInfoForUpdating(method).markNeverReturnsNormally();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 435f05f..49ffaf9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -90,9 +90,6 @@
   public void methodReturnValueOnlyDependsOnArguments(DexEncodedMethod method) {}
 
   @Override
-  public void methodNeverReturnsNull(DexEncodedMethod method) {}
-
-  @Override
   public void methodNeverReturnsNormally(DexEncodedMethod method) {}
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 9696162..822e0c5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -104,7 +104,7 @@
   @Override
   public void methodReturnsObjectWithUpperBoundType(
       DexEncodedMethod method, AppView<?> appView, TypeElement type) {
-    // Ignored.
+    method.getMutableOptimizationInfo().markReturnsObjectWithUpperBoundType(appView, type);
   }
 
   @Override
@@ -124,11 +124,6 @@
   }
 
   @Override
-  public void methodNeverReturnsNull(DexEncodedMethod method) {
-    method.getMutableOptimizationInfo().markNeverReturnsNull();
-  }
-
-  @Override
   public void methodNeverReturnsNormally(DexEncodedMethod method) {
     // Ignored.
   }
@@ -140,8 +135,7 @@
 
   @Override
   public void markProcessed(DexEncodedMethod method, ConstraintWithTarget state) {
-    // Just as processed, don't provide any inlining constraints.
-    method.markProcessed(ConstraintWithTarget.NEVER);
+    method.markProcessed(state);
   }
 
   @Override
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 c1012ca..3971c57 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
@@ -22,7 +22,7 @@
 import java.util.Set;
 import java.util.function.Function;
 
-public class UpdatableMethodOptimizationInfo implements MethodOptimizationInfo {
+public class UpdatableMethodOptimizationInfo extends MethodOptimizationInfo {
 
   private Set<DexType> initializedClassesOnNormalExit =
       DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT;
@@ -71,9 +71,9 @@
   private static final int HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG = 0x4;
   private static final int MAY_HAVE_SIDE_EFFECT_FLAG = 0x8;
   private static final int RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG = 0x10;
-  private static final int NEVER_RETURNS_NULL_FLAG = 0x20;
+  private static final int UNUSED_FLAG_1 = 0x20;
   private static final int NEVER_RETURNS_NORMALLY_FLAG = 0x40;
-  private static final int UNUSED_FLAG = 0x80;
+  private static final int UNUSED_FLAG_2 = 0x80;
   private static final int CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG = 0x100;
   private static final int TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG = 0x200;
   private static final int INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG = 0x400;
@@ -97,11 +97,10 @@
     defaultFlags |=
         BooleanUtils.intValue(defaultOptInfo.returnValueOnlyDependsOnArguments())
             * RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG;
-    defaultFlags |=
-        BooleanUtils.intValue(defaultOptInfo.neverReturnsNull()) * NEVER_RETURNS_NULL_FLAG;
+    defaultFlags |= 0 * UNUSED_FLAG_1;
     defaultFlags |=
         BooleanUtils.intValue(defaultOptInfo.neverReturnsNormally()) * NEVER_RETURNS_NORMALLY_FLAG;
-    defaultFlags |= 0 * UNUSED_FLAG;
+    defaultFlags |= 0 * UNUSED_FLAG_2;
     defaultFlags |=
         BooleanUtils.intValue(defaultOptInfo.checksNullReceiverBeforeAnySideEffect())
             * CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG;
@@ -293,11 +292,6 @@
   }
 
   @Override
-  public boolean neverReturnsNull() {
-    return isFlagSet(NEVER_RETURNS_NULL_FLAG);
-  }
-
-  @Override
   public boolean neverReturnsNormally() {
     return isFlagSet(NEVER_RETURNS_NORMALLY_FLAG);
   }
@@ -402,10 +396,6 @@
     setFlag(RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG);
   }
 
-  void markNeverReturnsNull() {
-    setFlag(NEVER_RETURNS_NULL_FLAG);
-  }
-
   void markNeverReturnsNormally() {
     setFlag(NEVER_RETURNS_NORMALLY_FLAG);
   }
@@ -429,6 +419,9 @@
             || type.lessThanOrEqualUpToNullability(returnsObjectWithUpperBoundType, appView)
         : "upper bound type changed from " + returnsObjectWithUpperBoundType + " to " + type;
     returnsObjectWithUpperBoundType = type;
+    if (type.isNullType()) {
+      returnsObjectWithLowerBoundType = null;
+    }
   }
 
   void markReturnsObjectWithLowerBoundType(ClassTypeElement type) {
@@ -436,9 +429,7 @@
     // Currently, we only have a lower bound type when we have _exact_ runtime type information.
     // Thus, the type should never become more precise (although the nullability could).
     assert returnsObjectWithLowerBoundType == DefaultMethodOptimizationInfo.UNKNOWN_CLASS_TYPE
-            || (type.equalUpToNullability(returnsObjectWithLowerBoundType)
-                && type.nullability()
-                    .lessThanOrEqual(returnsObjectWithLowerBoundType.nullability()))
+            || type.equalUpToNullability(returnsObjectWithLowerBoundType)
         : "lower bound type changed from " + returnsObjectWithLowerBoundType + " to " + type;
     returnsObjectWithLowerBoundType = type;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
index 2fe8a19..fe70918 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
@@ -4,7 +4,9 @@
 
 package com.android.tools.r8.ir.optimize.info.field;
 
+
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
@@ -32,9 +34,10 @@
       BiConsumer<DexEncodedField, InstanceFieldInitializationInfo> consumer) {
     infos.forEach(
         (field, info) -> {
-          DexEncodedField encodedField = definitions.definitionFor(field);
-          if (encodedField != null) {
-            consumer.accept(encodedField, info);
+          DexClass holder = definitions.definitionForHolder(field);
+          DexEncodedField definition = field.lookupOnClass(holder);
+          if (definition != null) {
+            consumer.accept(definition, info);
           } else {
             assert false;
           }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
index e392112..e9ab954 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
@@ -49,7 +49,7 @@
         genericSignature,
         mainMethod,
         innerClass,
-        lambda.getEnclosingMethod());
+        lambda.getEnclosingMethodAttribute());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
index ea39dce..495ab0b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
@@ -49,7 +49,7 @@
         genericSignature,
         mainMethod,
         innerClass,
-        lambda.getEnclosingMethod());
+        lambda.getEnclosingMethodAttribute());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
index 3a7e099..bcea380 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
@@ -44,8 +44,7 @@
     register(new ObjectMethodOptimizer(appView));
     register(new ObjectsMethodOptimizer(appView));
     register(new StringMethodOptimizer(appView));
-    if (appView.enableWholeProgramOptimizations()
-        && appView.options().enableDynamicTypeOptimization) {
+    if (appView.enableWholeProgramOptimizations()) {
       // Subtyping is required to prove the enum class is a subtype of java.lang.Enum.
       register(new EnumMethodOptimizer(appView));
     }
@@ -66,7 +65,7 @@
     for (LibraryMembers libraryMembers : appView.dexItemFactory().libraryMembersCollection) {
       libraryMembers.forEachFinalField(
           field -> {
-            DexEncodedField definition = appView.definitionFor(field);
+            DexEncodedField definition = field.lookupOnClass(appView.definitionForHolder(field));
             if (definition != null) {
               if (definition.isFinal()) {
                 finalLibraryFields.add(definition);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
index a677b43..92c78af 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -4,12 +4,15 @@
 
 package com.android.tools.r8.ir.optimize.library;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+
 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.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.analysis.value.ObjectState;
 import com.android.tools.r8.ir.optimize.info.LibraryOptimizationInfoInitializerFeedback;
@@ -57,7 +60,16 @@
     for (DexMethod method : dexItemFactory.libraryMethodsReturningNonNull) {
       DexEncodedMethod definition = lookupMethod(method);
       if (definition != null) {
-        feedback.methodNeverReturnsNull(definition);
+        TypeElement staticType =
+            TypeElement.fromDexType(method.proto.returnType, maybeNull(), appView);
+        feedback.methodReturnsObjectWithUpperBoundType(
+            definition,
+            appView,
+            definition
+                .getOptimizationInfo()
+                .getDynamicUpperBoundTypeOrElse(staticType)
+                .asReferenceType()
+                .asDefinitelyNotNull());
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 0e0e1c9..29250dc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -35,6 +35,7 @@
 import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
+import com.android.tools.r8.ir.optimize.AssumeInserter;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
@@ -390,7 +391,10 @@
   }
 
   private void insertAssumeInstructions(IRCode code, MethodProcessor methodProcessor) {
-    CodeRewriter.insertAssumeInstructions(code, converter.assumers, Timing.empty());
+    AssumeInserter assumeInserter = converter.assumeInserter;
+    if (assumeInserter != null) {
+      assumeInserter.insertAssumeInstructions(code, Timing.empty());
+    }
   }
 
   private BiConsumer<IRCode, MethodProcessor> collectOptimizationInfo(
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 7eddfcc..7af08f9 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -347,6 +347,7 @@
       while (instructionIterator.hasNext()) {
         Instruction instruction = instructionIterator.next();
         if (!instructionIterator.hasNext()) {
+          instruction.clearDebugValues();
           break;
         }
 
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
index 61bdbbc..5e9d63f 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfIfCmp;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLabel;
@@ -66,7 +67,7 @@
             CfLabel dest = new CfLabel();
             instructions.add(new CfLoad(ValueType.fromDexType(factory.intType), 0));
             instructions.add(new CfConstNumber(enumValueInfo.convertToInt(), ValueType.INT));
-            instructions.add(new CfIf(If.Type.EQ, ValueType.INT, dest));
+            instructions.add(new CfIfCmp(If.Type.NE, ValueType.INT, dest));
             instructions.add(new CfConstString(field.name));
             instructions.add(new CfReturn(ValueType.OBJECT));
             instructions.add(dest);
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 18de3c5..01c3daa 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -172,8 +172,8 @@
     writeAnnotations(writer::visitAnnotation, clazz.annotations().annotations);
     ImmutableMap<DexString, DexValue> defaults = getAnnotationDefaults(clazz.annotations());
 
-    if (clazz.getEnclosingMethod() != null) {
-      clazz.getEnclosingMethod().write(writer, namingLens);
+    if (clazz.getEnclosingMethodAttribute() != null) {
+      clazz.getEnclosingMethodAttribute().write(writer, namingLens);
     }
 
     if (clazz.getNestHostClassAttribute() != null) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java b/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java
index 84d6e82..7431fb9 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java
@@ -13,6 +13,7 @@
 import kotlinx.metadata.KmTypeVisitor;
 import kotlinx.metadata.KmValueParameterVisitor;
 import kotlinx.metadata.KmVariance;
+import kotlinx.metadata.KmVersionRequirementVisitor;
 
 /**
  * The reason for having these visitor providers is to make the separation of concern a bit easier
@@ -101,4 +102,10 @@
 
     KmTypeVisitor get(int flags, String typeFlexibilityId);
   }
+
+  @FunctionalInterface
+  public interface KmVersionRequirementVisitorProvider {
+
+    KmVersionRequirementVisitor get();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index 602165c..07f6b0b 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -42,7 +42,7 @@
   public static final class ClassClassifiers {
 
     public static final String arrayBinaryName = NAME + "/Array";
-    public static final String anyName = NAME + "/Any";
+    public static final String anyDescriptor = "L" + NAME + "/Any;";
   }
 
   // Mappings from JVM types to Kotlin types (of type DexType)
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
index a4bc5b0..90ed44a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
@@ -4,14 +4,12 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.referenceTypeFromBinaryName;
-
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -20,35 +18,33 @@
 import kotlinx.metadata.KmAnnotationArgument;
 
 // Holds information about a KmAnnotation
-public class KotlinAnnotationInfo {
+public class KotlinAnnotationInfo implements EnqueuerMetadataTraceable {
 
   private static final List<KotlinAnnotationInfo> EMPTY_ANNOTATIONS = ImmutableList.of();
 
-  private final DexType annotationType;
+  private final KotlinTypeReference annotationType;
   // TODO(b/155053894): Model KmAnnotationArgument.
   private final Map<String, KmAnnotationArgument<?>> arguments;
 
   private KotlinAnnotationInfo(
-      DexType annotationType, Map<String, KmAnnotationArgument<?>> arguments) {
+      KotlinTypeReference annotationType, Map<String, KmAnnotationArgument<?>> arguments) {
     this.annotationType = annotationType;
     this.arguments = arguments;
   }
 
-  private static KotlinAnnotationInfo create(
-      KmAnnotation annotation, DexDefinitionSupplier definitionSupplier) {
+  private static KotlinAnnotationInfo create(KmAnnotation annotation, DexItemFactory factory) {
     return new KotlinAnnotationInfo(
-        referenceTypeFromBinaryName(annotation.getClassName(), definitionSupplier),
+        KotlinTypeReference.fromBinaryName(annotation.getClassName(), factory),
         annotation.getArguments());
   }
 
-  static List<KotlinAnnotationInfo> create(
-      List<KmAnnotation> annotations, DexDefinitionSupplier definitionSupplier) {
+  static List<KotlinAnnotationInfo> create(List<KmAnnotation> annotations, DexItemFactory factory) {
     if (annotations.isEmpty()) {
       return EMPTY_ANNOTATIONS;
     }
     ImmutableList.Builder<KotlinAnnotationInfo> builder = ImmutableList.builder();
     for (KmAnnotation annotation : annotations) {
-      builder.add(create(annotation, definitionSupplier));
+      builder.add(create(annotation, factory));
     }
     return builder.build();
   }
@@ -57,12 +53,20 @@
       KmVisitorProviders.KmAnnotationVisitorProvider visitorProvider,
       AppView<AppInfoWithLiveness> appView,
       NamingLens namingLens) {
-    if (appView.appInfo().wasPruned(annotationType)) {
+    String renamedDescriptor =
+        annotationType.toRenamedDescriptorOrDefault(appView, namingLens, null);
+    if (renamedDescriptor == null) {
+      // The type has been pruned
       return;
     }
-    DexString descriptor = namingLens.lookupDescriptor(annotationType);
-    String classifier = DescriptorUtils.descriptorToKotlinClassifier(descriptor.toString());
+    String classifier = DescriptorUtils.descriptorToKotlinClassifier(renamedDescriptor);
     KmAnnotation annotation = new KmAnnotation(classifier, arguments);
     visitorProvider.get(annotation);
   }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    annotationType.trace(definitionSupplier);
+    // TODO(b/155053894): Trace annotation arguments.
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
index b507093..9987457 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -4,17 +4,17 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.referenceTypeFromBinaryName;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toJvmFieldSignature;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toJvmMethodSignature;
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -41,14 +41,15 @@
   private final KotlinDeclarationContainerInfo declarationContainerInfo;
   private final List<KotlinTypeParameterInfo> typeParameters;
   private final List<KotlinTypeInfo> superTypes;
-  private final List<DexType> sealedSubClasses;
-  private final List<DexType> nestedClasses;
+  private final List<KotlinTypeReference> sealedSubClasses;
+  private final List<KotlinTypeReference> nestedClasses;
   // TODO(b/154347404): Understand enum entries.
   private final List<String> enumEntries;
-  private final DexType anonymousObjectOrigin;
+  private final KotlinVersionRequirementInfo versionRequirements;
+  private final KotlinTypeReference anonymousObjectOrigin;
   private final String packageName;
 
-  public KotlinClassInfo(
+  private KotlinClassInfo(
       int flags,
       String name,
       String moduleName,
@@ -56,10 +57,11 @@
       List<KotlinTypeParameterInfo> typeParameters,
       List<KotlinConstructorInfo> constructorsWithNoBacking,
       List<KotlinTypeInfo> superTypes,
-      List<DexType> sealedSubClasses,
-      List<DexType> nestedClasses,
+      List<KotlinTypeReference> sealedSubClasses,
+      List<KotlinTypeReference> nestedClasses,
       List<String> enumEntries,
-      DexType anonymousObjectOrigin,
+      KotlinVersionRequirementInfo versionRequirements,
+      KotlinTypeReference anonymousObjectOrigin,
       String packageName) {
     this.flags = flags;
     this.name = name;
@@ -71,6 +73,7 @@
     this.sealedSubClasses = sealedSubClasses;
     this.nestedClasses = nestedClasses;
     this.enumEntries = enumEntries;
+    this.versionRequirements = versionRequirements;
     this.anonymousObjectOrigin = anonymousObjectOrigin;
     this.packageName = packageName;
   }
@@ -79,7 +82,7 @@
       KmClass kmClass,
       String packageName,
       DexClass hostClass,
-      DexDefinitionSupplier definitionSupplier,
+      DexItemFactory factory,
       Reporter reporter,
       Consumer<DexEncodedMethod> keepByteCode) {
     Map<String, DexEncodedField> fieldMap = new HashMap<>();
@@ -93,7 +96,7 @@
     ImmutableList.Builder<KotlinConstructorInfo> notBackedConstructors = ImmutableList.builder();
     for (KmConstructor kmConstructor : kmClass.getConstructors()) {
       KotlinConstructorInfo constructorInfo =
-          KotlinConstructorInfo.create(kmConstructor, definitionSupplier, reporter);
+          KotlinConstructorInfo.create(kmConstructor, factory, reporter);
       JvmMethodSignature signature = JvmExtensionsKt.getSignature(kmConstructor);
       if (signature != null) {
         DexEncodedMethod method = methodMap.get(signature.asString());
@@ -107,60 +110,61 @@
     }
     KotlinDeclarationContainerInfo container =
         KotlinDeclarationContainerInfo.create(
-            kmClass, methodMap, fieldMap, definitionSupplier, reporter, keepByteCode);
+            kmClass, methodMap, fieldMap, factory, reporter, keepByteCode);
     setCompanionObject(kmClass, hostClass, reporter);
     return new KotlinClassInfo(
         kmClass.getFlags(),
         kmClass.name,
         JvmExtensionsKt.getModuleName(kmClass),
         container,
-        KotlinTypeParameterInfo.create(kmClass.getTypeParameters(), definitionSupplier, reporter),
+        KotlinTypeParameterInfo.create(kmClass.getTypeParameters(), factory, reporter),
         notBackedConstructors.build(),
-        getSuperTypes(kmClass.getSupertypes(), definitionSupplier, reporter),
-        getSealedSubClasses(hostClass, kmClass.getSealedSubclasses(), definitionSupplier),
-        getNestedClasses(hostClass, kmClass.getNestedClasses(), definitionSupplier),
+        getSuperTypes(kmClass.getSupertypes(), factory, reporter),
+        getSealedSubClasses(kmClass.getSealedSubclasses(), factory),
+        getNestedClasses(hostClass, kmClass.getNestedClasses(), factory),
         kmClass.getEnumEntries(),
-        getAnonymousObjectOrigin(kmClass, definitionSupplier),
+        KotlinVersionRequirementInfo.create(kmClass.getVersionRequirements()),
+        getAnonymousObjectOrigin(kmClass, factory),
         packageName);
   }
 
-  private static DexType getAnonymousObjectOrigin(
-      KmClass kmClass, DexDefinitionSupplier definitionSupplier) {
+  private static KotlinTypeReference getAnonymousObjectOrigin(
+      KmClass kmClass, DexItemFactory factory) {
     String anonymousObjectOriginName = JvmExtensionsKt.getAnonymousObjectOriginName(kmClass);
     if (anonymousObjectOriginName != null) {
-      return referenceTypeFromBinaryName(anonymousObjectOriginName, definitionSupplier);
+      return KotlinTypeReference.fromBinaryName(anonymousObjectOriginName, factory);
     }
     return null;
   }
 
-  private static List<DexType> getNestedClasses(
-      DexClass clazz, List<String> nestedClasses, DexDefinitionSupplier definitionSupplier) {
-    ImmutableList.Builder<DexType> nestedTypes = ImmutableList.builder();
+  private static List<KotlinTypeReference> getNestedClasses(
+      DexClass clazz, List<String> nestedClasses, DexItemFactory factory) {
+    ImmutableList.Builder<KotlinTypeReference> nestedTypes = ImmutableList.builder();
     for (String nestedClass : nestedClasses) {
       String binaryName =
           clazz.type.toBinaryName() + DescriptorUtils.INNER_CLASS_SEPARATOR + nestedClass;
-      nestedTypes.add(referenceTypeFromBinaryName(binaryName, definitionSupplier));
+      nestedTypes.add(KotlinTypeReference.fromBinaryName(binaryName, factory));
     }
     return nestedTypes.build();
   }
 
-  private static List<DexType> getSealedSubClasses(
-      DexClass clazz, List<String> sealedSubclasses, DexDefinitionSupplier definitionSupplier) {
-    ImmutableList.Builder<DexType> sealedTypes = ImmutableList.builder();
+  private static List<KotlinTypeReference> getSealedSubClasses(
+      List<String> sealedSubclasses, DexItemFactory factory) {
+    ImmutableList.Builder<KotlinTypeReference> sealedTypes = ImmutableList.builder();
     for (String sealedSubClass : sealedSubclasses) {
       String binaryName =
           sealedSubClass.replace(
               DescriptorUtils.JAVA_PACKAGE_SEPARATOR, DescriptorUtils.INNER_CLASS_SEPARATOR);
-      sealedTypes.add(referenceTypeFromBinaryName(binaryName, definitionSupplier));
+      sealedTypes.add(KotlinTypeReference.fromBinaryName(binaryName, factory));
     }
     return sealedTypes.build();
   }
 
   private static List<KotlinTypeInfo> getSuperTypes(
-      List<KmType> superTypes, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
+      List<KmType> superTypes, DexItemFactory factory, Reporter reporter) {
     ImmutableList.Builder<KotlinTypeInfo> superTypeInfos = ImmutableList.builder();
     for (KmType superType : superTypes) {
-      superTypeInfos.add(KotlinTypeInfo.create(superType, definitionSupplier, reporter));
+      superTypeInfos.add(KotlinTypeInfo.create(superType, factory, reporter));
     }
     return superTypeInfos.build();
   }
@@ -205,7 +209,7 @@
     kmClass.setName(
         originalDescriptor.equals(rewrittenDescriptor)
             ? this.name
-            : KotlinMetadataUtils.kotlinNameFromDescriptor(rewrittenDescriptor));
+            : DescriptorUtils.getBinaryNameFromDescriptor(rewrittenDescriptor.toString()));
     // Find a companion object.
     for (DexEncodedField field : clazz.fields()) {
       if (field.getKotlinMemberInfo().isCompanion()) {
@@ -240,34 +244,35 @@
       superType.rewrite(kmClass::visitSupertype, appView, namingLens);
     }
     // Rewrite nested classes.
-    for (DexType nestedClass : nestedClasses) {
-      if (appView.appInfo().isNonProgramTypeOrLiveProgramType(nestedClass)) {
-        String descriptor =
-            KotlinMetadataUtils.kotlinNameFromDescriptor(namingLens.lookupDescriptor(nestedClass));
+    for (KotlinTypeReference nestedClass : nestedClasses) {
+      String nestedDescriptor = nestedClass.toRenamedBinaryNameOrDefault(appView, namingLens, null);
+      if (nestedDescriptor != null) {
         // If the class is a nested class, it should be on the form Foo.Bar$Baz, where Baz is the
         // name we should record.
-        int innerClassIndex = descriptor.lastIndexOf(DescriptorUtils.INNER_CLASS_SEPARATOR);
-        kmClass.visitNestedClass(descriptor.substring(innerClassIndex + 1));
+        int innerClassIndex = nestedDescriptor.lastIndexOf(DescriptorUtils.INNER_CLASS_SEPARATOR);
+        kmClass.visitNestedClass(nestedDescriptor.substring(innerClassIndex + 1));
       }
     }
     // Rewrite sealed sub classes.
-    for (DexType sealedSubClass : sealedSubClasses) {
-      if (appView.appInfo().isNonProgramTypeOrLiveProgramType(sealedSubClass)) {
-        String descriptor =
-            KotlinMetadataUtils.kotlinNameFromDescriptor(
-                namingLens.lookupDescriptor(sealedSubClass));
+    for (KotlinTypeReference sealedSubClass : sealedSubClasses) {
+      String sealedDescriptor =
+          sealedSubClass.toRenamedBinaryNameOrDefault(appView, namingLens, null);
+      if (sealedDescriptor != null) {
         kmClass.visitSealedSubclass(
-            descriptor.replace(
+            sealedDescriptor.replace(
                 DescriptorUtils.INNER_CLASS_SEPARATOR, DescriptorUtils.JAVA_PACKAGE_SEPARATOR));
       }
     }
     // TODO(b/154347404): Understand enum entries.
     kmClass.getEnumEntries().addAll(enumEntries);
-
+    versionRequirements.rewrite(kmClass::visitVersionRequirement);
     JvmExtensionsKt.setModuleName(kmClass, moduleName);
     if (anonymousObjectOrigin != null) {
-      JvmExtensionsKt.setAnonymousObjectOriginName(
-          kmClass, KotlinMetadataUtils.kotlinNameFromDescriptor(anonymousObjectOrigin.descriptor));
+      String renamedAnon =
+          anonymousObjectOrigin.toRenamedBinaryNameOrDefault(appView, namingLens, null);
+      if (renamedAnon != null) {
+        JvmExtensionsKt.setAnonymousObjectOriginName(kmClass, renamedAnon);
+      }
     }
 
     KotlinClassMetadata.Class.Writer writer = new KotlinClassMetadata.Class.Writer();
@@ -279,4 +284,18 @@
   public String getPackageName() {
     return packageName;
   }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    forEachApply(constructorsWithNoBacking, constructor -> constructor::trace, definitionSupplier);
+    declarationContainerInfo.trace(definitionSupplier);
+    forEachApply(typeParameters, param -> param::trace, definitionSupplier);
+    forEachApply(superTypes, type -> type::trace, definitionSupplier);
+    forEachApply(sealedSubClasses, sealed -> sealed::trace, definitionSupplier);
+    forEachApply(nestedClasses, nested -> nested::trace, definitionSupplier);
+    // TODO(b/154347404): trace enum entries.
+    if (anonymousObjectOrigin != null) {
+      anonymousObjectOrigin.trace(definitionSupplier);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
index f9662ef..1a24001 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
@@ -8,9 +8,14 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 
-public interface KotlinClassLevelInfo {
+public interface KotlinClassLevelInfo extends EnqueuerMetadataTraceable {
+
+  default boolean isNoKotlinInformation() {
+    return false;
+  }
 
   default boolean isClass() {
     return false;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index 5fe02b2..2171a00 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
@@ -31,22 +32,18 @@
   public static KotlinClassLevelInfo getKotlinInfo(
       Kotlin kotlin,
       DexClass clazz,
-      DexDefinitionSupplier definitionSupplier,
+      DexItemFactory factory,
       Reporter reporter,
       boolean onlyProcessLambda,
       Consumer<DexEncodedMethod> keepByteCode) {
-    DexAnnotation meta =
-        clazz
-            .annotations()
-            .getFirstMatching(definitionSupplier.dexItemFactory().kotlinMetadataType);
+    DexAnnotation meta = clazz.annotations().getFirstMatching(factory.kotlinMetadataType);
     if (meta != null) {
       try {
         KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, meta.annotation);
         if (onlyProcessLambda && kMetadata.getHeader().getKind() != KOTLIN_METADATA_KIND_LAMBDA) {
           return NO_KOTLIN_INFO;
         }
-        return createKotlinInfo(
-            kotlin, clazz, kMetadata, definitionSupplier, reporter, keepByteCode);
+        return createKotlinInfo(kotlin, clazz, kMetadata, factory, reporter, keepByteCode);
       } catch (ClassCastException | InconsistentKotlinMetadataException | MetadataError e) {
         reporter.info(
             new StringDiagnostic(
@@ -68,6 +65,14 @@
     return NO_KOTLIN_INFO;
   }
 
+  public static boolean hasKotlinClassMetadataAnnotation(
+      DexClass clazz, DexDefinitionSupplier definitionSupplier) {
+    return clazz
+            .annotations()
+            .getFirstMatching(definitionSupplier.dexItemFactory().kotlinMetadataType)
+        != null;
+  }
+
   public static KotlinClassMetadata toKotlinClassMetadata(
       Kotlin kotlin, DexEncodedAnnotation metadataAnnotation) {
     Map<DexString, DexAnnotationElement> elementMap = new IdentityHashMap<>();
@@ -103,7 +108,7 @@
       Kotlin kotlin,
       DexClass clazz,
       KotlinClassMetadata kMetadata,
-      DexDefinitionSupplier definitionSupplier,
+      DexItemFactory factory,
       Reporter reporter,
       Consumer<DexEncodedMethod> keepByteCode) {
     String packageName = kMetadata.getHeader().getPackageName();
@@ -112,7 +117,7 @@
           ((KotlinClassMetadata.Class) kMetadata).toKmClass(),
           packageName,
           clazz,
-          definitionSupplier,
+          factory,
           reporter,
           keepByteCode);
     } else if (kMetadata instanceof KotlinClassMetadata.FileFacade) {
@@ -121,20 +126,20 @@
           (KotlinClassMetadata.FileFacade) kMetadata,
           packageName,
           clazz,
-          definitionSupplier,
+          factory,
           reporter,
           keepByteCode);
     } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
       // multi-file class with the same @JvmName.
       return KotlinMultiFileClassFacadeInfo.create(
-          (KotlinClassMetadata.MultiFileClassFacade) kMetadata, packageName, definitionSupplier);
+          (KotlinClassMetadata.MultiFileClassFacade) kMetadata, packageName, factory);
     } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassPart) {
       // A single file, which is part of multi-file class.
       return KotlinMultiFileClassPartInfo.create(
           (KotlinClassMetadata.MultiFileClassPart) kMetadata,
           packageName,
           clazz,
-          definitionSupplier,
+          factory,
           reporter,
           keepByteCode);
     } else if (kMetadata instanceof KotlinClassMetadata.SyntheticClass) {
@@ -143,7 +148,7 @@
           packageName,
           clazz,
           kotlin,
-          definitionSupplier,
+          factory,
           reporter);
     } else {
       throw new MetadataError("unsupported 'k' value: " + kMetadata.getHeader().getKind());
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
index d6ff5b4..ac19e28 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
@@ -4,15 +4,13 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.referenceTypeFromDescriptor;
-
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.kotlin.Kotlin.ClassClassifiers;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.Reporter;
 import kotlinx.metadata.KmClassifier;
@@ -20,10 +18,10 @@
 import kotlinx.metadata.KmClassifier.TypeParameter;
 import kotlinx.metadata.KmTypeVisitor;
 
-public abstract class KotlinClassifierInfo {
+public abstract class KotlinClassifierInfo implements EnqueuerMetadataTraceable {
 
   public static KotlinClassifierInfo create(
-      KmClassifier classifier, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
+      KmClassifier classifier, DexItemFactory factory, Reporter reporter) {
     if (classifier instanceof KmClassifier.Class) {
       String originalTypeName = ((KmClassifier.Class) classifier).getName();
       // If this name starts with '.', it represents a local class or an anonymous object. This is
@@ -35,7 +33,7 @@
               isLocalOrAnonymous ? originalTypeName.substring(1) : originalTypeName);
       if (DescriptorUtils.isClassDescriptor(descriptor)) {
         return new KotlinClassClassifierInfo(
-            referenceTypeFromDescriptor(descriptor, definitionSupplier), isLocalOrAnonymous);
+            KotlinTypeReference.fromDescriptor(descriptor, factory), isLocalOrAnonymous);
       } else {
         return new KotlinUnknownClassClassifierInfo(originalTypeName);
       }
@@ -54,10 +52,10 @@
 
   public static class KotlinClassClassifierInfo extends KotlinClassifierInfo {
 
-    private final DexType type;
+    private final KotlinTypeReference type;
     private final boolean isLocalOrAnonymous;
 
-    private KotlinClassClassifierInfo(DexType type, boolean isLocalOrAnonymous) {
+    private KotlinClassClassifierInfo(KotlinTypeReference type, boolean isLocalOrAnonymous) {
       this.type = type;
       this.isLocalOrAnonymous = isLocalOrAnonymous;
     }
@@ -65,20 +63,21 @@
     @Override
     void rewrite(
         KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
-      if (appView.appInfo().wasPruned(type)) {
-        visitor.visitClass(ClassClassifiers.anyName);
-        return;
-      }
-      DexString descriptor = namingLens.lookupDescriptor(type);
+      String descriptor =
+          type.toRenamedDescriptorOrDefault(appView, namingLens, ClassClassifiers.anyDescriptor);
       // For local or anonymous classes, the classifier is prefixed with '.' and inner classes are
       // separated with '$'.
       if (isLocalOrAnonymous) {
-        visitor.visitClass(
-            "." + DescriptorUtils.getBinaryNameFromDescriptor(descriptor.toString()));
+        visitor.visitClass("." + DescriptorUtils.getBinaryNameFromDescriptor(descriptor));
       } else {
-        visitor.visitClass(DescriptorUtils.descriptorToKotlinClassifier(descriptor.toString()));
+        visitor.visitClass(DescriptorUtils.descriptorToKotlinClassifier(descriptor));
       }
     }
+
+    @Override
+    public void trace(DexDefinitionSupplier definitionSupplier) {
+      type.trace(definitionSupplier);
+    }
   }
 
   public static class KotlinTypeParameterClassifierInfo extends KotlinClassifierInfo {
@@ -94,6 +93,11 @@
         KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
       visitor.visitTypeParameter(typeId);
     }
+
+    @Override
+    public void trace(DexDefinitionSupplier definitionSupplier) {
+      // Intentionally empty.
+    }
   }
 
   public static class KotlinTypeAliasClassifierInfo extends KotlinClassifierInfo {
@@ -109,6 +113,11 @@
         KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
       visitor.visitTypeAlias(typeAlias);
     }
+
+    @Override
+    public void trace(DexDefinitionSupplier definitionSupplier) {
+      // Intentionally empty.
+    }
   }
 
   public static class KotlinUnknownClassClassifierInfo extends KotlinClassifierInfo {
@@ -123,6 +132,11 @@
         KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
       visitor.visitClass(classifier);
     }
+
+    @Override
+    public void trace(DexDefinitionSupplier definitionSupplier) {
+      // Intentionally empty.
+    }
   }
 
   public static class KotlinUnknownClassifierInfo extends KotlinClassifierInfo {
@@ -137,5 +151,10 @@
         KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
       visitor.visitTypeAlias(classifier);
     }
+
+    @Override
+    public void trace(DexDefinitionSupplier definitionSupplier) {
+      // Intentionally empty.
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java
index 2f97b4f..1e19f1a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.kotlin;
 
+import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.naming.NamingLens;
@@ -27,4 +28,9 @@
     String finalName = dexString.toString();
     visitor.visitCompanionObject(finalName);
   }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    // Do nothing.
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java
index 6f9f366..c28b4a7 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java
@@ -4,9 +4,12 @@
 
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Reporter;
@@ -21,27 +24,30 @@
   // Information from original KmValueParameter(s) if available.
   private final int flags;
   // Information about the value parameters.
-  private final List<KotlinValueParameterInfo> valueParameterInfos;
+  private final List<KotlinValueParameterInfo> valueParameters;
+  // Information about version requirements.
+  private final KotlinVersionRequirementInfo versionRequirements;
   // Information about the signature.
   private final KotlinJvmMethodSignatureInfo signature;
 
   private KotlinConstructorInfo(
       int flags,
-      List<KotlinValueParameterInfo> valueParameterInfos,
+      List<KotlinValueParameterInfo> valueParameters,
+      KotlinVersionRequirementInfo versionRequirements,
       KotlinJvmMethodSignatureInfo signature) {
     this.flags = flags;
-    this.valueParameterInfos = valueParameterInfos;
+    this.valueParameters = valueParameters;
+    this.versionRequirements = versionRequirements;
     this.signature = signature;
   }
 
   public static KotlinConstructorInfo create(
-      KmConstructor kmConstructor, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
+      KmConstructor kmConstructor, DexItemFactory factory, Reporter reporter) {
     return new KotlinConstructorInfo(
         kmConstructor.getFlags(),
-        KotlinValueParameterInfo.create(
-            kmConstructor.getValueParameters(), definitionSupplier, reporter),
-        KotlinJvmMethodSignatureInfo.create(
-            JvmExtensionsKt.getSignature(kmConstructor), definitionSupplier));
+        KotlinValueParameterInfo.create(kmConstructor.getValueParameters(), factory, reporter),
+        KotlinVersionRequirementInfo.create(kmConstructor.getVersionRequirements()),
+        KotlinJvmMethodSignatureInfo.create(JvmExtensionsKt.getSignature(kmConstructor), factory));
   }
 
   public void rewrite(
@@ -56,9 +62,10 @@
     if (signature != null) {
       JvmExtensionsKt.setSignature(kmConstructor, signature.rewrite(method, appView, namingLens));
     }
-    for (KotlinValueParameterInfo valueParameterInfo : valueParameterInfos) {
+    for (KotlinValueParameterInfo valueParameterInfo : valueParameters) {
       valueParameterInfo.rewrite(kmConstructor::visitValueParameter, appView, namingLens);
     }
+    versionRequirements.rewrite(kmConstructor::visitVersionRequirement);
     kmClass.getConstructors().add(kmConstructor);
   }
 
@@ -71,4 +78,12 @@
   public KotlinConstructorInfo asConstructor() {
     return this;
   }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    forEachApply(valueParameters, param -> param::trace, definitionSupplier);
+    if (signature != null) {
+      signature.trace(definitionSupplier);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
index cd349ab..a83f77a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
@@ -5,15 +5,18 @@
 package com.android.tools.r8.kotlin;
 
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.isValidMethodDescriptor;
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.kotlin.KotlinMetadataUtils.KmPropertyProcessor;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
 import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.ImmutableList;
 import java.util.IdentityHashMap;
@@ -29,7 +32,7 @@
 import kotlinx.metadata.jvm.JvmMethodSignature;
 
 // Holds information about KmDeclarationContainer
-public class KotlinDeclarationContainerInfo {
+public class KotlinDeclarationContainerInfo implements EnqueuerMetadataTraceable {
 
   private final List<KotlinTypeAliasInfo> typeAliases;
   // The functions in notBackedFunctions are KmFunctions where we could not find a representative.
@@ -51,7 +54,7 @@
       KmDeclarationContainer container,
       Map<String, DexEncodedMethod> methodSignatureMap,
       Map<String, DexEncodedField> fieldSignatureMap,
-      DexDefinitionSupplier definitionSupplier,
+      DexItemFactory factory,
       Reporter reporter,
       Consumer<DexEncodedMethod> keepByteCode) {
     ImmutableList.Builder<KotlinFunctionInfo> notBackedFunctions = ImmutableList.builder();
@@ -62,7 +65,7 @@
         continue;
       }
       KotlinFunctionInfo kotlinFunctionInfo =
-          KotlinFunctionInfo.create(kmFunction, definitionSupplier, reporter);
+          KotlinFunctionInfo.create(kmFunction, factory, reporter);
       DexEncodedMethod method = methodSignatureMap.get(signature.asString());
       if (method == null) {
         notBackedFunctions.add(kotlinFunctionInfo);
@@ -85,7 +88,7 @@
     ImmutableList.Builder<KotlinPropertyInfo> notBackedProperties = ImmutableList.builder();
     for (KmProperty kmProperty : container.getProperties()) {
       KotlinPropertyInfo kotlinPropertyInfo =
-          KotlinPropertyInfo.create(kmProperty, definitionSupplier, reporter);
+          KotlinPropertyInfo.create(kmProperty, factory, reporter);
       KmPropertyProcessor propertyProcessor = new KmPropertyProcessor(kmProperty);
       boolean hasBacking = false;
       if (propertyProcessor.fieldSignature() != null) {
@@ -119,7 +122,7 @@
       }
     }
     return new KotlinDeclarationContainerInfo(
-        getTypeAliases(container.getTypeAliases(), definitionSupplier, reporter),
+        getTypeAliases(container.getTypeAliases(), factory, reporter),
         notBackedFunctions.build(),
         notBackedProperties.build());
   }
@@ -139,10 +142,10 @@
   }
 
   private static List<KotlinTypeAliasInfo> getTypeAliases(
-      List<KmTypeAlias> aliases, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
+      List<KmTypeAlias> aliases, DexItemFactory factory, Reporter reporter) {
     ImmutableList.Builder<KotlinTypeAliasInfo> builder = ImmutableList.builder();
     for (KmTypeAlias alias : aliases) {
-      builder.add(KotlinTypeAliasInfo.create(alias, definitionSupplier, reporter));
+      builder.add(KotlinTypeAliasInfo.create(alias, factory, reporter));
     }
     return builder.build();
   }
@@ -208,6 +211,13 @@
     }
   }
 
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    forEachApply(typeAliases, alias -> alias::trace, definitionSupplier);
+    forEachApply(functionsWithNoBacking, function -> function::trace, definitionSupplier);
+    forEachApply(propertiesWithNoBacking, property -> property::trace, definitionSupplier);
+  }
+
   public static class KotlinPropertyGroup {
 
     private DexEncodedField backingField = null;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java
index 7df7afb..8d9d468 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java
@@ -4,7 +4,9 @@
 
 package com.android.tools.r8.kotlin;
 
-public interface KotlinFieldLevelInfo {
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
+
+public interface KotlinFieldLevelInfo extends EnqueuerMetadataTraceable {
 
   default boolean isCompanion() {
     return false;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
index c31df8c..94617e1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Reporter;
@@ -32,12 +33,12 @@
       FileFacade kmFileFacade,
       String packageName,
       DexClass clazz,
-      DexDefinitionSupplier definitionSupplier,
+      DexItemFactory factory,
       Reporter reporter,
       Consumer<DexEncodedMethod> keepByteCode) {
     return new KotlinFileFacadeInfo(
         KotlinPackageInfo.create(
-            kmFileFacade.toKmPackage(), clazz, definitionSupplier, reporter, keepByteCode),
+            kmFileFacade.toKmPackage(), clazz, factory, reporter, keepByteCode),
         packageName);
   }
 
@@ -65,4 +66,9 @@
   public String getPackageName() {
     return packageName;
   }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    packageInfo.trace(definitionSupplier);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java
index c8c8836..da2e960 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java
@@ -6,6 +6,7 @@
 
 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.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Reporter;
@@ -45,22 +46,20 @@
   }
 
   static KotlinFlexibleTypeUpperBoundInfo create(
-      KmFlexibleTypeUpperBound flexibleTypeUpperBound,
-      DexDefinitionSupplier definitionSupplier,
-      Reporter reporter) {
+      KmFlexibleTypeUpperBound flexibleTypeUpperBound, DexItemFactory factory, Reporter reporter) {
     if (flexibleTypeUpperBound == null) {
       return NO_FLEXIBLE_UPPER_BOUND;
     }
     KmType kmType = flexibleTypeUpperBound.getType();
     return new KotlinFlexibleTypeUpperBoundInfo(
         kmType.getFlags(),
-        KotlinClassifierInfo.create(kmType.classifier, definitionSupplier, reporter),
-        KotlinTypeInfo.create(kmType.getAbbreviatedType(), definitionSupplier, reporter),
-        KotlinTypeInfo.create(kmType.getOuterType(), definitionSupplier, reporter),
-        getArguments(kmType.getArguments(), definitionSupplier, reporter),
-        KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmType), definitionSupplier),
+        KotlinClassifierInfo.create(kmType.classifier, factory, reporter),
+        KotlinTypeInfo.create(kmType.getAbbreviatedType(), factory, reporter),
+        KotlinTypeInfo.create(kmType.getOuterType(), factory, reporter),
+        getArguments(kmType.getArguments(), factory, reporter),
+        KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmType), factory),
         KotlinFlexibleTypeUpperBoundInfo.create(
-            kmType.getFlexibleTypeUpperBound(), definitionSupplier, reporter),
+            kmType.getFlexibleTypeUpperBound(), factory, reporter),
         flexibleTypeUpperBound.getTypeFlexibilityId());
   }
 
@@ -74,4 +73,12 @@
     }
     super.rewrite(flags -> visitorProvider.get(flags, typeFlexibilityId), appView, namingLens);
   }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    if (this == NO_FLEXIBLE_UPPER_BOUND) {
+      return;
+    }
+    super.trace(definitionSupplier);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
index 58b07cb..d71d574 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
@@ -4,12 +4,12 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.referenceTypeFromBinaryName;
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Reporter;
@@ -36,7 +36,11 @@
   // Information about the signature
   private final KotlinJvmMethodSignatureInfo signature;
   // Information about the lambdaClassOrigin.
-  private final DexType lambdaClassOrigin;
+  private final KotlinTypeReference lambdaClassOrigin;
+  // Information about version requirements.
+  private final KotlinVersionRequirementInfo versionRequirements;
+  // A value describing if any of the parameters are crossinline.
+  private final boolean crossInlineParameter;
 
   private KotlinFunctionInfo(
       int flags,
@@ -46,7 +50,9 @@
       List<KotlinValueParameterInfo> valueParameters,
       List<KotlinTypeParameterInfo> typeParameters,
       KotlinJvmMethodSignatureInfo signature,
-      DexType lambdaClassOrigin) {
+      KotlinTypeReference lambdaClassOrigin,
+      KotlinVersionRequirementInfo versionRequirements,
+      boolean crossInlineParameter) {
     this.flags = flags;
     this.name = name;
     this.returnType = returnType;
@@ -55,29 +61,43 @@
     this.typeParameters = typeParameters;
     this.signature = signature;
     this.lambdaClassOrigin = lambdaClassOrigin;
+    this.versionRequirements = versionRequirements;
+    this.crossInlineParameter = crossInlineParameter;
+  }
+
+  public boolean hasCrossInlineParameter() {
+    return crossInlineParameter;
   }
 
   static KotlinFunctionInfo create(
-      KmFunction kmFunction, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
+      KmFunction kmFunction, DexItemFactory factory, Reporter reporter) {
+    boolean isCrossInline = false;
+    List<KotlinValueParameterInfo> valueParameters =
+        KotlinValueParameterInfo.create(kmFunction.getValueParameters(), factory, reporter);
+    for (KotlinValueParameterInfo valueParameter : valueParameters) {
+      if (valueParameter.isCrossInline()) {
+        isCrossInline = true;
+        break;
+      }
+    }
     return new KotlinFunctionInfo(
         kmFunction.getFlags(),
         kmFunction.getName(),
-        KotlinTypeInfo.create(kmFunction.getReturnType(), definitionSupplier, reporter),
-        KotlinTypeInfo.create(kmFunction.getReceiverParameterType(), definitionSupplier, reporter),
-        KotlinValueParameterInfo.create(
-            kmFunction.getValueParameters(), definitionSupplier, reporter),
-        KotlinTypeParameterInfo.create(
-            kmFunction.getTypeParameters(), definitionSupplier, reporter),
-        KotlinJvmMethodSignatureInfo.create(
-            JvmExtensionsKt.getSignature(kmFunction), definitionSupplier),
-        getlambdaClassOrigin(kmFunction, definitionSupplier));
+        KotlinTypeInfo.create(kmFunction.getReturnType(), factory, reporter),
+        KotlinTypeInfo.create(kmFunction.getReceiverParameterType(), factory, reporter),
+        valueParameters,
+        KotlinTypeParameterInfo.create(kmFunction.getTypeParameters(), factory, reporter),
+        KotlinJvmMethodSignatureInfo.create(JvmExtensionsKt.getSignature(kmFunction), factory),
+        getlambdaClassOrigin(kmFunction, factory),
+        KotlinVersionRequirementInfo.create(kmFunction.getVersionRequirements()),
+        isCrossInline);
   }
 
-  private static DexType getlambdaClassOrigin(
-      KmFunction kmFunction, DexDefinitionSupplier definitionSupplier) {
+  private static KotlinTypeReference getlambdaClassOrigin(
+      KmFunction kmFunction, DexItemFactory factory) {
     String lambdaClassOriginName = JvmExtensionsKt.getLambdaClassOriginName(kmFunction);
     if (lambdaClassOriginName != null) {
-      return referenceTypeFromBinaryName(lambdaClassOriginName, definitionSupplier);
+      return KotlinTypeReference.fromBinaryName(lambdaClassOriginName, factory);
     }
     return null;
   }
@@ -108,14 +128,18 @@
     if (receiverParameterType != null) {
       receiverParameterType.rewrite(kmFunction::visitReceiverParameterType, appView, namingLens);
     }
+    versionRequirements.rewrite(kmFunction::visitVersionRequirement);
     JvmFunctionExtensionVisitor extensionVisitor =
         (JvmFunctionExtensionVisitor) kmFunction.visitExtensions(JvmFunctionExtensionVisitor.TYPE);
     if (signature != null && extensionVisitor != null) {
       extensionVisitor.visit(signature.rewrite(method, appView, namingLens));
     }
     if (lambdaClassOrigin != null && extensionVisitor != null) {
-      extensionVisitor.visitLambdaClassOriginName(
-          KotlinMetadataUtils.kotlinNameFromDescriptor(lambdaClassOrigin.descriptor));
+      String lambdaClassOriginName =
+          lambdaClassOrigin.toRenamedBinaryNameOrDefault(appView, namingLens, null);
+      if (lambdaClassOriginName != null) {
+        extensionVisitor.visitLambdaClassOriginName(lambdaClassOriginName);
+      }
     }
   }
 
@@ -132,4 +156,24 @@
   public boolean isExtensionFunction() {
     return receiverParameterType != null;
   }
+
+  public KotlinJvmMethodSignatureInfo getSignature() {
+    return signature;
+  }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    forEachApply(valueParameters, param -> param::trace, definitionSupplier);
+    returnType.trace(definitionSupplier);
+    if (receiverParameterType != null) {
+      receiverParameterType.trace(definitionSupplier);
+    }
+    forEachApply(typeParameters, param -> param::trace, definitionSupplier);
+    if (signature != null) {
+      signature.trace(definitionSupplier);
+    }
+    if (lambdaClassOrigin != null) {
+      lambdaClassOrigin.trace(definitionSupplier);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java
index 69c0442..6daf3d7 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java
@@ -4,39 +4,37 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.referenceTypeFromDescriptor;
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toRenamedDescriptorOrDefault;
-
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
 import kotlinx.metadata.jvm.JvmFieldSignature;
 
 /**
  * The JvmSignature for a method or property does not always correspond to the actual signature, see
  * b/154201250. We therefore need to model the signature as well.
  */
-public class KotlinJvmFieldSignatureInfo {
+public class KotlinJvmFieldSignatureInfo implements EnqueuerMetadataTraceable {
 
-  private final DexType type;
+  private final KotlinTypeReference type;
   private final String name;
 
-  private KotlinJvmFieldSignatureInfo(String name, DexType type) {
+  private KotlinJvmFieldSignatureInfo(String name, KotlinTypeReference type) {
     this.name = name;
     this.type = type;
   }
 
   public static KotlinJvmFieldSignatureInfo create(
-      JvmFieldSignature fieldSignature, DexDefinitionSupplier definitionSupplier) {
+      JvmFieldSignature fieldSignature, DexItemFactory factory) {
     if (fieldSignature == null) {
       return null;
     }
     return new KotlinJvmFieldSignatureInfo(
         fieldSignature.getName(),
-        referenceTypeFromDescriptor(fieldSignature.getDesc(), definitionSupplier));
+        KotlinTypeReference.fromDescriptor(fieldSignature.getDesc(), factory));
   }
 
   public JvmFieldSignature rewrite(
@@ -51,6 +49,11 @@
     }
     String defValue = appView.dexItemFactory().objectType.toDescriptorString();
     return new JvmFieldSignature(
-        finalName, toRenamedDescriptorOrDefault(type, appView, namingLens, defValue));
+        finalName, type.toRenamedDescriptorOrDefault(appView, namingLens, defValue));
+  }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    type.trace(definitionSupplier);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java
index d9d5c9d..f4674a1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java
@@ -4,15 +4,15 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.referenceTypeFromDescriptor;
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toRenamedDescriptorOrDefault;
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -22,36 +22,51 @@
  * The JvmSignature for a method or property does not always correspond to the actual signature, see
  * b/154201250. We therefore need to model the signature as well.
  */
-public class KotlinJvmMethodSignatureInfo {
+public class KotlinJvmMethodSignatureInfo implements EnqueuerMetadataTraceable {
 
-  private static final List<DexType> EMPTY_PARAMETERS_LIST = ImmutableList.of();
+  private static final List<KotlinTypeReference> EMPTY_PARAMETERS_LIST = ImmutableList.of();
 
   private final String name;
-  private final DexType returnType;
-  private final List<DexType> parameters;
+  private final KotlinTypeReference returnType;
+  private final List<KotlinTypeReference> parameters;
+  private final String invalidDescriptor;
 
-  private KotlinJvmMethodSignatureInfo(String name, DexType returnType, List<DexType> parameters) {
+  private KotlinJvmMethodSignatureInfo(
+      String name, KotlinTypeReference returnType, List<KotlinTypeReference> parameters) {
     this.name = name;
     this.returnType = returnType;
     this.parameters = parameters;
+    this.invalidDescriptor = null;
+  }
+
+  private KotlinJvmMethodSignatureInfo(String name, String invalidDescriptor) {
+    this.name = name;
+    this.invalidDescriptor = invalidDescriptor;
+    this.parameters = EMPTY_PARAMETERS_LIST;
+    this.returnType = null;
   }
 
   public static KotlinJvmMethodSignatureInfo create(
-      JvmMethodSignature methodSignature, DexDefinitionSupplier definitionSupplier) {
+      JvmMethodSignature methodSignature, DexItemFactory factory) {
     if (methodSignature == null) {
       return null;
     }
     String kotlinDescriptor = methodSignature.getDesc();
+    if (!KotlinMetadataUtils.isValidMethodDescriptor(kotlinDescriptor)) {
+      // If the method descriptor is invalid, keep it as invalid.
+      return new KotlinJvmMethodSignatureInfo(methodSignature.getName(), kotlinDescriptor);
+    }
     String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(kotlinDescriptor);
-    DexType returnType = referenceTypeFromDescriptor(returnTypeDescriptor, definitionSupplier);
+    KotlinTypeReference returnType =
+        KotlinTypeReference.fromDescriptor(returnTypeDescriptor, factory);
     String[] descriptors = DescriptorUtils.getArgumentTypeDescriptors(kotlinDescriptor);
     if (descriptors.length == 0) {
       return new KotlinJvmMethodSignatureInfo(
           methodSignature.getName(), returnType, EMPTY_PARAMETERS_LIST);
     }
-    ImmutableList.Builder<DexType> parameters = ImmutableList.builder();
+    ImmutableList.Builder<KotlinTypeReference> parameters = ImmutableList.builder();
     for (String descriptor : descriptors) {
-      parameters.add(referenceTypeFromDescriptor(descriptor, definitionSupplier));
+      parameters.add(KotlinTypeReference.fromDescriptor(descriptor, factory));
     }
     return new KotlinJvmMethodSignatureInfo(
         methodSignature.getName(), returnType, parameters.build());
@@ -59,6 +74,10 @@
 
   public JvmMethodSignature rewrite(
       DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+    if (invalidDescriptor != null) {
+      return new JvmMethodSignature(name, invalidDescriptor);
+    }
+    assert returnType != null;
     String finalName = name;
     if (method != null) {
       String methodName = method.method.name.toString();
@@ -70,11 +89,38 @@
     StringBuilder descBuilder = new StringBuilder();
     descBuilder.append("(");
     String defValue = appView.dexItemFactory().objectType.toDescriptorString();
-    for (DexType parameter : parameters) {
-      descBuilder.append(toRenamedDescriptorOrDefault(parameter, appView, namingLens, defValue));
+    for (KotlinTypeReference parameter : parameters) {
+      descBuilder.append(parameter.toRenamedDescriptorOrDefault(appView, namingLens, defValue));
     }
     descBuilder.append(")");
-    descBuilder.append(toRenamedDescriptorOrDefault(returnType, appView, namingLens, defValue));
+    descBuilder.append(returnType.toRenamedDescriptorOrDefault(appView, namingLens, defValue));
     return new JvmMethodSignature(finalName, descBuilder.toString());
   }
+
+  @Override
+  public String toString() {
+    if (invalidDescriptor != null) {
+      return name + "(" + invalidDescriptor + ")";
+    }
+    assert returnType != null;
+    StringBuilder descBuilder = new StringBuilder();
+    descBuilder.append(name);
+    descBuilder.append("(");
+    for (KotlinTypeReference parameter : parameters) {
+      descBuilder.append(parameter.toString());
+    }
+    descBuilder.append(")");
+    descBuilder.append(returnType.toString());
+    return descBuilder.toString();
+  }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    if (invalidDescriptor != null) {
+      return;
+    }
+    assert returnType != null;
+    returnType.trace(definitionSupplier);
+    forEachApply(parameters, param -> param::trace, definitionSupplier);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
index d25f105..459599b 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
@@ -10,48 +10,44 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
 import com.android.tools.r8.utils.Reporter;
 import kotlinx.metadata.KmLambda;
-import kotlinx.metadata.KmLambdaVisitor;
 import kotlinx.metadata.jvm.JvmExtensionsKt;
 import kotlinx.metadata.jvm.JvmMethodSignature;
 
 // Holds information about a KmLambda
-public class KotlinLambdaInfo {
+public class KotlinLambdaInfo implements EnqueuerMetadataTraceable {
 
   private final KotlinFunctionInfo function;
+  private final boolean hasBacking;
 
-  private KotlinLambdaInfo(KotlinFunctionInfo function) {
+  private KotlinLambdaInfo(KotlinFunctionInfo function, boolean hasBacking) {
     this.function = function;
+    this.hasBacking = hasBacking;
   }
 
   static KotlinLambdaInfo create(
-      DexClass clazz,
-      KmLambda lambda,
-      DexDefinitionSupplier definitionSupplier,
-      Reporter reporter) {
+      DexClass clazz, KmLambda lambda, DexItemFactory factory, Reporter reporter) {
     if (lambda == null) {
       assert false;
       return null;
     }
+    KotlinFunctionInfo kotlinFunctionInfo =
+        KotlinFunctionInfo.create(lambda.function, factory, reporter);
     JvmMethodSignature signature = JvmExtensionsKt.getSignature(lambda.function);
-    if (signature == null) {
-      assert false;
-      return null;
-    }
-    for (DexEncodedMethod method : clazz.methods()) {
-      if (toJvmMethodSignature(method.method).asString().equals(signature.asString())) {
-        KotlinFunctionInfo kotlinFunctionInfo =
-            KotlinFunctionInfo.create(lambda.function, definitionSupplier, reporter);
-        method.setKotlinMemberInfo(kotlinFunctionInfo);
-        return new KotlinLambdaInfo(kotlinFunctionInfo);
+    if (signature != null) {
+      for (DexEncodedMethod method : clazz.methods()) {
+        if (toJvmMethodSignature(method.method).asString().equals(signature.asString())) {
+          method.setKotlinMemberInfo(kotlinFunctionInfo);
+          return new KotlinLambdaInfo(kotlinFunctionInfo, true);
+        }
       }
     }
-    // TODO(b/155536535): Resolve this assert for NestTreeShakeJarVerificationTest.
-    // assert false;
-    return null;
+    return new KotlinLambdaInfo(kotlinFunctionInfo, false);
   }
 
   boolean rewrite(
@@ -59,13 +55,31 @@
       DexClass clazz,
       AppView<AppInfoWithLiveness> appView,
       NamingLens namingLens) {
+    if (!hasBacking) {
+      function.rewrite(visitorProvider.get()::visitFunction, null, appView, namingLens);
+      return true;
+    }
+    DexEncodedMethod backing = null;
     for (DexEncodedMethod method : clazz.methods()) {
       if (method.getKotlinMemberInfo() == function) {
-        KmLambdaVisitor kmLambdaVisitor = visitorProvider.get();
-        function.rewrite(kmLambdaVisitor::visitFunction, method, appView, namingLens);
-        return true;
+        backing = method;
+        break;
       }
     }
-    return false;
+    if (backing == null) {
+      appView
+          .options()
+          .reporter
+          .info(
+              KotlinMetadataDiagnostic.lambdaBackingNotFound(clazz.type, function.getSignature()));
+      return false;
+    }
+    function.rewrite(visitorProvider.get()::visitFunction, backing, appView, namingLens);
+    return true;
+  }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    function.trace(definitionSupplier);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java
index 83947d7..3415e13 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java
@@ -73,4 +73,18 @@
             + StringUtils.LINE_SEPARATOR
             + StringUtils.stacktraceAsString(t));
   }
+
+  static KotlinMetadataDiagnostic lambdaBackingNotFound(
+      DexType type, KotlinJvmMethodSignatureInfo signatureInfo) {
+    return new KotlinMetadataDiagnostic(
+        Origin.unknown(),
+        Position.UNKNOWN,
+        "The lambda function "
+            + signatureInfo.toString()
+            + " could no longer be found in "
+            + type.toSourceString()
+            + " . The method is most likely pruned and would require a specific keep rule to keep"
+            + " alive. As a result, the metadata information regarding the lambda structure has"
+            + " been discarded.");
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
index 28928ef..ba814e9 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -4,11 +4,20 @@
 
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.kotlin.KotlinClassMetadataReader.hasKotlinClassMetadataAnnotation;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.google.common.collect.Sets;
@@ -18,12 +27,11 @@
 
   private final AppView<?> appView;
   private final DexDefinitionSupplier definitionSupplier;
-  private final Set<DexMethod> keepByteCodeFunctions = Sets.newIdentityHashSet();
 
   public KotlinMetadataEnqueuerExtension(
-      AppView<?> appView, DexDefinitionSupplier definitionSupplier) {
+      AppView<?> appView, DexDefinitionSupplier definitionSupplier, Set<DexType> prunedTypes) {
     this.appView = appView;
-    this.definitionSupplier = definitionSupplier;
+    this.definitionSupplier = new KotlinMetadataDefinitionSupplier(definitionSupplier, prunedTypes);
   }
 
   @Override
@@ -31,23 +39,111 @@
     DexType kotlinMetadataType = appView.dexItemFactory().kotlinMetadataType;
     DexClass kotlinMetadataClass =
         appView.appInfo().definitionForWithoutExistenceAssert(kotlinMetadataType);
-    // We will process kotlin.Metadata even if the type is not present in the program, as long as
-    // the annotation will be in the output
+    // In the first round of tree shaking build up all metadata such that it can be traced later.
     boolean keepMetadata =
-        enqueuer.isPinned(kotlinMetadataType)
-            || enqueuer.isMissing(kotlinMetadataType)
-            || (kotlinMetadataClass != null && kotlinMetadataClass.isNotProgramClass());
+        kotlinMetadataClass == null
+            || kotlinMetadataClass.isNotProgramClass()
+            || enqueuer.isPinned(kotlinMetadataType);
+    if (enqueuer.getMode().isInitialTreeShaking()) {
+      Set<DexMethod> keepByteCodeFunctions = Sets.newIdentityHashSet();
+      Set<DexProgramClass> localOrAnonymousClasses = Sets.newIdentityHashSet();
+      enqueuer.forAllLiveClasses(
+          clazz -> {
+            boolean onlyProcessLambdas = !keepMetadata || !enqueuer.isPinned(clazz.type);
+            assert clazz.getKotlinInfo().isNoKotlinInformation();
+            clazz.setKotlinInfo(
+                KotlinClassMetadataReader.getKotlinInfo(
+                    appView.dexItemFactory().kotlin,
+                    clazz,
+                    definitionSupplier.dexItemFactory(),
+                    appView.options().reporter,
+                    onlyProcessLambdas,
+                    method -> keepByteCodeFunctions.add(method.method)));
+            if (clazz.getEnclosingMethodAttribute() != null
+                && clazz.getEnclosingMethodAttribute().getEnclosingMethod() != null) {
+              localOrAnonymousClasses.add(clazz);
+            }
+          });
+      appView.setCfByteCodePassThrough(keepByteCodeFunctions);
+      for (DexProgramClass localOrAnonymousClass : localOrAnonymousClasses) {
+        EnclosingMethodAttribute enclosingAttribute =
+            localOrAnonymousClass.getEnclosingMethodAttribute();
+        DexClass holder =
+            definitionSupplier.definitionForHolder(enclosingAttribute.getEnclosingMethod());
+        if (holder == null) {
+          continue;
+        }
+        DexEncodedMethod method = holder.lookupMethod(enclosingAttribute.getEnclosingMethod());
+        // If we cannot lookup the method, the conservative choice is keep the byte code.
+        if (method == null
+            || (method.getKotlinMemberInfo().isFunction()
+                && method.getKotlinMemberInfo().asFunction().hasCrossInlineParameter())) {
+          localOrAnonymousClass.forEachProgramMethod(
+              m -> keepByteCodeFunctions.add(m.getReference()));
+        }
+      }
+    } else {
+      assert verifyKotlinMetadataModeledForAllClasses(enqueuer, keepMetadata);
+    }
+    // Trace through the modeled kotlin metadata.
     enqueuer.forAllLiveClasses(
         clazz -> {
-          clazz.setKotlinInfo(
-              KotlinClassMetadataReader.getKotlinInfo(
-                  appView.dexItemFactory().kotlin,
-                  clazz,
-                  definitionSupplier,
-                  appView.options().reporter,
-                  !keepMetadata || !enqueuer.isPinned(clazz.type),
-                  method -> keepByteCodeFunctions.add(method.method)));
+          clazz.getKotlinInfo().trace(definitionSupplier);
+          forEachApply(
+              clazz.methods(), method -> method.getKotlinMemberInfo()::trace, definitionSupplier);
+          forEachApply(
+              clazz.fields(), field -> field.getKotlinMemberInfo()::trace, definitionSupplier);
         });
-    appView.setCfByteCodePassThrough(keepByteCodeFunctions);
+  }
+
+  private boolean verifyKotlinMetadataModeledForAllClasses(
+      Enqueuer enqueuer, boolean keepMetadata) {
+    enqueuer.forAllLiveClasses(
+        clazz -> {
+          // Trace through class and member definitions
+          assert !hasKotlinClassMetadataAnnotation(clazz, definitionSupplier)
+              || !keepMetadata
+              || !enqueuer.isPinned(clazz.type)
+              || clazz.getKotlinInfo() != NO_KOTLIN_INFO;
+        });
+    return true;
+  }
+
+  public static class KotlinMetadataDefinitionSupplier implements DexDefinitionSupplier {
+
+    private final DexDefinitionSupplier baseSupplier;
+    private final Set<DexType> prunedTypes;
+
+    private KotlinMetadataDefinitionSupplier(
+        DexDefinitionSupplier baseSupplier, Set<DexType> prunedTypes) {
+      this.baseSupplier = baseSupplier;
+      this.prunedTypes = prunedTypes;
+    }
+
+    @Override
+    public DexEncodedMethod definitionFor(DexMethod method) {
+      throw new Unreachable("Should not be called");
+    }
+
+    @Override
+    public DexClass definitionFor(DexType type) {
+      // TODO(b/157700128) Metadata cannot at this point keep anything alive. Therefore, if a type
+      //  has been pruned it may still be referenced, so we do an early check here to ensure it will
+      //  not end up as. Ideally, those types should be removed by a pass on the modeled data.
+      if (prunedTypes != null && prunedTypes.contains(type)) {
+        return null;
+      }
+      return baseSupplier.definitionFor(type);
+    }
+
+    @Override
+    public DexProgramClass definitionForProgramType(DexType type) {
+      throw new Unreachable("Should not be called");
+    }
+
+    @Override
+    public DexItemFactory dexItemFactory() {
+      return baseSupplier.dexItemFactory();
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
index 1f21451..1ee78a5 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -59,6 +58,16 @@
     public String getPackageName() {
       throw new Unreachable("Should never be called");
     }
+
+    @Override
+    public boolean isNoKotlinInformation() {
+      return true;
+    }
+
+    @Override
+    public void trace(DexDefinitionSupplier definitionSupplier) {
+      // No information needed to trace.
+    }
   }
 
   static JvmFieldSignature toJvmFieldSignature(DexField field) {
@@ -137,41 +146,6 @@
     }
   }
 
-  static String toRenamedDescriptorOrDefault(
-      DexType type,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens namingLens,
-      String defaultValue) {
-    if (appView.appInfo().wasPruned(type)) {
-      return defaultValue;
-    }
-    DexString descriptor = namingLens.lookupDescriptor(type);
-    if (descriptor != null) {
-      return descriptor.toString();
-    }
-    return defaultValue;
-  }
-
-  static String kotlinNameFromDescriptor(DexString descriptor) {
-    return DescriptorUtils.getBinaryNameFromDescriptor(descriptor.toString());
-  }
-
-  static DexType referenceTypeFromBinaryName(
-      String binaryName, DexDefinitionSupplier definitionSupplier) {
-    return referenceTypeFromDescriptor(
-        DescriptorUtils.getDescriptorFromClassBinaryName(binaryName), definitionSupplier);
-  }
-
-  static DexType referenceTypeFromDescriptor(
-      String descriptor, DexDefinitionSupplier definitionSupplier) {
-    DexType type = definitionSupplier.dexItemFactory().createType(descriptor);
-    // Lookup the definition, ignoring the result. This populates the sets in the Enqueuer.
-    if (type.isClassType()) {
-      definitionSupplier.definitionFor(type);
-    }
-    return type;
-  }
-
   public static boolean mayProcessKotlinMetadata(AppView<?> appView) {
     // This can run before we have determined the pinned items, because we may need to load the
     // stack-map table on input. This is therefore a conservative guess on kotlin.Metadata is kept.
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
index fa21f81..bc124c6 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
@@ -33,6 +33,7 @@
 import kotlinx.metadata.KmTypeParameter;
 import kotlinx.metadata.KmTypeProjection;
 import kotlinx.metadata.KmValueParameter;
+import kotlinx.metadata.KmVersionRequirement;
 import kotlinx.metadata.jvm.JvmExtensionsKt;
 import kotlinx.metadata.jvm.JvmFieldSignature;
 import kotlinx.metadata.jvm.JvmMethodSignature;
@@ -351,6 +352,7 @@
                 appendKmProperty(nextNextIndent, sb, kmProperty);
               });
         });
+    appendKmVersionRequirement(indent, sb, kmClass.getVersionRequirements());
     appendKeyValue(
         indent,
         "constructors",
@@ -389,6 +391,7 @@
           JvmMethodSignature signature = JvmExtensionsKt.getSignature(constructor);
           appendKeyValue(
               newIndent, "signature", sb, signature != null ? signature.asString() : "null");
+          appendKmVersionRequirement(newIndent, sb, constructor.getVersionRequirements());
         });
   }
 
@@ -420,6 +423,7 @@
               "valueParameters",
               sb,
               nextIndent -> appendValueParameters(nextIndent, sb, function.getValueParameters()));
+          appendKmVersionRequirement(newIndent, sb, function.getVersionRequirements());
           JvmMethodSignature signature = JvmExtensionsKt.getSignature(function);
           appendKeyValue(
               newIndent, "signature", sb, signature != null ? signature.asString() : "null");
@@ -461,6 +465,7 @@
               "setterParameter",
               sb,
               nextIndent -> appendValueParameter(nextIndent, sb, kmProperty.getSetterParameter()));
+          appendKmVersionRequirement(newIndent, sb, kmProperty.getVersionRequirements());
           appendKeyValue(newIndent, "jvmFlags", sb, JvmExtensionsKt.getJvmFlags(kmProperty) + "");
           JvmFieldSignature fieldSignature = JvmExtensionsKt.getFieldSignature(kmProperty);
           appendKeyValue(
@@ -730,6 +735,7 @@
               nextIndent -> {
                 appendKmType(nextIndent, sb, kmTypeAlias.underlyingType);
               });
+          appendKmVersionRequirement(newIndent, sb, kmTypeAlias.getVersionRequirements());
         });
   }
 
@@ -753,4 +759,44 @@
               });
         });
   }
+
+  private static void appendKmVersionRequirement(
+      String indent, StringBuilder sb, List<KmVersionRequirement> kmVersionRequirements) {
+    appendKeyValue(
+        indent,
+        "versionRequirements",
+        sb,
+        newIndent -> {
+          appendKmList(
+              newIndent,
+              "KmVersionRequirement",
+              sb,
+              kmVersionRequirements,
+              (nextIndent, kmVersionRequirement) -> {
+                appendKmSection(
+                    nextIndent,
+                    "KmVersionRequirement",
+                    sb,
+                    nextNextIndent -> {
+                      appendKeyValue(nextNextIndent, "kind", sb, kmVersionRequirement.kind.name());
+                      appendKeyValue(
+                          nextNextIndent, "level", sb, kmVersionRequirement.level.name());
+                      appendKeyValue(
+                          nextNextIndent,
+                          "errorCode",
+                          sb,
+                          kmVersionRequirement.getErrorCode() == null
+                              ? "null"
+                              : kmVersionRequirement.getErrorCode().toString());
+                      appendKeyValue(
+                          nextNextIndent, "message", sb, kmVersionRequirement.getMessage());
+                      appendKeyValue(
+                          nextNextIndent,
+                          "version",
+                          sb,
+                          kmVersionRequirement.getVersion().toString());
+                    });
+              });
+        });
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java
index 604374e..a02cb0a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java
@@ -4,7 +4,9 @@
 
 package com.android.tools.r8.kotlin;
 
-public interface KotlinMethodLevelInfo {
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
+
+public interface KotlinMethodLevelInfo extends EnqueuerMetadataTraceable {
 
   default boolean isConstructor() {
     return false;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
index 3baea9c..1ccd35f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
@@ -4,14 +4,14 @@
 
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.List;
@@ -22,23 +22,20 @@
 // Holds information about Metadata.MultiFileClassFace
 public class KotlinMultiFileClassFacadeInfo implements KotlinClassLevelInfo {
 
-  private final List<DexType> partClassNames;
+  private final List<KotlinTypeReference> partClassNames;
   private final String packageName;
 
-  private KotlinMultiFileClassFacadeInfo(List<DexType> partClassNames, String packageName) {
+  private KotlinMultiFileClassFacadeInfo(
+      List<KotlinTypeReference> partClassNames, String packageName) {
     this.partClassNames = partClassNames;
     this.packageName = packageName;
   }
 
   static KotlinMultiFileClassFacadeInfo create(
-      MultiFileClassFacade kmMultiFileClassFacade,
-      String packageName,
-      DexDefinitionSupplier definitionSupplier) {
-    ImmutableList.Builder<DexType> builder = ImmutableList.builder();
+      MultiFileClassFacade kmMultiFileClassFacade, String packageName, DexItemFactory factory) {
+    ImmutableList.Builder<KotlinTypeReference> builder = ImmutableList.builder();
     for (String partClassName : kmMultiFileClassFacade.getPartClassNames()) {
-      String descriptor = DescriptorUtils.getDescriptorFromClassBinaryName(partClassName);
-      DexType type = definitionSupplier.dexItemFactory().createType(descriptor);
-      builder.add(type);
+      builder.add(KotlinTypeReference.fromBinaryName(partClassName, factory));
     }
     return new KotlinMultiFileClassFacadeInfo(builder.build(), packageName);
   }
@@ -59,11 +56,10 @@
     KotlinClassMetadata.MultiFileClassFacade.Writer writer =
         new KotlinClassMetadata.MultiFileClassFacade.Writer();
     List<String> partClassNameStrings = new ArrayList<>(partClassNames.size());
-    for (DexType partClassName : partClassNames) {
-      if (appView.appInfo().isNonProgramTypeOrLiveProgramType(partClassName)) {
-        DexString descriptor = namingLens.lookupDescriptor(partClassName);
-        String classifier = DescriptorUtils.descriptorToKotlinClassifier(descriptor.toString());
-        partClassNameStrings.add(classifier);
+    for (KotlinTypeReference partClassName : partClassNames) {
+      String binaryName = partClassName.toRenamedBinaryNameOrDefault(appView, namingLens, null);
+      if (binaryName != null) {
+        partClassNameStrings.add(binaryName);
       }
     }
     return writer.write(partClassNameStrings).getHeader();
@@ -73,4 +69,9 @@
   public String getPackageName() {
     return packageName;
   }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    forEachApply(partClassNames, type -> type::trace, definitionSupplier);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
index d4ec800..9470082 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Reporter;
@@ -20,6 +21,7 @@
 // Holds information about Metadata.MultiFileClassPartInfo
 public class KotlinMultiFileClassPartInfo implements KotlinClassLevelInfo {
 
+  // TODO(b/157630779): Maybe model facadeClassName.
   private final String facadeClassName;
   private final KotlinPackageInfo packageInfo;
   private final String packageName;
@@ -35,13 +37,12 @@
       MultiFileClassPart classPart,
       String packageName,
       DexClass clazz,
-      DexDefinitionSupplier definitionSupplier,
+      DexItemFactory factory,
       Reporter reporter,
       Consumer<DexEncodedMethod> keepByteCode) {
     return new KotlinMultiFileClassPartInfo(
         classPart.getFacadeClassName(),
-        KotlinPackageInfo.create(
-            classPart.toKmPackage(), clazz, definitionSupplier, reporter, keepByteCode),
+        KotlinPackageInfo.create(classPart.toKmPackage(), clazz, factory, reporter, keepByteCode),
         packageName);
   }
 
@@ -70,4 +71,9 @@
   public String getPackageName() {
     return packageName;
   }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    packageInfo.trace(definitionSupplier);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
index d98e109..8f0268a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
@@ -12,8 +12,10 @@
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
 import com.android.tools.r8.utils.Reporter;
 import java.util.HashMap;
 import java.util.Map;
@@ -22,7 +24,7 @@
 import kotlinx.metadata.jvm.JvmExtensionsKt;
 
 // Holds information about a KmPackage object.
-public class KotlinPackageInfo {
+public class KotlinPackageInfo implements EnqueuerMetadataTraceable {
 
   private final String moduleName;
   private final KotlinDeclarationContainerInfo containerInfo;
@@ -35,7 +37,7 @@
   public static KotlinPackageInfo create(
       KmPackage kmPackage,
       DexClass clazz,
-      DexDefinitionSupplier definitionSupplier,
+      DexItemFactory factory,
       Reporter reporter,
       Consumer<DexEncodedMethod> keepByteCode) {
     Map<String, DexEncodedField> fieldMap = new HashMap<>();
@@ -49,7 +51,7 @@
     return new KotlinPackageInfo(
         JvmExtensionsKt.getModuleName(kmPackage),
         KotlinDeclarationContainerInfo.create(
-            kmPackage, methodMap, fieldMap, definitionSupplier, reporter, keepByteCode));
+            kmPackage, methodMap, fieldMap, factory, reporter, keepByteCode));
   }
 
   public void rewrite(
@@ -66,4 +68,9 @@
         namingLens);
     JvmExtensionsKt.setModuleName(kmPackage, moduleName);
   }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    containerInfo.trace(definitionSupplier);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
index 802428a..4d14c67 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
@@ -4,10 +4,13 @@
 
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Reporter;
@@ -41,6 +44,8 @@
 
   private final List<KotlinTypeParameterInfo> typeParameters;
 
+  private final KotlinVersionRequirementInfo versionRequirements;
+
   private final int jvmFlags;
 
   private final KotlinJvmFieldSignatureInfo fieldSignature;
@@ -60,6 +65,7 @@
       KotlinTypeInfo receiverParameterType,
       KotlinValueParameterInfo setterParameter,
       List<KotlinTypeParameterInfo> typeParameters,
+      KotlinVersionRequirementInfo versionRequirements,
       int jvmFlags,
       KotlinJvmFieldSignatureInfo fieldSignature,
       KotlinJvmMethodSignatureInfo getterSignature,
@@ -73,6 +79,7 @@
     this.receiverParameterType = receiverParameterType;
     this.setterParameter = setterParameter;
     this.typeParameters = typeParameters;
+    this.versionRequirements = versionRequirements;
     this.jvmFlags = jvmFlags;
     this.fieldSignature = fieldSignature;
     this.getterSignature = getterSignature;
@@ -81,27 +88,25 @@
   }
 
   public static KotlinPropertyInfo create(
-      KmProperty kmProperty, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
+      KmProperty kmProperty, DexItemFactory factory, Reporter reporter) {
     return new KotlinPropertyInfo(
         kmProperty.getFlags(),
         kmProperty.getGetterFlags(),
         kmProperty.getSetterFlags(),
         kmProperty.getName(),
-        KotlinTypeInfo.create(kmProperty.getReturnType(), definitionSupplier, reporter),
-        KotlinTypeInfo.create(kmProperty.getReceiverParameterType(), definitionSupplier, reporter),
-        KotlinValueParameterInfo.create(
-            kmProperty.getSetterParameter(), definitionSupplier, reporter),
-        KotlinTypeParameterInfo.create(
-            kmProperty.getTypeParameters(), definitionSupplier, reporter),
+        KotlinTypeInfo.create(kmProperty.getReturnType(), factory, reporter),
+        KotlinTypeInfo.create(kmProperty.getReceiverParameterType(), factory, reporter),
+        KotlinValueParameterInfo.create(kmProperty.getSetterParameter(), factory, reporter),
+        KotlinTypeParameterInfo.create(kmProperty.getTypeParameters(), factory, reporter),
+        KotlinVersionRequirementInfo.create(kmProperty.getVersionRequirements()),
         JvmExtensionsKt.getJvmFlags(kmProperty),
-        KotlinJvmFieldSignatureInfo.create(
-            JvmExtensionsKt.getFieldSignature(kmProperty), definitionSupplier),
+        KotlinJvmFieldSignatureInfo.create(JvmExtensionsKt.getFieldSignature(kmProperty), factory),
         KotlinJvmMethodSignatureInfo.create(
-            JvmExtensionsKt.getGetterSignature(kmProperty), definitionSupplier),
+            JvmExtensionsKt.getGetterSignature(kmProperty), factory),
         KotlinJvmMethodSignatureInfo.create(
-            JvmExtensionsKt.getSetterSignature(kmProperty), definitionSupplier),
+            JvmExtensionsKt.getSetterSignature(kmProperty), factory),
         KotlinJvmMethodSignatureInfo.create(
-            JvmExtensionsKt.getSyntheticMethodForAnnotations(kmProperty), definitionSupplier));
+            JvmExtensionsKt.getSyntheticMethodForAnnotations(kmProperty), factory));
   }
 
   @Override
@@ -146,6 +151,7 @@
     for (KotlinTypeParameterInfo typeParameter : typeParameters) {
       typeParameter.rewrite(kmProperty::visitTypeParameter, appView, namingLens);
     }
+    versionRequirements.rewrite(kmProperty::visitVersionRequirement);
     JvmPropertyExtensionVisitor extensionVisitor =
         (JvmPropertyExtensionVisitor) kmProperty.visitExtensions(JvmPropertyExtensionVisitor.TYPE);
     if (extensionVisitor != null) {
@@ -160,4 +166,30 @@
       }
     }
   }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    if (returnType != null) {
+      returnType.trace(definitionSupplier);
+    }
+    if (receiverParameterType != null) {
+      receiverParameterType.trace(definitionSupplier);
+    }
+    if (setterParameter != null) {
+      setterParameter.trace(definitionSupplier);
+    }
+    forEachApply(typeParameters, param -> param::trace, definitionSupplier);
+    if (fieldSignature != null) {
+      fieldSignature.trace(definitionSupplier);
+    }
+    if (getterSignature != null) {
+      getterSignature.trace(definitionSupplier);
+    }
+    if (setterSignature != null) {
+      setterSignature.trace(definitionSupplier);
+    }
+    if (syntheticMethodForAnnotations != null) {
+      syntheticMethodForAnnotations.trace(definitionSupplier);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
index 38933b5..5af2a4c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Reporter;
@@ -41,7 +42,7 @@
       String packageName,
       DexClass clazz,
       Kotlin kotlin,
-      DexDefinitionSupplier definitionSupplier,
+      DexItemFactory factory,
       Reporter reporter) {
     KmLambda lambda = null;
     if (syntheticClass.isLambda()) {
@@ -49,9 +50,7 @@
       assert lambda != null;
     }
     return new KotlinSyntheticClassInfo(
-        lambda != null
-            ? KotlinLambdaInfo.create(clazz, lambda, definitionSupplier, reporter)
-            : null,
+        lambda != null ? KotlinLambdaInfo.create(clazz, lambda, factory, reporter) : null,
         getFlavour(syntheticClass, clazz, kotlin),
         packageName);
   }
@@ -92,6 +91,13 @@
   }
 
   @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    if (lambda != null) {
+      lambda.trace(definitionSupplier);
+    }
+  }
+
+  @Override
   public String getPackageName() {
     return packageName;
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java
index b4b5354..bb75b7f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java
@@ -4,17 +4,21 @@
 
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
 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.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
 import com.android.tools.r8.utils.Reporter;
 import java.util.List;
 import kotlinx.metadata.KmTypeAlias;
 import kotlinx.metadata.KmTypeAliasVisitor;
 
 // Holds information about KmTypeAlias
-public class KotlinTypeAliasInfo {
+public class KotlinTypeAliasInfo implements EnqueuerMetadataTraceable {
 
   private final int flags;
   private final String name;
@@ -22,6 +26,7 @@
   private final KotlinTypeInfo expandedType;
   private final List<KotlinTypeParameterInfo> typeParameters;
   private final List<KotlinAnnotationInfo> annotations;
+  private final KotlinVersionRequirementInfo versionRequirements;
 
   private KotlinTypeAliasInfo(
       int flags,
@@ -29,7 +34,8 @@
       KotlinTypeInfo underlyingType,
       KotlinTypeInfo expandedType,
       List<KotlinTypeParameterInfo> typeParameters,
-      List<KotlinAnnotationInfo> annotations) {
+      List<KotlinAnnotationInfo> annotations,
+      KotlinVersionRequirementInfo versionRequirements) {
     this.flags = flags;
     this.name = name;
     assert underlyingType != null;
@@ -38,17 +44,19 @@
     this.expandedType = expandedType;
     this.typeParameters = typeParameters;
     this.annotations = annotations;
+    this.versionRequirements = versionRequirements;
   }
 
   public static KotlinTypeAliasInfo create(
-      KmTypeAlias alias, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
+      KmTypeAlias alias, DexItemFactory factory, Reporter reporter) {
     return new KotlinTypeAliasInfo(
         alias.getFlags(),
         alias.getName(),
-        KotlinTypeInfo.create(alias.underlyingType, definitionSupplier, reporter),
-        KotlinTypeInfo.create(alias.expandedType, definitionSupplier, reporter),
-        KotlinTypeParameterInfo.create(alias.getTypeParameters(), definitionSupplier, reporter),
-        KotlinAnnotationInfo.create(alias.getAnnotations(), definitionSupplier));
+        KotlinTypeInfo.create(alias.underlyingType, factory, reporter),
+        KotlinTypeInfo.create(alias.expandedType, factory, reporter),
+        KotlinTypeParameterInfo.create(alias.getTypeParameters(), factory, reporter),
+        KotlinAnnotationInfo.create(alias.getAnnotations(), factory),
+        KotlinVersionRequirementInfo.create(alias.getVersionRequirements()));
   }
 
   void rewrite(
@@ -64,5 +72,14 @@
     for (KotlinAnnotationInfo annotation : annotations) {
       annotation.rewrite(kmTypeAliasVisitor::visitAnnotation, appView, namingLens);
     }
+    versionRequirements.rewrite(kmTypeAliasVisitor::visitVersionRequirement);
+  }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    underlyingType.trace(definitionSupplier);
+    expandedType.trace(definitionSupplier);
+    forEachApply(typeParameters, typeParam -> typeParam::trace, definitionSupplier);
+    forEachApply(annotations, annotation -> annotation::trace, definitionSupplier);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
index 8601667..f21db98 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
@@ -4,10 +4,14 @@
 
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
 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.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
 import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -18,7 +22,7 @@
 import kotlinx.metadata.jvm.JvmTypeExtensionVisitor;
 
 // Provides access to Kotlin information about a kotlin type.
-public class KotlinTypeInfo {
+public class KotlinTypeInfo implements EnqueuerMetadataTraceable {
 
   private static final List<KotlinTypeProjectionInfo> EMPTY_ARGUMENTS = ImmutableList.of();
 
@@ -47,32 +51,29 @@
     this.flexibleTypeUpperBoundInfo = flexibleTypeUpperBoundInfo;
   }
 
-  static KotlinTypeInfo create(
-      KmType kmType, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
+  static KotlinTypeInfo create(KmType kmType, DexItemFactory factory, Reporter reporter) {
     if (kmType == null) {
       return null;
     }
     return new KotlinTypeInfo(
         kmType.getFlags(),
-        KotlinClassifierInfo.create(kmType.classifier, definitionSupplier, reporter),
-        KotlinTypeInfo.create(kmType.getAbbreviatedType(), definitionSupplier, reporter),
-        KotlinTypeInfo.create(kmType.getOuterType(), definitionSupplier, reporter),
-        getArguments(kmType.getArguments(), definitionSupplier, reporter),
-        KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmType), definitionSupplier),
+        KotlinClassifierInfo.create(kmType.classifier, factory, reporter),
+        KotlinTypeInfo.create(kmType.getAbbreviatedType(), factory, reporter),
+        KotlinTypeInfo.create(kmType.getOuterType(), factory, reporter),
+        getArguments(kmType.getArguments(), factory, reporter),
+        KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmType), factory),
         KotlinFlexibleTypeUpperBoundInfo.create(
-            kmType.getFlexibleTypeUpperBound(), definitionSupplier, reporter));
+            kmType.getFlexibleTypeUpperBound(), factory, reporter));
   }
 
   static List<KotlinTypeProjectionInfo> getArguments(
-      List<KmTypeProjection> projections,
-      DexDefinitionSupplier definitionSupplier,
-      Reporter reporter) {
+      List<KmTypeProjection> projections, DexItemFactory factory, Reporter reporter) {
     if (projections.isEmpty()) {
       return EMPTY_ARGUMENTS;
     }
     ImmutableList.Builder<KotlinTypeProjectionInfo> arguments = ImmutableList.builder();
     for (KmTypeProjection projection : projections) {
-      arguments.add(KotlinTypeProjectionInfo.create(projection, definitionSupplier, reporter));
+      arguments.add(KotlinTypeProjectionInfo.create(projection, factory, reporter));
     }
     return arguments.build();
   }
@@ -107,4 +108,18 @@
       }
     }
   }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    classifier.trace(definitionSupplier);
+    if (abbreviatedType != null) {
+      abbreviatedType.trace(definitionSupplier);
+    }
+    if (outerType != null) {
+      outerType.trace(definitionSupplier);
+    }
+    forEachApply(arguments, argument -> argument::trace, definitionSupplier);
+    flexibleTypeUpperBoundInfo.trace(definitionSupplier);
+    forEachApply(annotations, annotation -> annotation::trace, definitionSupplier);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java
index fd11c11..53c2eb2 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java
@@ -4,10 +4,14 @@
 
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
 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.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
 import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -19,7 +23,7 @@
 import kotlinx.metadata.jvm.JvmTypeParameterExtensionVisitor;
 
 // Provides access to Kotlin information about a type-parameter.
-public class KotlinTypeParameterInfo {
+public class KotlinTypeParameterInfo implements EnqueuerMetadataTraceable {
 
   private static final List<KotlinTypeParameterInfo> EMPTY_TYPE_PARAMETERS = ImmutableList.of();
   private static final List<KotlinTypeInfo> EMPTY_UPPER_BOUNDS = ImmutableList.of();
@@ -47,41 +51,36 @@
   }
 
   private static KotlinTypeParameterInfo create(
-      KmTypeParameter kmTypeParameter,
-      DexDefinitionSupplier definitionSupplier,
-      Reporter reporter) {
+      KmTypeParameter kmTypeParameter, DexItemFactory factory, Reporter reporter) {
     return new KotlinTypeParameterInfo(
         kmTypeParameter.getFlags(),
         kmTypeParameter.getId(),
         kmTypeParameter.getName(),
         kmTypeParameter.getVariance(),
-        getUpperBounds(kmTypeParameter.getUpperBounds(), definitionSupplier, reporter),
-        KotlinAnnotationInfo.create(
-            JvmExtensionsKt.getAnnotations(kmTypeParameter), definitionSupplier));
+        getUpperBounds(kmTypeParameter.getUpperBounds(), factory, reporter),
+        KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmTypeParameter), factory));
   }
 
   static List<KotlinTypeParameterInfo> create(
-      List<KmTypeParameter> kmTypeParameters,
-      DexDefinitionSupplier definitionSupplier,
-      Reporter reporter) {
+      List<KmTypeParameter> kmTypeParameters, DexItemFactory factory, Reporter reporter) {
     if (kmTypeParameters.isEmpty()) {
       return EMPTY_TYPE_PARAMETERS;
     }
     ImmutableList.Builder<KotlinTypeParameterInfo> builder = ImmutableList.builder();
     for (KmTypeParameter kmTypeParameter : kmTypeParameters) {
-      builder.add(create(kmTypeParameter, definitionSupplier, reporter));
+      builder.add(create(kmTypeParameter, factory, reporter));
     }
     return builder.build();
   }
 
   private static List<KotlinTypeInfo> getUpperBounds(
-      List<KmType> upperBounds, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
+      List<KmType> upperBounds, DexItemFactory factory, Reporter reporter) {
     if (upperBounds.isEmpty()) {
       return EMPTY_UPPER_BOUNDS;
     }
     ImmutableList.Builder<KotlinTypeInfo> builder = ImmutableList.builder();
     for (KmType upperBound : upperBounds) {
-      builder.add(KotlinTypeInfo.create(upperBound, definitionSupplier, reporter));
+      builder.add(KotlinTypeInfo.create(upperBound, factory, reporter));
     }
     return builder.build();
   }
@@ -104,4 +103,10 @@
       annotation.rewrite(extensionVisitor::visitAnnotation, appView, namingLens);
     }
   }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    forEachApply(originalUpperBounds, upperBound -> upperBound::trace, definitionSupplier);
+    forEachApply(annotations, annotation -> annotation::trace, definitionSupplier);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
index 68da5db..9ab3de0 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
@@ -6,14 +6,16 @@
 
 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.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
 import com.android.tools.r8.utils.Reporter;
 import kotlinx.metadata.KmTypeProjection;
 import kotlinx.metadata.KmVariance;
 
 // Provides access to Kotlin information about the type projection of a type (arguments).
-public class KotlinTypeProjectionInfo {
+public class KotlinTypeProjectionInfo implements EnqueuerMetadataTraceable {
 
   final KmVariance variance;
   final KotlinTypeInfo typeInfo;
@@ -24,12 +26,10 @@
   }
 
   static KotlinTypeProjectionInfo create(
-      KmTypeProjection kmTypeProjection,
-      DexDefinitionSupplier definitionSupplier,
-      Reporter reporter) {
+      KmTypeProjection kmTypeProjection, DexItemFactory factory, Reporter reporter) {
     return new KotlinTypeProjectionInfo(
         kmTypeProjection.getVariance(),
-        KotlinTypeInfo.create(kmTypeProjection.getType(), definitionSupplier, reporter));
+        KotlinTypeInfo.create(kmTypeProjection.getType(), factory, reporter));
   }
 
   private boolean isStarProjection() {
@@ -47,4 +47,11 @@
       typeInfo.rewrite(flags -> visitorProvider.get(flags, variance), appView, namingLens);
     }
   }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    if (typeInfo != null) {
+      typeInfo.trace(definitionSupplier);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java
new file mode 100644
index 0000000..dd2be72
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+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.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
+import com.android.tools.r8.utils.DescriptorUtils;
+
+/**
+ * To account for invalid type references in kotlin metadata, the class KotlinTypeReference will
+ * either hold a DexType reference, or a String, with the original name reference, which is not a
+ * valid jvm descriptor/name. The values will be disjoint.
+ */
+class KotlinTypeReference implements EnqueuerMetadataTraceable {
+
+  private final DexType known;
+  private final String unknown;
+
+  private KotlinTypeReference(DexType known) {
+    this.known = known;
+    this.unknown = null;
+    assert known != null;
+  }
+
+  private KotlinTypeReference(String unknown) {
+    this.known = null;
+    this.unknown = unknown;
+    assert unknown != null;
+  }
+
+  static KotlinTypeReference fromBinaryName(String binaryName, DexItemFactory factory) {
+    if (DescriptorUtils.isValidBinaryName(binaryName)) {
+      return fromDescriptor(
+          DescriptorUtils.getDescriptorFromClassBinaryName(binaryName), factory, binaryName);
+    }
+    return new KotlinTypeReference(binaryName);
+  }
+
+  static KotlinTypeReference fromDescriptor(String descriptor, DexItemFactory factory) {
+    return fromDescriptor(descriptor, factory, descriptor);
+  }
+
+  static KotlinTypeReference fromDescriptor(
+      String descriptor, DexItemFactory factory, String unknownValue) {
+    if (DescriptorUtils.isDescriptor(descriptor)) {
+      DexType type = factory.createType(descriptor);
+      return new KotlinTypeReference(type);
+    }
+    return new KotlinTypeReference(unknownValue);
+  }
+
+  String toRenamedDescriptorOrDefault(
+      AppView<AppInfoWithLiveness> appView, NamingLens namingLens, String defaultValue) {
+    if (unknown != null) {
+      return unknown;
+    }
+    assert known != null;
+    if (!known.isClassType()) {
+      return known.descriptor.toString();
+    }
+    if (!appView.appInfo().isNonProgramTypeOrLiveProgramType(known)) {
+      return defaultValue;
+    }
+    DexString descriptor = namingLens.lookupDescriptor(known);
+    if (descriptor != null) {
+      return descriptor.toString();
+    }
+    return defaultValue;
+  }
+
+  String toRenamedBinaryNameOrDefault(
+      AppView<AppInfoWithLiveness> appView, NamingLens namingLens, String defaultValue) {
+    if (unknown != null) {
+      // Unknown values are always on the input form, so we can just return it.
+      return unknown;
+    }
+    String descriptor = toRenamedDescriptorOrDefault(appView, namingLens, defaultValue);
+    if (descriptor == null) {
+      return null;
+    }
+    if (descriptor.equals(defaultValue)) {
+      // We assume that the default value passed in is already a binary name.
+      return descriptor;
+    }
+    return DescriptorUtils.getBinaryNameFromDescriptor(descriptor);
+  }
+
+  @Override
+  public String toString() {
+    return known != null ? known.descriptor.toString() : unknown;
+  }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    if (known != null && known.isClassType()) {
+      // Lookup the definition, ignoring the result. This populates the sets in the Enqueuer.
+      definitionSupplier.definitionFor(known);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
index ee2f99b..83dd5d2 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
@@ -6,17 +6,20 @@
 
 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.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
 import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import kotlinx.metadata.KmType;
 import kotlinx.metadata.KmValueParameter;
 import kotlinx.metadata.KmValueParameterVisitor;
+import kotlinx.metadata.internal.metadata.deserialization.Flags;
 
 // Provides access to Kotlin information about value parameter.
-class KotlinValueParameterInfo {
+class KotlinValueParameterInfo implements EnqueuerMetadataTraceable {
   private static final List<KotlinValueParameterInfo> EMPTY_VALUE_PARAMETERS = ImmutableList.of();
   // Original parameter name.
   final String name;
@@ -35,10 +38,12 @@
     this.varargElementType = varargElementType;
   }
 
+  boolean isCrossInline() {
+    return Flags.IS_CROSSINLINE.get(flags);
+  }
+
   static KotlinValueParameterInfo create(
-      KmValueParameter kmValueParameter,
-      DexDefinitionSupplier definitionSupplier,
-      Reporter reporter) {
+      KmValueParameter kmValueParameter, DexItemFactory factory, Reporter reporter) {
     if (kmValueParameter == null) {
       return null;
     }
@@ -46,21 +51,18 @@
     return new KotlinValueParameterInfo(
         kmValueParameter.getFlags(),
         kmValueParameter.getName(),
-        KotlinTypeInfo.create(kmType, definitionSupplier, reporter),
-        KotlinTypeInfo.create(
-            kmValueParameter.getVarargElementType(), definitionSupplier, reporter));
+        KotlinTypeInfo.create(kmType, factory, reporter),
+        KotlinTypeInfo.create(kmValueParameter.getVarargElementType(), factory, reporter));
   }
 
   static List<KotlinValueParameterInfo> create(
-      List<KmValueParameter> parameters,
-      DexDefinitionSupplier definitionSupplier,
-      Reporter reporter) {
+      List<KmValueParameter> parameters, DexItemFactory factory, Reporter reporter) {
     if (parameters.isEmpty()) {
       return EMPTY_VALUE_PARAMETERS;
     }
     ImmutableList.Builder<KotlinValueParameterInfo> builder = ImmutableList.builder();
     for (KmValueParameter parameter : parameters) {
-      builder.add(create(parameter, definitionSupplier, reporter));
+      builder.add(create(parameter, factory, reporter));
     }
     return builder.build();
   }
@@ -76,4 +78,12 @@
           kmValueParameterVisitor::visitVarargElementType, appView, namingLens);
     }
   }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    type.trace(definitionSupplier);
+    if (varargElementType != null) {
+      varargElementType.trace(definitionSupplier);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinVersionRequirementInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinVersionRequirementInfo.java
new file mode 100644
index 0000000..9530f4c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinVersionRequirementInfo.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import kotlinx.metadata.KmVersion;
+import kotlinx.metadata.KmVersionRequirement;
+import kotlinx.metadata.KmVersionRequirementLevel;
+import kotlinx.metadata.KmVersionRequirementVersionKind;
+import kotlinx.metadata.KmVersionRequirementVisitor;
+
+class KotlinVersionRequirementInfo {
+
+  private static final KotlinVersionRequirementInfo NO_VERSION_REQUIREMENTS =
+      new KotlinVersionRequirementInfo(ImmutableList.of());
+
+  private final List<KotlinVersionRequirementPoint> versionRequirements;
+
+  private KotlinVersionRequirementInfo(List<KotlinVersionRequirementPoint> versionRequirements) {
+    this.versionRequirements = versionRequirements;
+  }
+
+  static KotlinVersionRequirementInfo create(List<KmVersionRequirement> kmVersionRequirements) {
+    if (kmVersionRequirements.isEmpty()) {
+      return NO_VERSION_REQUIREMENTS;
+    }
+    ImmutableList.Builder<KotlinVersionRequirementPoint> builder = ImmutableList.builder();
+    for (KmVersionRequirement kmVersionRequirement : kmVersionRequirements) {
+      builder.add(KotlinVersionRequirementPoint.create(kmVersionRequirement));
+    }
+    return new KotlinVersionRequirementInfo(builder.build());
+  }
+
+  public void rewrite(KmVisitorProviders.KmVersionRequirementVisitorProvider visitorProvider) {
+    if (this == NO_VERSION_REQUIREMENTS) {
+      return;
+    }
+    for (KotlinVersionRequirementPoint versionRequirement : versionRequirements) {
+      versionRequirement.rewrite(visitorProvider.get());
+    }
+  }
+
+  private static class KotlinVersionRequirementPoint {
+
+    private final Integer errorCode;
+    private final KmVersionRequirementVersionKind kind;
+    private final KmVersionRequirementLevel level;
+    private final String message;
+    private final KmVersion version;
+
+    private KotlinVersionRequirementPoint(
+        KmVersionRequirementVersionKind kind,
+        KmVersionRequirementLevel level,
+        Integer errorCode,
+        String message,
+        KmVersion version) {
+      this.errorCode = errorCode;
+      this.kind = kind;
+      this.level = level;
+      this.message = message;
+      this.version = version;
+    }
+
+    private static KotlinVersionRequirementPoint create(KmVersionRequirement kmVersionRequirement) {
+      return new KotlinVersionRequirementPoint(
+          kmVersionRequirement.kind,
+          kmVersionRequirement.level,
+          kmVersionRequirement.getErrorCode(),
+          kmVersionRequirement.getMessage(),
+          kmVersionRequirement.version);
+    }
+
+    private void rewrite(KmVersionRequirementVisitor visitor) {
+      visitor.visit(kind, level, errorCode, message);
+      visitor.visitVersion(version.getMajor(), version.getMinor(), version.getPatch());
+      visitor.visitEnd();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index 8cffa05..b014c4f 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -3,15 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -178,9 +182,7 @@
                   getOrCreateReservedFieldNamingState(clazz.type);
               FieldNamingState state = parentState.createChildState(reservedNames);
               if (clazz.isProgramClass()) {
-                for (DexEncodedField field : clazz.fields()) {
-                  renameField(field, state);
-                }
+                clazz.asProgramClass().forEachProgramField(field -> renameField(field, state));
               }
 
               assert !states.containsKey(clazz.type);
@@ -212,11 +214,14 @@
     for (DexClass clazz : partition) {
       if (clazz.isProgramClass()) {
         assert clazz.isInterface();
-        for (DexEncodedField field : clazz.fields()) {
-          DexString newName = renameField(field, state);
-          namesToBeReservedInImplementsSubclasses.markReservedDirectly(
-              newName, field.field.name, field.field.type);
-        }
+        clazz
+            .asProgramClass()
+            .forEachProgramField(
+                field -> {
+                  DexString newName = renameField(field, state);
+                  namesToBeReservedInImplementsSubclasses.markReservedDirectly(
+                      newName, field.getReference().name, field.getReference().type);
+                });
       }
     }
 
@@ -236,11 +241,10 @@
     }
   }
 
-  private DexString renameField(DexEncodedField encodedField, FieldNamingState state) {
-    DexField field = encodedField.field;
+  private DexString renameField(ProgramField field, FieldNamingState state) {
     DexString newName = state.getOrCreateNameFor(field);
-    if (newName != field.name) {
-      renaming.put(field, newName);
+    if (newName != field.getReference().name) {
+      renaming.put(field.getReference(), newName);
     }
     return newName;
   }
@@ -256,32 +260,17 @@
   }
 
   private void renameNonReboundAccessToField(DexField field) {
-    // Already renamed
+    // If the given field reference is a non-rebound reference to a program field, then assign the
+    // same name as the resolved field.
     if (renaming.containsKey(field)) {
       return;
     }
-    DexEncodedField definition = appView.definitionFor(field);
-    if (definition != null) {
-      assert definition.field == field;
+    DexProgramClass holder = asProgramClassOrNull(appView.definitionForHolder(field));
+    if (holder == null) {
       return;
     }
-    // Now, `field` is reference. Find its definition and check if it's renamed.
-    DexClass holder = appView.definitionFor(field.holder);
-    // We don't care pruned types or library classes.
-    if (holder == null || holder.isNotProgramClass()) {
-      return;
-    }
-    definition = appView.appInfo().resolveField(field).getResolvedField();
-    if (definition == null) {
-      // The program is already broken in the sense that it has an unresolvable field reference.
-      // Leave it as-is.
-      return;
-    }
-    assert definition.field != field;
-    assert definition.holder() != field.holder;
-    // If the definition is renamed,
-    if (renaming.containsKey(definition.field)) {
-      // Assign the same, renamed name as the definition to the reference.
+    DexEncodedField definition = appView.appInfo().resolveFieldOn(holder, field).getResolvedField();
+    if (definition != null && definition.field != field && renaming.containsKey(definition.field)) {
       renaming.put(field, renaming.get(definition.field));
     }
   }
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNamingState.java b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
index f8d80cb..35e3d3f 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
@@ -6,11 +6,9 @@
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.naming.FieldNamingState.InternalState;
 import java.util.IdentityHashMap;
 import java.util.Map;
@@ -20,7 +18,7 @@
 
   private final ReservedFieldNamingState reservedNames;
   private final MemberNamingStrategy strategy;
-  private final BiPredicate<DexString, DexField> isAvailable;
+  private final BiPredicate<DexString, ProgramField> isAvailable;
 
   public FieldNamingState(
       AppView<? extends AppInfoWithClassHierarchy> appView, MemberNamingStrategy strategy) {
@@ -42,7 +40,8 @@
     super(appView, internalStates);
     this.reservedNames = reservedNames;
     this.strategy = strategy;
-    this.isAvailable = (newName, field) -> !reservedNames.isReserved(newName, field.type);
+    this.isAvailable =
+        (newName, field) -> !reservedNames.isReserved(newName, field.getReference().type);
   }
 
   public FieldNamingState createChildState(ReservedFieldNamingState reservedNames) {
@@ -52,20 +51,13 @@
     return childState;
   }
 
-  public DexString getOrCreateNameFor(DexField field) {
-    DexEncodedField encodedField = appView.appInfo().resolveField(field).getResolvedField();
-    if (encodedField != null) {
-      DexClass clazz = appView.definitionFor(encodedField.holder());
-      if (clazz == null) {
-        return field.name;
-      }
-      DexString reservedName = strategy.getReservedName(encodedField, clazz);
-      if (reservedName != null) {
-        return reservedName;
-      }
+  public DexString getOrCreateNameFor(ProgramField field) {
+    DexString reservedName = strategy.getReservedName(field.getDefinition(), field.getHolder());
+    if (reservedName != null) {
+      return reservedName;
     }
     // TODO(b/133208730) If we cannot resolve the field, are we then allowed to rename it?
-    return getOrCreateInternalState(field).createNewName(field);
+    return getOrCreateInternalState(field.getReference()).createNewName(field);
   }
 
   public void includeReservations(ReservedFieldNamingState reservedNames) {
@@ -100,9 +92,9 @@
       this.nextNameIndex = nextNameIndex;
     }
 
-    public DexString createNewName(DexField field) {
+    public DexString createNewName(ProgramField field) {
       DexString name = strategy.next(field, this, isAvailable);
-      assert !reservedNames.isReserved(name, field.type);
+      assert !reservedNames.isReserved(name, field.getReference().type);
       return name;
     }
 
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
index bacc7e0..cddbea8 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
@@ -7,9 +7,9 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.ProgramField;
 import java.util.function.BiPredicate;
 
 public interface MemberNamingStrategy {
@@ -20,9 +20,9 @@
       BiPredicate<DexString, DexMethod> isAvailable);
 
   DexString next(
-      DexField field,
+      ProgramField field,
       InternalNamingState internalState,
-      BiPredicate<DexString, DexField> isAvailable);
+      BiPredicate<DexString, ProgramField> isAvailable);
 
   DexString getReservedName(DexEncodedMethod method, DexClass holder);
 
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 8f1a41a..d0f0381 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -11,11 +11,11 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
 import com.android.tools.r8.naming.ClassNameMinifier.ClassNamingStrategy;
@@ -240,10 +240,10 @@
 
     @Override
     public DexString next(
-        DexField field,
+        ProgramField field,
         InternalNamingState internalState,
-        BiPredicate<DexString, DexField> isAvailable) {
-      assert checkAllowMemberRenaming(field.holder);
+        BiPredicate<DexString, ProgramField> isAvailable) {
+      assert checkAllowMemberRenaming(field.getHolderType());
       DexString candidate;
       do {
         candidate = getNextName(internalState, false);
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 3ce8e7f..e720b30 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
@@ -32,7 +33,6 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.Timing;
-import com.android.tools.r8.utils.TriFunction;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.Maps;
@@ -301,8 +301,9 @@
       DexField originalField = ((FieldSignature) signature).toDexField(factory, type);
       addMemberNaming(
           originalField, memberNaming, addToAdditionalMaps ? additionalFieldNamings : null);
-      DexEncodedField encodedField = appView.definitionFor(originalField);
-      if (encodedField == null || !encodedField.accessFlags.isPrivate()) {
+      DexClass holder = appView.definitionForHolder(originalField);
+      DexEncodedField field = originalField.lookupOnClass(holder);
+      if (field == null || !field.isPrivate()) {
         nonPrivateMembers.put(originalField, memberNaming);
       }
     }
@@ -465,62 +466,46 @@
 
     @Override
     public DexString next(
-        DexMethod method,
+        DexMethod reference,
         InternalNamingState internalState,
         BiPredicate<DexString, DexMethod> isAvailable) {
-      DexEncodedMethod definition = appView.definitionFor(method);
-      DexString nextName =
-          nextName(
-              method,
-              definition,
-              method.name,
-              method.holder,
-              internalState,
-              isAvailable,
-              super::next);
-      assert nextName == method.name || !definition.isClassInitializer();
-      assert nextName == method.name
-          || !appView.definitionFor(method.holder).accessFlags.isAnnotation();
+      DexClass holder = appView.definitionForHolder(reference);
+      assert holder != null;
+      DexEncodedMethod method = holder.lookupMethod(reference);
+      DexString reservedName = getReservedName(method, reference.name, holder);
+      DexString nextName;
+      if (reservedName != null) {
+        if (!isAvailable.test(reservedName, reference)) {
+          reportReservationError(reference, reservedName);
+        }
+        nextName = reservedName;
+      } else {
+        assert !mappedNames.containsKey(reference);
+        assert appView.rootSet().mayBeMinified(reference, appView);
+        nextName = super.next(reference, internalState, isAvailable);
+      }
+      assert nextName == reference.name || !method.isInitializer();
+      assert nextName == reference.name || !holder.isAnnotation();
       return nextName;
     }
 
     @Override
     public DexString next(
-        DexField field,
+        ProgramField field,
         InternalNamingState internalState,
-        BiPredicate<DexString, DexField> isAvailable) {
-      return nextName(
-          field,
-          appView.definitionFor(field),
-          field.name,
-          field.holder,
-          internalState,
-          isAvailable,
-          super::next);
-    }
-
-    private <T extends DexReference> DexString nextName(
-        T reference,
-        DexDefinition definition,
-        DexString name,
-        DexType holderType,
-        InternalNamingState internalState,
-        BiPredicate<DexString, T> isAvailable,
-        TriFunction<T, InternalNamingState, BiPredicate<DexString, T>, DexString> generateName) {
-      assert definition.isDexEncodedMethod() || definition.isDexEncodedField();
-      assert definition.toReference() == reference;
-      DexClass holder = appView.definitionFor(holderType);
-      assert holder != null;
-      DexString reservedName = getReservedName(definition, name, holder);
+        BiPredicate<DexString, ProgramField> isAvailable) {
+      DexField reference = field.getReference();
+      DexString reservedName =
+          getReservedName(field.getDefinition(), reference.name, field.getHolder());
       if (reservedName != null) {
-        if (!isAvailable.test(reservedName, reference)) {
-          reportReservationError(definition.toReference(), reservedName);
+        if (!isAvailable.test(reservedName, field)) {
+          reportReservationError(reference, reservedName);
         }
         return reservedName;
       }
       assert !mappedNames.containsKey(reference);
       assert appView.rootSet().mayBeMinified(reference, appView);
-      return generateName.apply(reference, internalState, isAvailable);
+      return super.next(field, internalState, isAvailable);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
index 31e239b..54decce 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
@@ -254,8 +254,9 @@
 
     // Remove all of the bridges in the eligible subclasses.
     for (DexProgramClass subclass : eligibleSubclasses) {
+      assert !appView.appInfo().isPinned(method);
       DexEncodedMethod removed = subclass.removeMethod(method);
-      assert removed != null && !appView.appInfo().isPinned(removed.method);
+      assert removed != null;
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index b992f8d..1c1de57 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -347,9 +347,9 @@
     computeMethodRebinding(appInfo.directInvokes, this::anyLookup, Type.DIRECT);
     // Likewise static invokes.
     computeMethodRebinding(appInfo.staticInvokes, this::anyLookup, Type.STATIC);
-
     computeFieldRebinding();
-
-    return builder.build(lense);
+    GraphLense lens = builder.build(lense);
+    appInfo.getFieldAccessInfoCollection().flattenAccessContexts();
+    return lens;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
index 95e2772..01c923d 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
@@ -15,7 +15,6 @@
 import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.Map;
-import java.util.Set;
 
 public class MemberRebindingLense extends NestedGraphLense {
 
@@ -99,11 +98,6 @@
   }
 
   @Override
-  public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
-    return previousLense.lookupMethodInAllContexts(method);
-  }
-
-  @Override
   protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
     return super.mapVirtualInterfaceInvocationTypes(appView, newMethod, originalMethod, type);
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 584b84b..3c0d9dd 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -258,9 +258,9 @@
   }
 
   private boolean enclosingMethodPinned(DexClass clazz) {
-    return clazz.getEnclosingMethod() != null
-        && clazz.getEnclosingMethod().getEnclosingClass() != null
-        && appView.appInfo().isPinned(clazz.getEnclosingMethod().getEnclosingClass());
+    return clazz.getEnclosingMethodAttribute() != null
+        && clazz.getEnclosingMethodAttribute().getEnclosingClass() != null
+        && appView.appInfo().isPinned(clazz.getEnclosingMethodAttribute().getEnclosingClass());
   }
 
   private static boolean hasInnerClassesFromSet(DexProgramClass clazz, Set<DexType> innerClasses) {
@@ -292,7 +292,7 @@
     }
     if (keptAnyway || keepForThisInnerClass || keepForThisEnclosingClass) {
       if (!keep.enclosingMethod) {
-        clazz.clearEnclosingMethod();
+        clazz.clearEnclosingMethodAttribute();
       }
       if (!keep.innerClasses) {
         clazz.clearInnerClasses();
@@ -305,14 +305,15 @@
               if (appView.appInfo().isPinned(ica.getInner())) {
                 return false;
               }
-              if (appView.appInfo().isPinned(ica.getOuter())) {
+              DexType outer = ica.getOuter();
+              if (outer != null && appView.appInfo().isPinned(outer)) {
                 return false;
               }
               if (finalKeepForThisInnerClass && ica.getInner() == clazz.type) {
                 return false;
               }
               if (finalKeepForThisEnclosingClass
-                  && ica.getOuter() == clazz.type
+                  && outer == clazz.type
                   && classesToRetainInnerClassAttributeFor.contains(ica.getInner())) {
                 return false;
               }
@@ -322,7 +323,7 @@
     } else {
       // These attributes are only relevant for reflection, and this class is not used for
       // reflection. (Note that clearing these attributes can enable more vertical class merging.)
-      clazz.clearEnclosingMethod();
+      clazz.clearEnclosingMethodAttribute();
       clazz.clearInnerClasses();
     }
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 275b882..87bfd9a 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -5,7 +5,6 @@
 
 import static com.android.tools.r8.graph.DexEncodedMethod.asProgramMethodOrNull;
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-import static com.android.tools.r8.graph.GraphLense.rewriteReferenceKeys;
 import static com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult.isOverriding;
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -27,6 +26,7 @@
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
 import com.android.tools.r8.graph.FieldAccessInfoImpl;
+import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.graph.InstantiatedSubTypeInfo;
@@ -44,6 +44,7 @@
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
+import com.android.tools.r8.utils.AssertionUtils;
 import com.android.tools.r8.utils.CollectionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
@@ -51,9 +52,6 @@
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.Visibility;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSortedSet;
-import com.google.common.collect.ImmutableSortedSet.Builder;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
@@ -63,13 +61,11 @@
 import java.util.Deque;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.SortedSet;
 import java.util.TreeMap;
 import java.util.function.Consumer;
-import java.util.function.Function;
 import java.util.stream.Collectors;
 
 /** Encapsulates liveness and reachability information for an application. */
@@ -136,10 +132,8 @@
    * will have been removed from the code.
    */
   public final Set<DexCallSite> callSites;
-  /** Set of all items that have to be kept independent of whether they are used. */
-  final Set<DexReference> pinnedItems;
-  /** Set of kept items that are allowed to be publicized. */
-  final Set<DexReference> allowAccessModification;
+  /** Collection of keep requirements for the program. */
+  private final KeepInfoCollection keepInfo;
   /** All items with assumemayhavesideeffects rule. */
   public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
   /** All items with assumenosideeffects rule. */
@@ -217,8 +211,7 @@
       SortedMap<DexMethod, ProgramMethodSet> directInvokes,
       SortedMap<DexMethod, ProgramMethodSet> staticInvokes,
       Set<DexCallSite> callSites,
-      Set<DexReference> pinnedItems,
-      Set<DexReference> allowAccessModification,
+      KeepInfoCollection keepInfo,
       Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
       Map<DexReference, ProguardMemberRule> noSideEffects,
       Map<DexReference, ProguardMemberRule> assumedValues,
@@ -253,8 +246,7 @@
     this.liveMethods = liveMethods;
     this.fieldAccessInfoCollection = fieldAccessInfoCollection;
     this.objectAllocationInfoCollection = objectAllocationInfoCollection;
-    this.pinnedItems = pinnedItems;
-    this.allowAccessModification = allowAccessModification;
+    this.keepInfo = keepInfo;
     this.mayHaveSideEffects = mayHaveSideEffects;
     this.noSideEffects = noSideEffects;
     this.assumedValues = assumedValues;
@@ -304,8 +296,7 @@
       SortedMap<DexMethod, ProgramMethodSet> directInvokes,
       SortedMap<DexMethod, ProgramMethodSet> staticInvokes,
       Set<DexCallSite> callSites,
-      Set<DexReference> pinnedItems,
-      Set<DexReference> allowAccessModification,
+      KeepInfoCollection keepInfo,
       Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
       Map<DexReference, ProguardMemberRule> noSideEffects,
       Map<DexReference, ProguardMemberRule> assumedValues,
@@ -340,8 +331,7 @@
     this.liveMethods = liveMethods;
     this.fieldAccessInfoCollection = fieldAccessInfoCollection;
     this.objectAllocationInfoCollection = objectAllocationInfoCollection;
-    this.pinnedItems = pinnedItems;
-    this.allowAccessModification = allowAccessModification;
+    this.keepInfo = keepInfo;
     this.mayHaveSideEffects = mayHaveSideEffects;
     this.noSideEffects = noSideEffects;
     this.assumedValues = assumedValues;
@@ -392,8 +382,7 @@
         previous.directInvokes,
         previous.staticInvokes,
         previous.callSites,
-        previous.pinnedItems,
-        previous.allowAccessModification,
+        previous.keepInfo,
         previous.mayHaveSideEffects,
         previous.noSideEffects,
         previous.assumedValues,
@@ -443,10 +432,7 @@
         previous.directInvokes,
         previous.staticInvokes,
         previous.callSites,
-        additionalPinnedItems == null
-            ? previous.pinnedItems
-            : CollectionUtils.mergeSets(previous.pinnedItems, additionalPinnedItems),
-        previous.allowAccessModification,
+        extendPinnedItems(previous, additionalPinnedItems),
         previous.mayHaveSideEffects,
         previous.noSideEffects,
         previous.assumedValues,
@@ -471,7 +457,44 @@
         previous.constClassReferences,
         previous.initClassReferences);
     copyMetadataFromPrevious(previous);
-    assert removedClasses == null || assertNoItemRemoved(previous.pinnedItems, removedClasses);
+    assert keepInfo.verifyNoneArePinned(removedClasses, previous);
+  }
+
+  private static KeepInfoCollection extendPinnedItems(
+      AppInfoWithLiveness previous, Collection<DexReference> additionalPinnedItems) {
+    if (additionalPinnedItems == null || additionalPinnedItems.isEmpty()) {
+      return previous.keepInfo;
+    }
+    return previous.keepInfo.mutate(
+        collection -> {
+          for (DexReference reference : additionalPinnedItems) {
+            if (reference.isDexType()) {
+              DexProgramClass clazz =
+                  asProgramClassOrNull(previous.definitionFor(reference.asDexType()));
+              if (clazz != null) {
+                collection.pinClass(clazz);
+              }
+            } else if (reference.isDexMethod()) {
+              DexMethod method = reference.asDexMethod();
+              DexProgramClass clazz = asProgramClassOrNull(previous.definitionFor(method.holder));
+              if (clazz != null) {
+                DexEncodedMethod definition = clazz.lookupMethod(method);
+                if (definition != null) {
+                  collection.pinMethod(clazz, definition);
+                }
+              }
+            } else {
+              DexField field = reference.asDexField();
+              DexProgramClass clazz = asProgramClassOrNull(previous.definitionFor(field.holder));
+              if (clazz != null) {
+                DexEncodedField definition = clazz.lookupField(field);
+                if (definition != null) {
+                  collection.pinField(clazz, definition);
+                }
+              }
+            }
+          }
+        });
   }
 
   public AppInfoWithLiveness(
@@ -491,8 +514,7 @@
     this.liveMethods = previous.liveMethods;
     this.fieldAccessInfoCollection = previous.fieldAccessInfoCollection;
     this.objectAllocationInfoCollection = previous.objectAllocationInfoCollection;
-    this.pinnedItems = previous.pinnedItems;
-    this.allowAccessModification = previous.allowAccessModification;
+    this.keepInfo = previous.keepInfo;
     this.mayHaveSideEffects = previous.mayHaveSideEffects;
     this.noSideEffects = previous.noSideEffects;
     this.assumedValues = previous.assumedValues;
@@ -543,8 +565,6 @@
             || TwrCloseResourceRewriter.isUtilityClassDescriptor(type)
             // TODO(b/150736225): Not sure how to remove these.
             || DesugaredLibraryAPIConverter.isVivifiedType(type)
-            // TODO(b/149363884): Handle references to dead proto builders.
-            || type.toDescriptorString().endsWith("$Builder;")
         : "Failed lookup of non-missing type: " + type;
     return definition;
   }
@@ -754,23 +774,6 @@
     singleTargetLookupCache.removeInstantiatedType(clazz.type, this);
   }
 
-  private boolean assertNoItemRemoved(Collection<DexReference> items, Collection<DexType> types) {
-    Set<DexType> typeSet = ImmutableSet.copyOf(types);
-    for (DexReference item : items) {
-      DexType typeToCheck;
-      if (item.isDexType()) {
-        typeToCheck = item.asDexType();
-      } else if (item.isDexMethod()) {
-        typeToCheck = item.asDexMethod().holder;
-      } else {
-        assert item.isDexField();
-        typeToCheck = item.asDexField().holder;
-      }
-      assert !typeSet.contains(typeToCheck);
-    }
-    return true;
-  }
-
   private boolean isInstantiatedDirectly(DexProgramClass clazz) {
     assert checkIfObsolete();
     DexType type = clazz.type;
@@ -797,7 +800,7 @@
     if (info != null && info.isRead()) {
       return true;
     }
-    return isPinned(field)
+    return keepInfo.isPinned(field, this)
         // Fields in the class that is synthesized by D8/R8 would be used soon.
         || field.holder.isD8R8SynthesizedClassType()
         // For library classes we don't know whether a field is read.
@@ -869,7 +872,7 @@
     return method.getDefinition().hasCode()
         && !method.getDefinition().isLibraryMethodOverride().isPossiblyTrue()
         && !neverReprocess.contains(reference)
-        && !pinnedItems.contains(reference);
+        && !keepInfo.getMethodInfo(method).isPinned();
   }
 
   public boolean mayPropagateValueFor(DexReference reference) {
@@ -884,27 +887,15 @@
     return holder == null || holder.isLibraryClass() || holder.isClasspathClass();
   }
 
-  private static <T extends PresortedComparable<T>> ImmutableSortedSet<T> rewriteItems(
-      Set<T> original, Function<T, T> rewrite) {
-    Builder<T> builder = new Builder<>(PresortedComparable::slowCompare);
-    for (T item : original) {
-      builder.add(rewrite.apply(item));
-    }
-    return builder.build();
-  }
-
-  private static <T extends PresortedComparable<T>>
-      SortedMap<T, ProgramMethodSet> rewriteKeysConservativelyWhileMergingValues(
-          Map<T, ProgramMethodSet> original, Function<T, Set<T>> rewrite) {
-    SortedMap<T, ProgramMethodSet> result = new TreeMap<>(PresortedComparable::slowCompare);
-    for (T item : original.keySet()) {
-      Set<T> rewrittenKeys = rewrite.apply(item);
-      for (T rewrittenKey : rewrittenKeys) {
-        result
-            .computeIfAbsent(rewrittenKey, k -> ProgramMethodSet.create())
-            .addAll(original.get(item));
-      }
-    }
+  private static SortedMap<DexMethod, ProgramMethodSet> rewriteInvokesWithContexts(
+      Map<DexMethod, ProgramMethodSet> invokes, GraphLense lens) {
+    SortedMap<DexMethod, ProgramMethodSet> result = new TreeMap<>(PresortedComparable::slowCompare);
+    invokes.forEach(
+        (method, contexts) ->
+            result
+                .computeIfAbsent(
+                    lens.getRenamedMethodSignature(method), ignore -> ProgramMethodSet.create())
+                .addAll(contexts));
     return Collections.unmodifiableSortedMap(result);
   }
 
@@ -927,12 +918,14 @@
 
   public boolean isAccessModificationAllowed(DexReference reference) {
     assert options().getProguardConfiguration().isAccessModificationAllowed();
-    return allowAccessModification.contains(reference) || !isPinned(reference);
+    return keepInfo
+        .getInfo(reference, this)
+        .isAccessModificationAllowed(options().getProguardConfiguration());
   }
 
   public boolean isPinned(DexReference reference) {
     assert checkIfObsolete();
-    return pinnedItems.contains(reference);
+    return keepInfo.isPinned(reference, this);
   }
 
   public boolean hasPinnedInstanceInitializer(DexType type) {
@@ -948,9 +941,8 @@
     return false;
   }
 
-  public Set<DexReference> getPinnedItems() {
-    assert checkIfObsolete();
-    return pinnedItems;
+  public KeepInfoCollection getKeepInfo() {
+    return keepInfo;
   }
 
   /**
@@ -984,70 +976,62 @@
     // Switchmap classes should never be affected by renaming.
     assert lens.assertDefinitionsNotModified(
         switchMaps.keySet().stream()
-            .map(this::definitionFor)
-            .filter(Objects::nonNull)
+            .map(this::resolveField)
+            .filter(FieldResolutionResult::isSuccessfulResolution)
+            .map(FieldResolutionResult::getResolvedField)
             .collect(Collectors.toList()));
 
     assert lens.assertDefinitionsNotModified(
         neverMerge.stream()
             .map(this::definitionFor)
-            .filter(Objects::nonNull)
-            .collect(Collectors.toList()));
-
-    assert lens.assertDefinitionsNotModified(
-        alwaysInline.stream()
-            .map(this::definitionFor)
-            .filter(Objects::nonNull)
+            .filter(AssertionUtils::assertNotNull)
             .collect(Collectors.toList()));
 
     return new AppInfoWithLiveness(
         application,
         deadProtoTypes,
         missingTypes,
-        rewriteItems(liveTypes, lens::lookupType),
-        rewriteItems(instantiatedAppServices, lens::lookupType),
-        lens.rewriteMethodsConservatively(targetedMethods),
-        lens.rewriteMethodsConservatively(failedResolutionTargets),
-        lens.rewriteMethodsConservatively(bootstrapMethods),
-        lens.rewriteMethodsConservatively(methodsTargetedByInvokeDynamic),
-        lens.rewriteMethodsConservatively(virtualMethodsTargetedByInvokeDirect),
-        lens.rewriteMethodsConservatively(liveMethods),
+        lens.rewriteTypes(liveTypes),
+        lens.rewriteTypes(instantiatedAppServices),
+        lens.rewriteMethods(targetedMethods),
+        lens.rewriteMethods(failedResolutionTargets),
+        lens.rewriteMethods(bootstrapMethods),
+        lens.rewriteMethods(methodsTargetedByInvokeDynamic),
+        lens.rewriteMethods(virtualMethodsTargetedByInvokeDirect),
+        lens.rewriteMethods(liveMethods),
         fieldAccessInfoCollection.rewrittenWithLens(application, lens),
         objectAllocationInfoCollection.rewrittenWithLens(application, lens),
-        rewriteKeysConservativelyWhileMergingValues(
-            virtualInvokes, lens::lookupMethodInAllContexts),
-        rewriteKeysConservativelyWhileMergingValues(
-            interfaceInvokes, lens::lookupMethodInAllContexts),
-        rewriteKeysConservativelyWhileMergingValues(superInvokes, lens::lookupMethodInAllContexts),
-        rewriteKeysConservativelyWhileMergingValues(directInvokes, lens::lookupMethodInAllContexts),
-        rewriteKeysConservativelyWhileMergingValues(staticInvokes, lens::lookupMethodInAllContexts),
+        rewriteInvokesWithContexts(virtualInvokes, lens),
+        rewriteInvokesWithContexts(interfaceInvokes, lens),
+        rewriteInvokesWithContexts(superInvokes, lens),
+        rewriteInvokesWithContexts(directInvokes, lens),
+        rewriteInvokesWithContexts(staticInvokes, lens),
         // TODO(sgjesse): Rewrite call sites as well? Right now they are only used by minification
         //   after second tree shaking.
         callSites,
-        lens.rewriteReferencesConservatively(pinnedItems),
-        lens.rewriteReferencesConservatively(allowAccessModification),
-        rewriteReferenceKeys(mayHaveSideEffects, lens::lookupReference),
-        rewriteReferenceKeys(noSideEffects, lens::lookupReference),
-        rewriteReferenceKeys(assumedValues, lens::lookupReference),
-        lens.rewriteMethodsWithRenamedSignature(alwaysInline),
-        lens.rewriteMethodsWithRenamedSignature(forceInline),
-        lens.rewriteMethodsWithRenamedSignature(neverInline),
-        lens.rewriteMethodsWithRenamedSignature(whyAreYouNotInlining),
-        lens.rewriteMethodsWithRenamedSignature(keepConstantArguments),
-        lens.rewriteMethodsWithRenamedSignature(keepUnusedArguments),
-        lens.rewriteMethodsWithRenamedSignature(reprocess),
-        lens.rewriteMethodsWithRenamedSignature(neverReprocess),
+        keepInfo.rewrite(lens),
+        lens.rewriteReferenceKeys(mayHaveSideEffects),
+        lens.rewriteReferenceKeys(noSideEffects),
+        lens.rewriteReferenceKeys(assumedValues),
+        lens.rewriteMethods(alwaysInline),
+        lens.rewriteMethods(forceInline),
+        lens.rewriteMethods(neverInline),
+        lens.rewriteMethods(whyAreYouNotInlining),
+        lens.rewriteMethods(keepConstantArguments),
+        lens.rewriteMethods(keepUnusedArguments),
+        lens.rewriteMethods(reprocess),
+        lens.rewriteMethods(neverReprocess),
         alwaysClassInline.rewriteItems(lens::lookupType),
-        rewriteItems(neverClassInline, lens::lookupType),
-        rewriteItems(neverMerge, lens::lookupType),
-        lens.rewriteReferencesConservatively(neverPropagateValue),
-        lens.rewriteReferencesConservatively(identifierNameStrings),
+        lens.rewriteTypes(neverClassInline),
+        lens.rewriteTypes(neverMerge),
+        lens.rewriteReferences(neverPropagateValue),
+        lens.rewriteReferenceKeys(identifierNameStrings),
         // Don't rewrite pruned types - the removed types are identified by their original name.
         prunedTypes,
-        rewriteReferenceKeys(switchMaps, lens::lookupField),
+        lens.rewriteFieldKeys(switchMaps),
         enumValueInfoMaps.rewrittenWithLens(lens),
-        rewriteItems(constClassReferences, lens::lookupType),
-        rewriteReferenceKeys(initClassReferences, lens::lookupType));
+        lens.rewriteTypes(constClassReferences),
+        lens.rewriteTypeKeys(initClassReferences));
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 8a54cca..a0a1924 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -92,7 +92,9 @@
 import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
 import com.android.tools.r8.shaking.EnqueuerWorklist.EnqueuerAction;
 import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
+import com.android.tools.r8.shaking.KeepInfoCollection.MutableKeepInfoCollection;
 import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
+import com.android.tools.r8.shaking.RootSetBuilder.ItemsWithRules;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
 import com.android.tools.r8.utils.Action;
@@ -237,7 +239,7 @@
   private final Set<DexType> missingTypes = Sets.newIdentityHashSet();
 
   /** Set of proto types that were found to be dead during the first round of tree shaking. */
-  private Set<DexType> initialDeadProtoTypes;
+  private Set<DexType> initialDeadProtoTypes = Sets.newIdentityHashSet();
 
   /** Set of types that were found to be missing during the first round of tree shaking. */
   private Set<DexType> initialMissingTypes;
@@ -305,18 +307,8 @@
    */
   private final Set<DexReference> reportedMissing = Sets.newIdentityHashSet();
 
-  /**
-   * A set of references that we are keeping due to keep rules. This may differ from the root set
-   * due to dependent keep rules.
-   */
-  private final Set<DexReference> pinnedItems = Sets.newIdentityHashSet();
-
-  /**
-   * A set of references that we are keeping due to keep rules, which we are allowed to publicize.
-   */
-  // TODO(b/156715504): This should be maintained in a structure that describes what we are allowed
-  //  and not allowed to do with program items that are referenced from keep rules.
-  private final Map<DexReference, OptionalBool> allowAccessModification = new IdentityHashMap<>();
+  /** Collection of keep requirements for the program. */
+  private final MutableKeepInfoCollection keepInfo = new MutableKeepInfoCollection();
 
   /**
    * A set of seen const-class references that both serve as an initial lock-candidate set and will
@@ -374,10 +366,15 @@
     this.useRegistryFactory = createUseRegistryFactory();
     this.workList = EnqueuerWorklist.createWorklist(appView);
 
-    if (options.protoShrinking().enableGeneratedMessageLiteShrinking
-        && mode.isInitialOrFinalTreeShaking()) {
-      registerAnalysis(new ProtoEnqueuerExtension(appView));
+    if (mode.isInitialOrFinalTreeShaking()) {
+      if (options.protoShrinking().enableGeneratedMessageLiteShrinking) {
+        registerAnalysis(new ProtoEnqueuerExtension(appView));
+      }
+      appView.withGeneratedMessageLiteBuilderShrinker(
+          shrinker -> registerAnalysis(shrinker.createEnqueuerAnalysis()));
     }
+
+
     liveTypes = new SetWithReportedReason<>();
     initializedTypes = new SetWithReportedReason<>();
     targetedMethods = new SetWithReason<>(graphReporter::registerMethod);
@@ -507,25 +504,6 @@
     recordTypeReference(field.type);
   }
 
-  public DexDefinition definitionFor(DexReference reference) {
-    if (reference.isDexType()) {
-      return definitionFor(reference.asDexType());
-    } else if (reference.isDexMethod()) {
-      return definitionFor(reference.asDexMethod());
-    } else {
-      assert reference.isDexField();
-      return definitionFor(reference.asDexField());
-    }
-  }
-
-  public DexEncodedField definitionFor(DexField field) {
-    DexClass clazz = definitionFor(field.holder);
-    if (clazz == null) {
-      return null;
-    }
-    return clazz.lookupField(field);
-  }
-
   public DexEncodedMethod definitionFor(DexMethod method) {
     DexClass clazz = definitionFor(method.holder);
     if (clazz == null) {
@@ -546,6 +524,10 @@
     return clazz;
   }
 
+  public boolean isPinned(DexType type) {
+    return keepInfo.isPinned(type, appInfo);
+  }
+
   private void addLiveNonProgramType(DexClass clazz) {
     assert clazz.isNotProgramClass();
     // Fast path to avoid the worklist when the class is already seen.
@@ -614,24 +596,98 @@
     }
   }
 
-  private static <T> SetWithReason<T> newSetWithoutReasonReporter() {
-    return new SetWithReason<>((f, r) -> {});
-  }
-
   private void enqueueRootItems(Map<DexReference, Set<ProguardKeepRuleBase>> items) {
     items.entrySet().forEach(this::enqueueRootItem);
   }
 
+  private void enqueueRootItems(ItemsWithRules items) {
+    items.forEachField(this::enqueueRootField);
+    items.forEachMethod(this::enqueueRootMethod);
+    items.forEachClass(this::enqueueRootClass);
+  }
+
   private void enqueueRootItem(Entry<DexReference, Set<ProguardKeepRuleBase>> root) {
-    DexDefinition item = appView.definitionFor(root.getKey());
-    if (item != null) {
-      enqueueRootItem(item, root.getValue());
+    DexReference reference = root.getKey();
+    Set<ProguardKeepRuleBase> rules = root.getValue();
+    if (reference.isDexField()) {
+      enqueueRootField(reference.asDexField(), rules);
+    } else if (reference.isDexMethod()) {
+      enqueueRootMethod(reference.asDexMethod(), rules);
+    } else if (reference.isDexType()) {
+      enqueueRootClass(reference.asDexType(), rules);
     } else {
-      // TODO(b/123923324): Verify that root items are present.
-      // assert false : "Expected root item `" + root.getKey().toSourceString() + "` to be present";
+      throw new Unreachable();
     }
   }
 
+  // TODO(b/123923324): Verify that root items are present.
+  private void enqueueRootClass(DexType type, Set<ProguardKeepRuleBase> rules) {
+    DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+    if (clazz != null) {
+      enqueueRootClass(clazz, rules, null);
+    }
+  }
+
+  private void enqueueRootClass(
+      DexProgramClass clazz, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
+    KeepReasonWitness witness = graphReporter.reportKeepClass(precondition, rules, clazz);
+    keepClassWithRules(clazz, rules);
+    if (clazz.isAnnotation()) {
+      workList.enqueueMarkAnnotationInstantiatedAction(clazz, witness);
+    } else if (clazz.isInterface()) {
+      workList.enqueueMarkInterfaceInstantiatedAction(clazz, witness);
+    } else {
+      workList.enqueueMarkInstantiatedAction(clazz, null, InstantiationReason.KEEP_RULE, witness);
+      if (clazz.hasDefaultInitializer()) {
+        ProgramMethod defaultInitializer = clazz.getProgramDefaultInitializer();
+        if (forceProguardCompatibility) {
+          workList.enqueueMarkMethodKeptAction(
+              defaultInitializer,
+              graphReporter.reportCompatKeepDefaultInitializer(defaultInitializer));
+        }
+        if (clazz.isExternalizable(appView)) {
+          enqueueMarkMethodLiveAction(defaultInitializer, witness);
+        }
+      }
+    }
+  }
+
+  // TODO(b/123923324): Verify that root items are present.
+  private void enqueueRootField(DexField reference, Set<ProguardKeepRuleBase> rules) {
+    DexProgramClass holder = getProgramClassOrNull(reference.holder);
+    if (holder != null) {
+      ProgramField field = holder.lookupProgramField(reference);
+      if (field != null) {
+        enqueueRootField(field, rules, null);
+      }
+    }
+  }
+
+  private void enqueueRootField(
+      ProgramField field, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
+    keepFieldWithRules(field.getHolder(), field.getDefinition(), rules);
+    workList.enqueueMarkFieldKeptAction(
+        field, graphReporter.reportKeepField(precondition, rules, field.getDefinition()));
+  }
+
+  // TODO(b/123923324): Verify that root items are present.
+  private void enqueueRootMethod(DexMethod reference, Set<ProguardKeepRuleBase> rules) {
+    DexProgramClass holder = getProgramClassOrNull(reference.holder);
+    if (holder != null) {
+      ProgramMethod method = holder.lookupProgramMethod(reference);
+      if (method != null) {
+        enqueueRootMethod(method, rules, null);
+      }
+    }
+  }
+
+  private void enqueueRootMethod(
+      ProgramMethod method, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
+    keepMethodWithRules(method.getHolder(), method.getDefinition(), rules);
+    workList.enqueueMarkMethodKeptAction(
+        method, graphReporter.reportKeepMethod(precondition, rules, method.getDefinition()));
+  }
+
   private void enqueueRootItem(DexDefinition item, Set<ProguardKeepRuleBase> rules) {
     internalEnqueueRootItem(item, rules, null);
   }
@@ -640,45 +696,24 @@
       DexDefinition item, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
     if (item.isDexClass()) {
       DexProgramClass clazz = item.asDexClass().asProgramClass();
-      KeepReasonWitness witness = graphReporter.reportKeepClass(precondition, rules, clazz);
-      if (clazz.isAnnotation()) {
-        workList.enqueueMarkAnnotationInstantiatedAction(clazz, witness);
-      } else if (clazz.isInterface()) {
-        workList.enqueueMarkInterfaceInstantiatedAction(clazz, witness);
-      } else {
-        workList.enqueueMarkInstantiatedAction(clazz, null, InstantiationReason.KEEP_RULE, witness);
-        if (clazz.hasDefaultInitializer()) {
-          ProgramMethod defaultInitializer = clazz.getProgramDefaultInitializer();
-          if (forceProguardCompatibility) {
-            workList.enqueueMarkMethodKeptAction(
-                defaultInitializer,
-                graphReporter.reportCompatKeepDefaultInitializer(defaultInitializer));
-          }
-          if (clazz.isExternalizable(appView)) {
-            enqueueMarkMethodLiveAction(defaultInitializer, witness);
-          }
-        }
+      if (clazz != null) {
+        enqueueRootClass(clazz, rules, precondition);
       }
     } else if (item.isDexEncodedField()) {
       DexEncodedField field = item.asDexEncodedField();
       DexProgramClass holder = getProgramClassOrNull(field.holder());
       if (holder != null) {
-        workList.enqueueMarkFieldKeptAction(
-            new ProgramField(holder, field),
-            graphReporter.reportKeepField(precondition, rules, field));
+        enqueueRootField(new ProgramField(holder, field), rules, precondition);
       }
     } else if (item.isDexEncodedMethod()) {
-      DexEncodedMethod encodedMethod = item.asDexEncodedMethod();
-      DexProgramClass holder = getProgramClassOrNull(encodedMethod.holder());
+      DexEncodedMethod method = item.asDexEncodedMethod();
+      DexProgramClass holder = getProgramClassOrNull(method.holder());
       if (holder != null) {
-        workList.enqueueMarkMethodKeptAction(
-            new ProgramMethod(holder, encodedMethod),
-            graphReporter.reportKeepMethod(precondition, rules, encodedMethod));
+        enqueueRootMethod(new ProgramMethod(holder, method), rules, precondition);
       }
     } else {
       throw new IllegalArgumentException(item.toString());
     }
-    addPinnedItem(item.toReference(), rules);
   }
 
   private void enqueueFirstNonSerializableClassInitializer(
@@ -1314,7 +1349,7 @@
       boolean skipTracing =
           appView.withGeneratedExtensionRegistryShrinker(
               shrinker ->
-                  shrinker.isDeadProtoExtensionField(field, fieldAccessInfoCollection, pinnedItems),
+                  shrinker.isDeadProtoExtensionField(field, fieldAccessInfoCollection, keepInfo),
               false);
       if (skipTracing) {
         addDeadProtoTypeCandidate(field.getHolder());
@@ -1373,7 +1408,7 @@
       boolean skipTracing =
           appView.withGeneratedExtensionRegistryShrinker(
               shrinker ->
-                  shrinker.isDeadProtoExtensionField(field, fieldAccessInfoCollection, pinnedItems),
+                  shrinker.isDeadProtoExtensionField(field, fieldAccessInfoCollection, keepInfo),
               false);
       if (skipTracing) {
         addDeadProtoTypeCandidate(field.getHolder());
@@ -1482,7 +1517,7 @@
       recordTypeReference(innerClassAttribute.getInner());
       recordTypeReference(innerClassAttribute.getOuter());
     }
-    EnclosingMethodAttribute enclosingMethodAttribute = holder.getEnclosingMethod();
+    EnclosingMethodAttribute enclosingMethodAttribute = holder.getEnclosingMethodAttribute();
     if (enclosingMethodAttribute != null) {
       DexMethod enclosingMethod = enclosingMethodAttribute.getEnclosingMethod();
       if (enclosingMethod != null) {
@@ -1619,8 +1654,8 @@
   }
 
   private void enqueueHolderWithDependentInstanceConstructor(
-      DexProgramClass clazz, ProgramMethod instanceInitializer, Set<ProguardKeepRuleBase> reasons) {
-    enqueueRootItem(clazz, reasons);
+      ProgramMethod instanceInitializer, Set<ProguardKeepRuleBase> reasons) {
+    enqueueRootItem(instanceInitializer.getHolder(), reasons);
   }
 
   private void processAnnotations(DexProgramClass holder, DexDefinition annotatedItem) {
@@ -1808,12 +1843,12 @@
             holder,
             (dexType, ignored) -> {
               if (holder.isProgramClass()) {
-                DexReference holderReference = holder.toReference();
-                addPinnedItem(holderReference);
-                rootSet.shouldNotBeMinified(holderReference);
+                DexProgramClass holderClass = holder.asProgramClass();
+                keepInfo.keepClass(holderClass);
+                rootSet.shouldNotBeMinified(holder.toReference());
                 for (DexEncodedMember<?, ?> member : holder.members()) {
+                  keepInfo.keepMember(holderClass, member);
                   DexMember<?, ?> memberReference = member.toReference();
-                  addPinnedItem(memberReference);
                   rootSet.shouldNotBeMinified(memberReference);
                 }
               }
@@ -1841,7 +1876,7 @@
   private void reportMissingClass(DexType clazz) {
     assert !mode.isFinalTreeShaking()
             || appView.dexItemFactory().isPossiblyCompilerSynthesizedType(clazz)
-            || (initialDeadProtoTypes != null && initialDeadProtoTypes.contains(clazz))
+            || initialDeadProtoTypes.contains(clazz)
             || initialMissingTypes.contains(clazz)
         : "Unexpected missing class `" + clazz.toSourceString() + "`";
     boolean newReport = missingTypes.add(clazz);
@@ -2461,7 +2496,7 @@
             (type, subTypeConsumer, lambdaConsumer) ->
                 objectAllocationInfoCollection.forEachInstantiatedSubType(
                     type, subTypeConsumer, lambdaConsumer, appInfo),
-            pinnedItems::contains)
+            reference -> keepInfo.isPinned(reference, appInfo))
         .forEach(
             target ->
                 markVirtualDispatchTargetAsLive(
@@ -2528,7 +2563,7 @@
       // TODO(sgjesse): Does this have to be enqueued as a root item? Right now it is done as the
       // marking for not renaming it is in the root set.
       workList.enqueueMarkMethodKeptAction(new ProgramMethod(clazz, valuesMethod), reason);
-      addPinnedItem(valuesMethod.toReference());
+      keepInfo.keepMethod(clazz, valuesMethod);
       rootSet.shouldNotBeMinified(valuesMethod.toReference());
     }
   }
@@ -2599,9 +2634,10 @@
     this.dontWarnPatterns = dontWarnPatterns;
     // Translate the result of root-set computation into enqueuer actions.
     if (appView.options().getProguardConfiguration() != null
-        && !options.kotlinOptimizationOptions().disableKotlinSpecificOptimizations
-        && mode.isInitialTreeShaking()) {
-      registerAnalysis(new KotlinMetadataEnqueuerExtension(appView, enqueuerDefinitionSupplier));
+        && !options.kotlinOptimizationOptions().disableKotlinSpecificOptimizations) {
+      registerAnalysis(
+          new KotlinMetadataEnqueuerExtension(
+              appView, enqueuerDefinitionSupplier, initialPrunedTypes));
     }
     if (appView.options().isShrinking() || appView.options().getProguardConfiguration() == null) {
       enqueueRootItems(rootSet.noShrinking);
@@ -2632,37 +2668,44 @@
     return appInfoWithLiveness;
   }
 
-  public boolean isPinned(DexReference reference) {
-    return pinnedItems.contains(reference);
+  private void keepClassWithRules(DexProgramClass clazz, Set<ProguardKeepRuleBase> rules) {
+    keepInfo.joinClass(
+        clazz,
+        info ->
+            info.pin()
+                .lazyDisallowAccessModification(() -> computeDisallowAccessModification(rules)));
   }
 
-  private boolean addPinnedItem(DexReference reference) {
-    allowAccessModification.put(reference, OptionalBool.unknown());
-    return pinnedItems.add(reference);
+  private void keepMethodWithRules(
+      DexProgramClass holder, DexEncodedMethod method, Set<ProguardKeepRuleBase> rules) {
+    keepInfo.joinMethod(
+        holder,
+        method,
+        info ->
+            info.pin()
+                .lazyDisallowAccessModification(() -> computeDisallowAccessModification(rules)));
   }
 
-  private boolean addPinnedItem(DexReference reference, Set<ProguardKeepRuleBase> rules) {
-    assert rules != null;
-    assert !rules.isEmpty();
-    OptionalBool allowAccessModificationOfReference =
-        allowAccessModification.getOrDefault(reference, OptionalBool.TRUE);
-    if (allowAccessModificationOfReference.isTrue()) {
-      for (ProguardKeepRuleBase rule : rules) {
-        ProguardKeepRuleModifiers modifiers =
-            (rule.isProguardIfRule() ? rule.asProguardIfRule().getSubsequentRule() : rule)
-                .getModifiers();
-        if (!modifiers.allowsAccessModification) {
-          allowAccessModificationOfReference = OptionalBool.FALSE;
-          break;
-        }
+  private void keepFieldWithRules(
+      DexProgramClass holder, DexEncodedField field, Set<ProguardKeepRuleBase> rules) {
+    keepInfo.joinField(
+        holder,
+        field,
+        info ->
+            info.pin()
+                .lazyDisallowAccessModification(() -> computeDisallowAccessModification(rules)));
+  }
+
+  private boolean computeDisallowAccessModification(Set<ProguardKeepRuleBase> rules) {
+    for (ProguardKeepRuleBase rule : rules) {
+      ProguardKeepRuleModifiers modifiers =
+          (rule.isProguardIfRule() ? rule.asProguardIfRule().getSubsequentRule() : rule)
+              .getModifiers();
+      if (!modifiers.allowsAccessModification) {
+        return true;
       }
-      allowAccessModification.put(reference, allowAccessModificationOfReference);
     }
-    return pinnedItems.add(reference);
-  }
-
-  public boolean isMissing(DexType type) {
-    return missingTypes.contains(type);
+    return false;
   }
 
   private static class SyntheticAdditions {
@@ -2674,15 +2717,16 @@
 
     Map<DexType, DexClasspathClass> syntheticClasspathClasses = new IdentityHashMap<>();
 
-    // Subset of live methods that need to be pinned.
-    Set<DexMethod> pinnedMethods = Sets.newIdentityHashSet();
+    // Subset of live methods that need have keep requirements.
+    List<Pair<ProgramMethod, Consumer<KeepMethodInfo.Joiner>>> liveMethodsWithKeepActions =
+        new ArrayList<>();
 
     // Subset of synthesized classes that need to be added to the main-dex file.
     Set<DexType> mainDexTypes = Sets.newIdentityHashSet();
 
     boolean isEmpty() {
       boolean empty = syntheticInstantiations.isEmpty() && liveMethods.isEmpty();
-      assert !empty || (pinnedMethods.isEmpty() && mainDexTypes.isEmpty());
+      assert !empty || (liveMethodsWithKeepActions.isEmpty() && mainDexTypes.isEmpty());
       return empty;
     }
 
@@ -2706,9 +2750,10 @@
       liveMethods.put(signature, method);
     }
 
-    void addLiveAndPinnedMethod(ProgramMethod method) {
+    void addLiveMethodWithKeepAction(
+        ProgramMethod method, Consumer<KeepMethodInfo.Joiner> keepAction) {
       addLiveMethod(method);
-      pinnedMethods.add(method.getDefinition().method);
+      liveMethodsWithKeepActions.add(new Pair<>(method, keepAction));
     }
 
     void amendApplication(Builder appBuilder) {
@@ -2727,7 +2772,8 @@
       // All synthetic additions are initial tree shaking only. No need to track keep reasons.
       KeepReasonWitness fakeReason = enqueuer.graphReporter.fakeReportShouldNotBeUsed();
 
-      pinnedMethods.forEach(enqueuer::addPinnedItem);
+      liveMethodsWithKeepActions.forEach(
+          item -> enqueuer.keepInfo.joinMethod(item.getFirst(), item.getSecond()));
       for (Pair<DexProgramClass, ProgramMethod> clazzAndContext :
           syntheticInstantiations.values()) {
         enqueuer.workList.enqueueMarkInstantiatedAction(
@@ -2777,7 +2823,7 @@
       DexProgramClass holder = bridge.getHolder();
       DexEncodedMethod method = bridge.getDefinition();
       holder.addVirtualMethod(method);
-      additions.addLiveAndPinnedMethod(bridge);
+      additions.addLiveMethodWithKeepAction(bridge, KeepMethodInfo.Joiner::pin);
     }
     syntheticInterfaceMethodBridges.clear();
   }
@@ -2842,11 +2888,12 @@
     // to a static method will invalidate the reachable method sets for tracing methods.
     ensureLambdaAccessibility();
 
-    // Remove the items from `allowAccessModification` that we are not allowed to publicize.
-    allowAccessModification.entrySet().removeIf(entry -> !entry.getValue().isTrue());
-
     // Compute the set of dead proto types.
     deadProtoTypeCandidates.removeIf(this::isTypeLive);
+    Set<DexType> deadProtoTypes =
+        SetUtils.newIdentityHashSet(deadProtoTypeCandidates.size() + initialDeadProtoTypes.size());
+    deadProtoTypeCandidates.forEach(deadProtoType -> deadProtoTypes.add(deadProtoType.type));
+    deadProtoTypes.addAll(initialDeadProtoTypes);
 
     // Remove the temporary mappings that have been inserted into the field access info collection
     // and verify that the mapping is then one-to-one.
@@ -2891,7 +2938,7 @@
     AppInfoWithLiveness appInfoWithLiveness =
         new AppInfoWithLiveness(
             app,
-            SetUtils.mapIdentityHashSet(deadProtoTypeCandidates, DexProgramClass::getType),
+            deadProtoTypes,
             mode.isFinalTreeShaking()
                 ? Sets.union(initialMissingTypes, missingTypes)
                 : missingTypes,
@@ -2914,8 +2961,7 @@
             toImmutableSortedMap(directInvokes, PresortedComparable::slowCompare),
             toImmutableSortedMap(staticInvokes, PresortedComparable::slowCompare),
             callSites,
-            pinnedItems,
-            allowAccessModification.keySet(),
+            keepInfo,
             rootSet.mayHaveSideEffects,
             rootSet.noSideEffects,
             rootSet.assumedValues,
@@ -3258,9 +3304,9 @@
         });
     consequentRootSet.forEachMemberWithDependentItems(
         appView,
-        member -> {
+        (member, dependentItems) -> {
           if (isMemberLive(member)) {
-            enqueueRootItems(consequentRootSet.getDependentItems(member));
+            enqueueRootItems(dependentItems);
           }
         });
     // TODO(b/132600955): This modifies the root set. Should the consequent be persistent?
@@ -3295,7 +3341,7 @@
     ProgramMethod methodToKeep = action.getMethodToKeep();
     ProgramMethod singleTarget = action.getSingleTarget();
     DexEncodedMethod singleTargetMethod = singleTarget.getDefinition();
-    if (rootSet.noShrinking.containsKey(singleTargetMethod.method)) {
+    if (rootSet.noShrinking.containsMethod(singleTarget.getReference())) {
       return;
     }
     if (methodToKeep != singleTarget) {
@@ -3319,11 +3365,12 @@
     action.getAction().accept(builder);
   }
 
+  // TODO(b/157700141): Determine if this is the right way to avoid modification of pinned lambdas.
   private void unpinLambdaMethods() {
     assert desugaredLambdaImplementationMethods.isEmpty()
         || options.desugarState == DesugarState.ON;
     for (DexMethod method : desugaredLambdaImplementationMethods) {
-      pinnedItems.remove(method);
+      keepInfo.unsafeUnpinMethod(method);
       rootSet.prune(method);
     }
     desugaredLambdaImplementationMethods.clear();
@@ -3610,7 +3657,8 @@
         workList.enqueueMarkInstantiatedAction(
             clazz, null, InstantiationReason.REFLECTION, KeepReason.reflectiveUseIn(method));
       }
-      if (addPinnedItem(encodedField.field)) {
+      if (!keepInfo.getFieldInfo(encodedField, clazz).isPinned()) {
+        keepInfo.pinField(clazz, encodedField);
         markFieldAsKept(new ProgramField(clazz, encodedField), KeepReason.reflectiveUseIn(method));
       }
     } else {
@@ -3797,14 +3845,14 @@
         // Add this interface to the set of pinned items to ensure that we do not merge the
         // interface into its unique subtype, if any.
         // TODO(b/145344105): This should be superseded by the unknown interface hierarchy.
-        addPinnedItem(clazz.type);
+        keepInfo.pinClass(clazz);
         KeepReason reason = KeepReason.reflectiveUseIn(method);
         markInterfaceAsInstantiated(clazz, graphReporter.registerClass(clazz, reason));
 
         // Also pin all of its virtual methods to ensure that the devirtualizer does not perform
         // illegal rewritings of invoke-interface instructions into invoke-virtual instructions.
         for (DexEncodedMethod virtualMethod : clazz.virtualMethods()) {
-          addPinnedItem(virtualMethod.method);
+          keepInfo.pinMethod(clazz, virtualMethod);
           markVirtualMethodAsReachable(virtualMethod.method, true, null, reason);
         }
       }
@@ -4113,16 +4161,7 @@
       this.enqueuer = enqueuer;
     }
 
-    @Override
-    public DexDefinition definitionFor(DexReference reference) {
-      return enqueuer.definitionFor(reference);
-    }
-
-    @Override
-    public DexEncodedField definitionFor(DexField field) {
-      return enqueuer.definitionFor(field);
-    }
-
+    @Deprecated
     @Override
     public DexEncodedMethod definitionFor(DexMethod method) {
       return enqueuer.definitionFor(method);
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerMetadataTraceable.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerMetadataTraceable.java
new file mode 100644
index 0000000..e17e571
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerMetadataTraceable.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+
+public interface EnqueuerMetadataTraceable {
+
+  void trace(DexDefinitionSupplier definitionSupplier);
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
new file mode 100644
index 0000000..58367a9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
@@ -0,0 +1,93 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+/** Immutable keep requirements for a class. */
+public final class KeepClassInfo extends KeepInfo<KeepClassInfo.Builder, KeepClassInfo> {
+
+  // Requires all aspects of a class to be kept.
+  private static final KeepClassInfo TOP = new Builder().makeTop().build();
+
+  // Requires no aspects of a class to be kept.
+  private static final KeepClassInfo BOTTOM = new Builder().makeBottom().build();
+
+  public static KeepClassInfo top() {
+    return TOP;
+  }
+
+  public static KeepClassInfo bottom() {
+    return BOTTOM;
+  }
+
+  private KeepClassInfo(Builder builder) {
+    super(builder);
+  }
+
+  private Builder builder() {
+    return new Builder(this);
+  }
+
+  public Joiner joiner() {
+    assert !isTop();
+    return new Joiner(this);
+  }
+
+  @Override
+  public boolean isTop() {
+    return this.equals(top());
+  }
+
+  @Override
+  public boolean isBottom() {
+    return this.equals(bottom());
+  }
+
+  public static class Builder extends KeepInfo.Builder<Builder, KeepClassInfo> {
+
+    private Builder() {
+      super();
+    }
+
+    private Builder(KeepClassInfo original) {
+      super(original);
+    }
+
+    @Override
+    public KeepClassInfo getTopInfo() {
+      return TOP;
+    }
+
+    @Override
+    public KeepClassInfo getBottomInfo() {
+      return BOTTOM;
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
+    public boolean isEqualTo(KeepClassInfo other) {
+      return true;
+    }
+
+    @Override
+    public KeepClassInfo doBuild() {
+      return new KeepClassInfo(this);
+    }
+  }
+
+  public static class Joiner extends KeepInfo.Joiner<Joiner, Builder, KeepClassInfo> {
+
+    public Joiner(KeepClassInfo info) {
+      super(info.builder());
+    }
+
+    @Override
+    Joiner self() {
+      return this;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
new file mode 100644
index 0000000..6f22db4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+/** Immutable keep requirements for a field. */
+public final class KeepFieldInfo extends KeepInfo<KeepFieldInfo.Builder, KeepFieldInfo> {
+
+  // Requires all aspects of a field to be kept.
+  private static final KeepFieldInfo TOP = new Builder().makeTop().build();
+
+  // Requires no aspects of a field to be kept.
+  private static final KeepFieldInfo BOTTOM = new Builder().makeBottom().build();
+
+  public static KeepFieldInfo top() {
+    return TOP;
+  }
+
+  public static KeepFieldInfo bottom() {
+    return BOTTOM;
+  }
+
+  private KeepFieldInfo(Builder builder) {
+    super(builder);
+  }
+
+  // This builder is not private as there are known instances where it is safe to modify keep info
+  // in a non-upwards direction.
+  Builder builder() {
+    return new Builder(this);
+  }
+
+  public Joiner joiner() {
+    assert !isTop();
+    return new Joiner(this);
+  }
+
+  @Override
+  public boolean isTop() {
+    return this.equals(top());
+  }
+
+  @Override
+  public boolean isBottom() {
+    return this.equals(bottom());
+  }
+
+  public static class Builder extends KeepInfo.Builder<Builder, KeepFieldInfo> {
+
+    private Builder() {
+      super();
+    }
+
+    private Builder(KeepFieldInfo original) {
+      super(original);
+    }
+
+    @Override
+    public KeepFieldInfo getTopInfo() {
+      return TOP;
+    }
+
+    @Override
+    public KeepFieldInfo getBottomInfo() {
+      return BOTTOM;
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
+    public boolean isEqualTo(KeepFieldInfo other) {
+      return true;
+    }
+
+    @Override
+    public KeepFieldInfo doBuild() {
+      return new KeepFieldInfo(this);
+    }
+  }
+
+  public static class Joiner extends KeepInfo.Joiner<Joiner, Builder, KeepFieldInfo> {
+
+    public Joiner(KeepFieldInfo info) {
+      super(info.builder());
+    }
+
+    @Override
+    Joiner self() {
+      return this;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
new file mode 100644
index 0000000..761547a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -0,0 +1,194 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.shaking.KeepInfo.Builder;
+import java.util.function.Supplier;
+
+/** Keep information that can be associated with any item, i.e., class, method or field. */
+public abstract class KeepInfo<B extends Builder, K extends KeepInfo> {
+
+  private final boolean pinned;
+  private final boolean allowAccessModification;
+
+  private KeepInfo(boolean pinned, boolean allowAccessModification) {
+    this.pinned = pinned;
+    this.allowAccessModification = allowAccessModification;
+  }
+
+  KeepInfo(B builder) {
+    this(builder.isPinned(), builder.isAccessModificationAllowed());
+  }
+
+  /** True if an item must be present in the output. */
+  public boolean isPinned() {
+    return pinned;
+  }
+
+  /**
+   * True if an item may have its access flags modified.
+   *
+   * <p>This method requires knowledge of the global access modification as that will override the
+   * concrete value on a given item.
+   *
+   * @param configuration Global configuration object to determine access modification.
+   */
+  public boolean isAccessModificationAllowed(ProguardConfiguration configuration) {
+    return configuration.isAccessModificationAllowed() && internalIsAccessModificationAllowed();
+  }
+
+  // Internal accessor for the items access-modification bit.
+  boolean internalIsAccessModificationAllowed() {
+    return allowAccessModification;
+  }
+
+  public abstract boolean isTop();
+
+  public abstract boolean isBottom();
+
+  public boolean isLessThanOrEquals(K other) {
+    // An item is less, aka, lower in the lattice, if each of its attributes is at least as
+    // permissive of that on other.
+    return (!pinned || other.isPinned())
+        && (allowAccessModification || !other.internalIsAccessModificationAllowed());
+  }
+
+  /** Builder to construct an arbitrary keep info object. */
+  public abstract static class Builder<B extends Builder, K extends KeepInfo> {
+
+    abstract B self();
+
+    abstract K doBuild();
+
+    abstract K getTopInfo();
+
+    abstract K getBottomInfo();
+
+    abstract boolean isEqualTo(K other);
+
+    private K original;
+    private boolean pinned;
+    private boolean allowAccessModification;
+
+    Builder() {
+      // Default initialized. Use should be followed by makeTop/makeBottom.
+    }
+
+    Builder(K original) {
+      this.original = original;
+      pinned = original.isPinned();
+      allowAccessModification = original.internalIsAccessModificationAllowed();
+    }
+
+    B makeTop() {
+      pin();
+      disallowAccessModification();
+      return self();
+    }
+
+    B makeBottom() {
+      unpin();
+      allowAccessModification();
+      return self();
+    }
+
+    public K build() {
+      if (original != null) {
+        if (internalIsEqualTo(original)) {
+          return original;
+        }
+        if (internalIsEqualTo(getTopInfo())) {
+          return getTopInfo();
+        }
+        if (internalIsEqualTo(getBottomInfo())) {
+          return getBottomInfo();
+        }
+      }
+      return doBuild();
+    }
+
+    private boolean internalIsEqualTo(K other) {
+      return isPinned() == other.isPinned()
+          && isAccessModificationAllowed() == other.internalIsAccessModificationAllowed()
+          && isEqualTo(other);
+    }
+
+    public boolean isPinned() {
+      return pinned;
+    }
+
+    public boolean isAccessModificationAllowed() {
+      return allowAccessModification;
+    }
+
+    public B setPinned(boolean pinned) {
+      this.pinned = pinned;
+      return self();
+    }
+
+    public B pin() {
+      return setPinned(true);
+    }
+
+    public B unpin() {
+      return setPinned(false);
+    }
+
+    public B setAllowAccessModification(boolean allowAccessModification) {
+      this.allowAccessModification = allowAccessModification;
+      return self();
+    }
+
+    public B allowAccessModification() {
+      return setAllowAccessModification(true);
+    }
+
+    public B disallowAccessModification() {
+      return setAllowAccessModification(false);
+    }
+  }
+
+  /** Joiner to construct monotonically increasing keep info object. */
+  public abstract static class Joiner<
+      J extends Joiner, B extends Builder, K extends KeepInfo<B, K>> {
+
+    abstract J self();
+
+    private final Builder<B, K> builder;
+
+    Joiner(Builder<B, K> builder) {
+      this.builder = builder;
+    }
+
+    public boolean isTop() {
+      return builder.isEqualTo(builder.getTopInfo());
+    }
+
+    public J top() {
+      builder.makeTop();
+      return self();
+    }
+
+    public J pin() {
+      builder.pin();
+      return self();
+    }
+
+    // Lazy modification of access modification.
+    // Only forced if access modification is still allowed.
+    public J lazyDisallowAccessModification(Supplier<Boolean> lazyShouldDisallow) {
+      if (builder.isAccessModificationAllowed() && lazyShouldDisallow.get()) {
+        builder.disallowAccessModification();
+      }
+      return self();
+    }
+
+    public K join() {
+      K joined = builder.build();
+      K original = builder.original;
+      assert original.isLessThanOrEquals(joined);
+      return joined;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
new file mode 100644
index 0000000..37c8771
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -0,0 +1,368 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMember;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.KeepFieldInfo.Joiner;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+// Non-mutable collection of keep information pertaining to a program.
+public abstract class KeepInfoCollection {
+
+  // TODO(b/157538235): This should not be bottom.
+  private static KeepClassInfo keepInfoForNonProgramClass() {
+    return KeepClassInfo.bottom();
+  }
+
+  // TODO(b/157538235): This should not be bottom.
+  private static KeepMethodInfo keepInfoForNonProgramMethod() {
+    return KeepMethodInfo.bottom();
+  }
+
+  // TODO(b/157538235): This should not be bottom.
+  private static KeepFieldInfo keepInfoForNonProgramField() {
+    return KeepFieldInfo.bottom();
+  }
+
+  /**
+   * Base accessor for keep info on a class.
+   *
+   * <p>Access may never be granted directly on DexType as the "keep info" for any non-program type
+   * is not the same as the default keep info for a program type. By typing the interface at program
+   * item we can eliminate errors where a reference to a non-program item results in optimizations
+   * assuming aspects of it can be changed when in fact they can not.
+   */
+  public abstract KeepClassInfo getClassInfo(DexProgramClass clazz);
+
+  /**
+   * Base accessor for keep info on a method.
+   *
+   * <p>See comment on class access for why this is typed at program method.
+   */
+  public abstract KeepMethodInfo getMethodInfo(DexEncodedMethod method, DexProgramClass holder);
+
+  /**
+   * Base accessor for keep info on a field.
+   *
+   * <p>See comment on class access for why this is typed at program field.
+   */
+  public abstract KeepFieldInfo getFieldInfo(DexEncodedField field, DexProgramClass holder);
+
+  public final KeepClassInfo getClassInfo(DexType type, DexDefinitionSupplier definitions) {
+    DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(type));
+    return clazz == null ? keepInfoForNonProgramClass() : getClassInfo(clazz);
+  }
+
+  public final KeepMethodInfo getMethodInfo(ProgramMethod method) {
+    return getMethodInfo(method.getDefinition(), method.getHolder());
+  }
+
+  public final KeepMethodInfo getMethodInfo(DexMethod method, DexDefinitionSupplier definitions) {
+    DexProgramClass holder = asProgramClassOrNull(definitions.definitionFor(method.holder));
+    if (holder == null) {
+      return keepInfoForNonProgramMethod();
+    }
+    DexEncodedMethod definition = holder.lookupMethod(method);
+    return definition == null ? KeepMethodInfo.bottom() : getMethodInfo(definition, holder);
+  }
+
+  public final KeepFieldInfo getFieldInfo(ProgramField field) {
+    return getFieldInfo(field.getDefinition(), field.getHolder());
+  }
+
+  public final KeepFieldInfo getFieldInfo(DexField field, DexDefinitionSupplier definitions) {
+    DexProgramClass holder = asProgramClassOrNull(definitions.definitionFor(field.holder));
+    if (holder == null) {
+      return keepInfoForNonProgramField();
+    }
+    DexEncodedField definition = holder.lookupField(field);
+    return definition == null ? KeepFieldInfo.bottom() : getFieldInfo(definition, holder);
+  }
+
+  public final KeepInfo getInfo(DexReference reference, DexDefinitionSupplier definitions) {
+    if (reference.isDexType()) {
+      return getClassInfo(reference.asDexType(), definitions);
+    }
+    if (reference.isDexMethod()) {
+      return getMethodInfo(reference.asDexMethod(), definitions);
+    }
+    if (reference.isDexField()) {
+      return getFieldInfo(reference.asDexField(), definitions);
+    }
+    throw new Unreachable();
+  }
+
+  public final boolean isPinned(DexReference reference, DexDefinitionSupplier definitions) {
+    return getInfo(reference, definitions).isPinned();
+  }
+
+  public final boolean isPinned(DexType type, DexDefinitionSupplier definitions) {
+    return getClassInfo(type, definitions).isPinned();
+  }
+
+  public final boolean isPinned(DexMethod method, DexDefinitionSupplier definitions) {
+    return getMethodInfo(method, definitions).isPinned();
+  }
+
+  public final boolean isPinned(DexField field, DexDefinitionSupplier definitions) {
+    return getFieldInfo(field, definitions).isPinned();
+  }
+
+  public final boolean verifyNoneArePinned(Collection<DexType> types, AppInfo appInfo) {
+    for (DexType type : types) {
+      DexProgramClass clazz =
+          asProgramClassOrNull(appInfo.definitionForWithoutExistenceAssert(type));
+      assert clazz == null || !getClassInfo(clazz).isPinned();
+    }
+    return true;
+  }
+
+  // TODO(b/156715504): We should try to avoid the need for iterating pinned items.
+  @Deprecated
+  public abstract void forEachPinnedType(Consumer<DexType> consumer);
+
+  // TODO(b/156715504): We should try to avoid the need for iterating pinned items.
+  @Deprecated
+  public abstract void forEachPinnedMethod(Consumer<DexMethod> consumer);
+
+  // TODO(b/156715504): We should try to avoid the need for iterating pinned items.
+  @Deprecated
+  public abstract void forEachPinnedField(Consumer<DexField> consumer);
+
+  public abstract KeepInfoCollection rewrite(NestedGraphLense lens);
+
+  public abstract KeepInfoCollection mutate(Consumer<MutableKeepInfoCollection> mutator);
+
+  // Mutation interface for building up the keep info.
+  public static class MutableKeepInfoCollection extends KeepInfoCollection {
+
+    // These are typed at signatures but the interface should make sure never to allow access
+    // directly with a signature. See the comment in KeepInfoCollection.
+    private final Map<DexType, KeepClassInfo> keepClassInfo;
+    private final Map<DexMethod, KeepMethodInfo> keepMethodInfo;
+    private final Map<DexField, KeepFieldInfo> keepFieldInfo;
+
+    MutableKeepInfoCollection() {
+      this(new IdentityHashMap<>(), new IdentityHashMap<>(), new IdentityHashMap<>());
+    }
+
+    private MutableKeepInfoCollection(
+        Map<DexType, KeepClassInfo> keepClassInfo,
+        Map<DexMethod, KeepMethodInfo> keepMethodInfo,
+        Map<DexField, KeepFieldInfo> keepFieldInfo) {
+      this.keepClassInfo = keepClassInfo;
+      this.keepMethodInfo = keepMethodInfo;
+      this.keepFieldInfo = keepFieldInfo;
+    }
+
+    @Override
+    public KeepInfoCollection rewrite(NestedGraphLense lens) {
+      Map<DexType, KeepClassInfo> newClassInfo = new IdentityHashMap<>(keepClassInfo.size());
+      keepClassInfo.forEach(
+          (type, info) -> {
+            DexType newType = lens.lookupType(type);
+            assert !info.isPinned() || type == newType;
+            newClassInfo.put(newType, info);
+          });
+      Map<DexMethod, KeepMethodInfo> newMethodInfo = new IdentityHashMap<>(keepMethodInfo.size());
+      keepMethodInfo.forEach(
+          (method, info) -> {
+            DexMethod newMethod = lens.getRenamedMethodSignature(method);
+            assert !info.isPinned() || method == newMethod;
+            newMethodInfo.put(newMethod, info);
+          });
+      Map<DexField, KeepFieldInfo> newFieldInfo = new IdentityHashMap<>(keepFieldInfo.size());
+      keepFieldInfo.forEach(
+          (field, info) -> {
+            DexField newField = lens.getRenamedFieldSignature(field);
+            assert !info.isPinned() || field == newField;
+            newFieldInfo.put(newField, info);
+          });
+      return new MutableKeepInfoCollection(newClassInfo, newMethodInfo, newFieldInfo);
+    }
+
+    @Override
+    public KeepClassInfo getClassInfo(DexProgramClass clazz) {
+      return keepClassInfo.getOrDefault(clazz.type, KeepClassInfo.bottom());
+    }
+
+    @Override
+    public KeepMethodInfo getMethodInfo(DexEncodedMethod method, DexProgramClass holder) {
+      assert method.holder() == holder.type;
+      return keepMethodInfo.getOrDefault(method.method, KeepMethodInfo.bottom());
+    }
+
+    @Override
+    public KeepFieldInfo getFieldInfo(DexEncodedField field, DexProgramClass holder) {
+      assert field.holder() == holder.type;
+      return keepFieldInfo.getOrDefault(field.field, KeepFieldInfo.bottom());
+    }
+
+    public void joinClass(DexProgramClass clazz, Consumer<KeepClassInfo.Joiner> fn) {
+      KeepClassInfo info = getClassInfo(clazz);
+      if (info.isTop()) {
+        return;
+      }
+      KeepClassInfo.Joiner joiner = info.joiner();
+      fn.accept(joiner);
+      KeepClassInfo joined = joiner.join();
+      if (!info.equals(joined)) {
+        keepClassInfo.put(clazz.type, joined);
+      }
+    }
+
+    public void keepClass(DexProgramClass clazz) {
+      joinClass(clazz, KeepInfo.Joiner::top);
+    }
+
+    public void pinClass(DexProgramClass clazz) {
+      joinClass(clazz, KeepInfo.Joiner::pin);
+    }
+
+    public void joinMethod(
+        DexProgramClass holder, DexEncodedMethod method, Consumer<KeepMethodInfo.Joiner> fn) {
+      KeepMethodInfo info = getMethodInfo(method, holder);
+      if (info == KeepMethodInfo.top()) {
+        return;
+      }
+      KeepMethodInfo.Joiner joiner = info.joiner();
+      fn.accept(joiner);
+      KeepMethodInfo joined = joiner.join();
+      if (!info.equals(joined)) {
+        keepMethodInfo.put(method.method, joined);
+      }
+    }
+
+    public void joinMethod(ProgramMethod programMethod, Consumer<KeepMethodInfo.Joiner> fn) {
+      joinMethod(programMethod.getHolder(), programMethod.getDefinition(), fn);
+    }
+
+    public void keepMethod(ProgramMethod programMethod) {
+      keepMethod(programMethod.getHolder(), programMethod.getDefinition());
+    }
+
+    public void keepMethod(DexProgramClass holder, DexEncodedMethod method) {
+      joinMethod(holder, method, KeepInfo.Joiner::top);
+    }
+
+    public void pinMethod(DexProgramClass holder, DexEncodedMethod method) {
+      joinMethod(holder, method, KeepInfo.Joiner::pin);
+    }
+
+    // Unpinning a method represents a non-monotonic change to the keep info of that item.
+    // This is generally unsound as it requires additional analysis to determine that a method that
+    // was pinned no longer is. A known sound example is the enum analysis that will identify
+    // non-escaping enums on enum types that are not pinned, thus their methods do not need to be
+    // retained even if a rule has marked them as conditionally pinned.
+    public void unsafeUnpinMethod(ProgramMethod method) {
+      // This asserts that the holder is not pinned as some analysis must have established that the
+      // type is not "present" and thus the method need not be pinned.
+      assert !getClassInfo(method.getHolder()).isPinned();
+      unsafeUnpinMethod(method.getReference());
+    }
+
+    // TODO(b/157700141): Avoid pinning/unpinning references.
+    @Deprecated
+    public void unsafeUnpinMethod(DexMethod method) {
+      KeepMethodInfo info = keepMethodInfo.get(method);
+      if (info != null && info.isPinned()) {
+        keepMethodInfo.put(method, info.builder().unpin().build());
+      }
+    }
+
+    public void joinField(
+        DexProgramClass holder, DexEncodedField field, Consumer<KeepFieldInfo.Joiner> fn) {
+      KeepFieldInfo info = getFieldInfo(field, holder);
+      if (info.isTop()) {
+        return;
+      }
+      Joiner joiner = info.joiner();
+      fn.accept(joiner);
+      KeepFieldInfo joined = joiner.join();
+      if (!info.equals(joined)) {
+        keepFieldInfo.put(field.field, joined);
+      }
+    }
+
+    public void keepField(DexProgramClass holder, DexEncodedField field) {
+      joinField(holder, field, KeepInfo.Joiner::top);
+    }
+
+    public void pinField(DexProgramClass holder, DexEncodedField field) {
+      joinField(holder, field, KeepInfo.Joiner::pin);
+    }
+
+    public void keepMember(DexProgramClass holder, DexEncodedMember<?, ?> member) {
+      if (member.isDexEncodedMethod()) {
+        keepMethod(holder, member.asDexEncodedMethod());
+      } else {
+        assert member.isDexEncodedField();
+        keepField(holder, member.asDexEncodedField());
+      }
+    }
+
+    public void unsafeUnpinField(DexProgramClass holder, DexEncodedField field) {
+      assert holder.type == field.holder();
+      assert !getClassInfo(holder).isPinned();
+      KeepFieldInfo info = this.keepFieldInfo.get(field.toReference());
+      if (info != null && info.isPinned()) {
+        keepFieldInfo.put(field.toReference(), info.builder().unpin().build());
+      }
+    }
+
+    @Override
+    public KeepInfoCollection mutate(Consumer<MutableKeepInfoCollection> mutator) {
+      mutator.accept(this);
+      return this;
+    }
+
+    @Override
+    public void forEachPinnedType(Consumer<DexType> consumer) {
+      keepClassInfo.forEach(
+          (type, info) -> {
+            if (info.isPinned()) {
+              consumer.accept(type);
+            }
+          });
+    }
+
+    @Override
+    public void forEachPinnedMethod(Consumer<DexMethod> consumer) {
+      keepMethodInfo.forEach(
+          (method, info) -> {
+            if (info.isPinned()) {
+              consumer.accept(method);
+            }
+          });
+    }
+
+    @Override
+    public void forEachPinnedField(Consumer<DexField> consumer) {
+      keepFieldInfo.forEach(
+          (field, info) -> {
+            if (info.isPinned()) {
+              consumer.accept(field);
+            }
+          });
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
new file mode 100644
index 0000000..98eae1b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+/** Immutable keep requirements for a method. */
+public final class KeepMethodInfo extends KeepInfo<KeepMethodInfo.Builder, KeepMethodInfo> {
+
+  // Requires all aspects of a method to be kept.
+  private static final KeepMethodInfo TOP = new Builder().makeTop().build();
+
+  // Requires no aspects of a method to be kept.
+  private static final KeepMethodInfo BOTTOM = new Builder().makeBottom().build();
+
+  public static KeepMethodInfo top() {
+    return TOP;
+  }
+
+  public static KeepMethodInfo bottom() {
+    return BOTTOM;
+  }
+
+  private KeepMethodInfo(Builder builder) {
+    super(builder);
+  }
+
+  // This builder is not private as there are known instances where it is safe to modify keep info
+  // in a non-upwards direction.
+  Builder builder() {
+    return new Builder(this);
+  }
+
+  public Joiner joiner() {
+    assert !isTop();
+    return new Joiner(this);
+  }
+
+  @Override
+  public boolean isTop() {
+    return this.equals(top());
+  }
+
+  @Override
+  public boolean isBottom() {
+    return this.equals(bottom());
+  }
+
+  public static class Builder extends KeepInfo.Builder<Builder, KeepMethodInfo> {
+
+    private Builder() {
+      super();
+    }
+
+    private Builder(KeepMethodInfo original) {
+      super(original);
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
+    public KeepMethodInfo getTopInfo() {
+      return TOP;
+    }
+
+    @Override
+    public KeepMethodInfo getBottomInfo() {
+      return BOTTOM;
+    }
+
+    @Override
+    public boolean isEqualTo(KeepMethodInfo other) {
+      return true;
+    }
+
+    @Override
+    public KeepMethodInfo doBuild() {
+      return new KeepMethodInfo(this);
+    }
+  }
+
+  public static class Joiner extends KeepInfo.Joiner<Joiner, Builder, KeepMethodInfo> {
+
+    public Joiner(KeepMethodInfo info) {
+      super(info.builder());
+    }
+
+    @Override
+    Joiner self() {
+      return this;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java b/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java
index 0979ef9..c6a1725 100644
--- a/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java
+++ b/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
@@ -50,8 +49,10 @@
         getClassesWithLibraryMethodOverrides(appView);
 
     // Remove all types that are pinned from the initial set of non-escaping classes.
-    DexReference.filterDexType(appView.appInfo().pinnedItems.stream())
-        .forEach(initialNonEscapingClassesWithLibraryMethodOverrides::remove);
+    appView
+        .appInfo()
+        .getKeepInfo()
+        .forEachPinnedType(initialNonEscapingClassesWithLibraryMethodOverrides::remove);
 
     return initialNonEscapingClassesWithLibraryMethodOverrides;
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index ffb940a..9abf328 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -40,6 +40,7 @@
 import com.android.tools.r8.utils.Consumer3;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.OriginWithPosition;
 import com.android.tools.r8.utils.PredicateSet;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -55,7 +56,6 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -71,6 +71,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -81,7 +82,7 @@
   private final SubtypingInfo subtypingInfo;
   private final DirectMappedDexApplication application;
   private final Iterable<? extends ProguardConfigurationRule> rules;
-  private final Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking = new IdentityHashMap<>();
+  private final MutableItemsWithRules noShrinking = new MutableItemsWithRules();
   private final Set<DexReference> noObfuscation = Sets.newIdentityHashSet();
   private final LinkedHashMap<DexReference, DexReference> reasonAsked = new LinkedHashMap<>();
   private final LinkedHashMap<DexReference, DexReference> checkDiscarded = new LinkedHashMap<>();
@@ -98,8 +99,8 @@
   private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
   private final Set<DexType> neverMerge = Sets.newIdentityHashSet();
   private final Set<DexReference> neverPropagateValue = Sets.newIdentityHashSet();
-  private final Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>>
-      dependentNoShrinking = new IdentityHashMap<>();
+  private final Map<DexReference, MutableItemsWithRules> dependentNoShrinking =
+      new IdentityHashMap<>();
   private final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule =
       new IdentityHashMap<>();
   private final Map<DexReference, ProguardMemberRule> mayHaveSideEffects = new IdentityHashMap<>();
@@ -113,6 +114,9 @@
   private final DexStringCache dexStringCache = new DexStringCache();
   private final Set<ProguardIfRule> ifRules = Sets.newIdentityHashSet();
 
+  private final Map<OriginWithPosition, List<DexMethod>> assumeNoSideEffectsWarnings =
+      new HashMap<>();
+
   public RootSetBuilder(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       SubtypingInfo subtypingInfo,
@@ -304,6 +308,7 @@
     } finally {
       application.timing.end();
     }
+    generateAssumeNoSideEffectsWarnings();
     if (!noSideEffects.isEmpty() || !assumedValues.isEmpty()) {
       BottomUpClassHierarchyTraversal.forAllClasses(appView, subtypingInfo)
           .visit(appView.appInfo().classes(), this::propagateAssumeRules);
@@ -689,53 +694,62 @@
   // TODO(b/67934426): Test this code.
   public static void writeSeeds(
       AppInfoWithLiveness appInfo, PrintStream out, Predicate<DexType> include) {
-    for (DexReference seed : appInfo.getPinnedItems()) {
-      if (seed.isDexType()) {
-        if (include.test(seed.asDexType())) {
-          out.println(seed.toSourceString());
-        }
-      } else if (seed.isDexField()) {
-        DexField field = seed.asDexField();
-        if (include.test(field.holder)) {
-          out.println(
-              field.holder.toSourceString()
-                  + ": "
-                  + field.type.toSourceString()
-                  + " "
-                  + field.name.toSourceString());
-        }
-      } else {
-        assert seed.isDexMethod();
-        DexMethod method = seed.asDexMethod();
-        if (!include.test(method.holder)) {
-          continue;
-        }
-        out.print(method.holder.toSourceString() + ": ");
-        DexEncodedMethod encodedMethod = appInfo.definitionFor(method);
-        if (encodedMethod.accessFlags.isConstructor()) {
-          if (encodedMethod.accessFlags.isStatic()) {
-            out.print(Constants.CLASS_INITIALIZER_NAME);
-          } else {
-            String holderName = method.holder.toSourceString();
-            String constrName = holderName.substring(holderName.lastIndexOf('.') + 1);
-            out.print(constrName);
-          }
-        } else {
-          out.print(
-              method.proto.returnType.toSourceString() + " " + method.name.toSourceString());
-        }
-        boolean first = true;
-        out.print("(");
-        for (DexType param : method.proto.parameters.values) {
-          if (!first) {
-            out.print(",");
-          }
-          first = false;
-          out.print(param.toSourceString());
-        }
-        out.println(")");
-      }
-    }
+    appInfo
+        .getKeepInfo()
+        .forEachPinnedType(
+            type -> {
+              if (include.test(type)) {
+                out.println(type.toSourceString());
+              }
+            });
+    appInfo
+        .getKeepInfo()
+        .forEachPinnedField(
+            field -> {
+              if (include.test(field.holder)) {
+                out.println(
+                    field.holder.toSourceString()
+                        + ": "
+                        + field.type.toSourceString()
+                        + " "
+                        + field.name.toSourceString());
+              }
+            });
+    appInfo
+        .getKeepInfo()
+        .forEachPinnedMethod(
+            method -> {
+              if (!include.test(method.holder)) {
+                return;
+              }
+              DexProgramClass holder = asProgramClassOrNull(appInfo.definitionForHolder(method));
+              DexEncodedMethod definition = method.lookupOnClass(holder);
+              if (definition == null) {
+                assert false;
+                return;
+              }
+              out.print(method.holder.toSourceString() + ": ");
+              if (definition.isClassInitializer()) {
+                out.print(Constants.CLASS_INITIALIZER_NAME);
+              } else if (definition.isInstanceInitializer()) {
+                String holderName = method.holder.toSourceString();
+                String constrName = holderName.substring(holderName.lastIndexOf('.') + 1);
+                out.print(constrName);
+              } else {
+                out.print(
+                    method.proto.returnType.toSourceString() + " " + method.name.toSourceString());
+              }
+              boolean first = true;
+              out.print("(");
+              for (DexType param : method.proto.parameters.values) {
+                if (!first) {
+                  out.print(",");
+                }
+                first = false;
+                out.print(param.toSourceString());
+              }
+              out.println(")");
+            });
     out.close();
   }
 
@@ -1002,9 +1016,8 @@
     }
     // Keep the type if the item is also kept.
     dependentNoShrinking
-        .computeIfAbsent(item.toReference(), x -> new IdentityHashMap<>())
-        .computeIfAbsent(type, k -> new HashSet<>())
-        .add(context);
+        .computeIfAbsent(item.toReference(), x -> new MutableItemsWithRules())
+        .addClassWithRule(type, context);
     // Unconditionally add to no-obfuscation, as that is only checked for surviving items.
     noObfuscation.add(type);
   }
@@ -1091,11 +1104,10 @@
       if (!modifiers.allowsShrinking) {
         if (precondition != null) {
           dependentNoShrinking
-              .computeIfAbsent(precondition.toReference(), x -> new IdentityHashMap<>())
-              .computeIfAbsent(item.toReference(), i -> new HashSet<>())
-              .add(keepRule);
+              .computeIfAbsent(precondition.toReference(), x -> new MutableItemsWithRules())
+              .addReferenceWithRule(item.toReference(), keepRule);
         } else {
-          noShrinking.computeIfAbsent(item.toReference(), i -> new HashSet<>()).add(keepRule);
+          noShrinking.addReferenceWithRule(item.toReference(), keepRule);
         }
         context.markAsUsed();
       }
@@ -1117,6 +1129,7 @@
       mayHaveSideEffects.put(item.toReference(), rule);
       context.markAsUsed();
     } else if (context instanceof ProguardAssumeNoSideEffectRule) {
+      checkAssumeNoSideEffectsWarnings(item, (ProguardAssumeNoSideEffectRule) context, rule);
       noSideEffects.put(item.toReference(), rule);
       context.markAsUsed();
     } else if (context instanceof ProguardWhyAreYouKeepingRule) {
@@ -1263,18 +1276,18 @@
 
     final Set<DexMethod> neverInline;
     final Set<DexType> neverClassInline;
-    final Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking;
+    final MutableItemsWithRules noShrinking;
     final Set<DexReference> noObfuscation;
-    final Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking;
+    final Map<DexReference, MutableItemsWithRules> dependentNoShrinking;
     final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
     final List<DelayedRootSetActionItem> delayedRootSetActionItems;
 
     RootSetBase(
         Set<DexMethod> neverInline,
         Set<DexType> neverClassInline,
-        Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking,
+        MutableItemsWithRules noShrinking,
         Set<DexReference> noObfuscation,
-        Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking,
+        Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
         List<DelayedRootSetActionItem> delayedRootSetActionItems) {
       this.neverInline = neverInline;
@@ -1286,10 +1299,6 @@
       this.delayedRootSetActionItems = delayedRootSetActionItems;
     }
 
-    public boolean noShrinking(DexReference reference) {
-      return noShrinking.containsKey(reference);
-    }
-
     public void forEachClassWithDependentItems(
         DexDefinitionSupplier definitions, Consumer<DexProgramClass> consumer) {
       for (DexReference reference : dependentNoShrinking.keySet()) {
@@ -1304,33 +1313,55 @@
     }
 
     public void forEachMemberWithDependentItems(
-        DexDefinitionSupplier definitions, Consumer<DexEncodedMember<?, ?>> consumer) {
-      for (DexReference reference : dependentNoShrinking.keySet()) {
-        if (reference.isDexMember()) {
-          DexEncodedMember<?, ?> definition = definitions.definitionFor(reference.asDexMember());
-          if (definition != null) {
-            consumer.accept(definition);
-          }
-        }
-      }
+        DexDefinitionSupplier definitions,
+        BiConsumer<DexEncodedMember<?, ?>, ItemsWithRules> consumer) {
+      dependentNoShrinking.forEach(
+          (reference, dependentItems) -> {
+            if (reference.isDexMember()) {
+              DexMember<?, ?> member = reference.asDexMember();
+              DexProgramClass holder =
+                  asProgramClassOrNull(definitions.definitionForHolder(member));
+              if (holder != null) {
+                DexEncodedMember<?, ?> definition = holder.lookupMember(member);
+                if (definition != null) {
+                  consumer.accept(definition, dependentItems);
+                }
+              }
+            }
+          });
     }
 
     public void forEachDependentInstanceConstructor(
         DexProgramClass clazz,
         AppView<?> appView,
-        Consumer3<DexProgramClass, ProgramMethod, Set<ProguardKeepRuleBase>> fn) {
+        BiConsumer<ProgramMethod, Set<ProguardKeepRuleBase>> fn) {
       getDependentItems(clazz)
-          .forEach(
+          .forEachMethod(
               (reference, reasons) -> {
-                if (reference.isDexMethod()) {
-                  DexMethod methodReference = reference.asDexMethod();
-                  DexProgramClass holder =
-                      asProgramClassOrNull(appView.definitionForHolder(methodReference));
-                  if (holder != null) {
-                    ProgramMethod method = holder.lookupProgramMethod(methodReference);
-                    if (method != null && method.getDefinition().isInstanceInitializer()) {
-                      fn.accept(clazz, method, reasons);
-                    }
+                DexProgramClass holder =
+                    asProgramClassOrNull(appView.definitionForHolder(reference));
+                if (holder != null) {
+                  ProgramMethod method = holder.lookupProgramMethod(reference);
+                  if (method != null && method.getDefinition().isInstanceInitializer()) {
+                    fn.accept(method, reasons);
+                  }
+                }
+              });
+    }
+
+    public void forEachDependentMember(
+        DexDefinition item,
+        AppView<?> appView,
+        Consumer3<DexDefinition, DexDefinition, Set<ProguardKeepRuleBase>> fn) {
+      getDependentItems(item)
+          .forEachMember(
+              (reference, reasons) -> {
+                DexProgramClass holder =
+                    asProgramClassOrNull(appView.definitionForHolder(reference));
+                if (holder != null) {
+                  DexEncodedMember<?, ?> member = holder.lookupMember(reference);
+                  if (member != null) {
+                    fn.accept(item, member, reasons);
                   }
                 }
               });
@@ -1340,35 +1371,33 @@
         DexDefinition item,
         AppView<?> appView,
         Consumer3<DexDefinition, DexDefinition, Set<ProguardKeepRuleBase>> fn) {
-      getDependentItems(item)
-          .forEach(
-              (reference, reasons) -> {
-                DexDefinition definition = appView.definitionFor(reference);
-                if (definition != null
-                    && !definition.isDexClass()
-                    && !definition.isStaticMember()) {
-                  fn.accept(item, definition, reasons);
-                }
-              });
+      forEachDependentMember(
+          item,
+          appView,
+          (precondition, member, reasons) -> {
+            if (!member.isStatic()) {
+              fn.accept(precondition, member, reasons);
+            }
+          });
     }
 
     public void forEachDependentStaticMember(
         DexDefinition item,
         AppView<?> appView,
         Consumer3<DexDefinition, DexDefinition, Set<ProguardKeepRuleBase>> fn) {
-      getDependentItems(item)
-          .forEach(
-              (reference, reasons) -> {
-                DexDefinition definition = appView.definitionFor(reference);
-                if (definition != null && !definition.isDexClass() && definition.isStaticMember()) {
-                  fn.accept(item, definition, reasons);
-                }
-              });
+      forEachDependentMember(
+          item,
+          appView,
+          (precondition, member, reasons) -> {
+            if (member.isStatic()) {
+              fn.accept(precondition, member, reasons);
+            }
+          });
     }
 
-    Map<DexReference, Set<ProguardKeepRuleBase>> getDependentItems(DexDefinition item) {
-      return Collections.unmodifiableMap(
-          dependentNoShrinking.getOrDefault(item.toReference(), Collections.emptyMap()));
+    ItemsWithRules getDependentItems(DexDefinition item) {
+      ItemsWithRules found = dependentNoShrinking.get(item.toReference());
+      return found != null ? found : ItemsWithRules.empty();
     }
 
     Set<ProguardKeepRuleBase> getDependentKeepClassCompatRule(DexType type) {
@@ -1376,6 +1405,292 @@
     }
   }
 
+  abstract static class ItemsWithRules {
+
+    public static ItemsWithRules empty() {
+      return MutableItemsWithRules.EMPTY;
+    }
+
+    public abstract boolean containsClass(DexType type);
+
+    public abstract boolean containsField(DexField field);
+
+    public abstract boolean containsMethod(DexMethod method);
+
+    public final boolean containsReference(DexReference reference) {
+      return reference.apply(this::containsClass, this::containsField, this::containsMethod);
+    }
+
+    public abstract void forEachClass(Consumer<DexType> consumer);
+
+    public abstract void forEachClass(BiConsumer<DexType, Set<ProguardKeepRuleBase>> consumer);
+
+    public abstract void forEachField(Consumer<? super DexField> consumer);
+
+    public abstract void forEachField(
+        BiConsumer<? super DexField, Set<ProguardKeepRuleBase>> consumer);
+
+    public abstract void forEachMember(Consumer<DexMember<?, ?>> consumer);
+
+    public abstract void forEachMember(
+        BiConsumer<DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer);
+
+    public abstract void forEachMethod(Consumer<? super DexMethod> consumer);
+
+    public abstract void forEachMethod(
+        BiConsumer<? super DexMethod, Set<ProguardKeepRuleBase>> consumer);
+
+    public abstract Set<ProguardKeepRuleBase> getRulesForClass(DexType type);
+
+    public abstract Set<ProguardKeepRuleBase> getRulesForField(DexField field);
+
+    public abstract Set<ProguardKeepRuleBase> getRulesForMethod(DexMethod method);
+
+    public final Set<ProguardKeepRuleBase> getRulesForReference(DexReference reference) {
+      return reference.apply(
+          this::getRulesForClass, this::getRulesForField, this::getRulesForMethod);
+    }
+  }
+
+  static class MutableItemsWithRules extends ItemsWithRules {
+
+    private static final ItemsWithRules EMPTY =
+        new MutableItemsWithRules(
+            Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap());
+
+    final Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules;
+    final Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules;
+    final Map<DexMethod, Set<ProguardKeepRuleBase>> methodsWithRules;
+
+    MutableItemsWithRules() {
+      this(new IdentityHashMap<>(), new IdentityHashMap<>(), new IdentityHashMap<>());
+    }
+
+    private MutableItemsWithRules(
+        Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules,
+        Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules,
+        Map<DexMethod, Set<ProguardKeepRuleBase>> methodsWithRules) {
+      this.classesWithRules = classesWithRules;
+      this.fieldsWithRules = fieldsWithRules;
+      this.methodsWithRules = methodsWithRules;
+    }
+
+    public void addAll(ItemsWithRules items) {
+      items.forEachClass(this::addClassWithRules);
+      items.forEachField(this::addFieldWithRules);
+      items.forEachMethod(this::addMethodWithRules);
+    }
+
+    public void addClassWithRule(DexType type, ProguardKeepRuleBase rule) {
+      classesWithRules.computeIfAbsent(type, ignore -> new HashSet<>()).add(rule);
+    }
+
+    public void addClassWithRules(DexType type, Set<ProguardKeepRuleBase> rules) {
+      classesWithRules.computeIfAbsent(type, ignore -> new HashSet<>()).addAll(rules);
+    }
+
+    public void addFieldWithRule(DexField field, ProguardKeepRuleBase rule) {
+      fieldsWithRules.computeIfAbsent(field, ignore -> new HashSet<>()).add(rule);
+    }
+
+    public void addFieldWithRules(DexField field, Set<ProguardKeepRuleBase> rules) {
+      fieldsWithRules.computeIfAbsent(field, ignore -> new HashSet<>()).addAll(rules);
+    }
+
+    public void addMethodWithRule(DexMethod method, ProguardKeepRuleBase rule) {
+      methodsWithRules.computeIfAbsent(method, ignore -> new HashSet<>()).add(rule);
+    }
+
+    public void addMethodWithRules(DexMethod method, Set<ProguardKeepRuleBase> rules) {
+      methodsWithRules.computeIfAbsent(method, ignore -> new HashSet<>()).addAll(rules);
+    }
+
+    public void addReferenceWithRule(DexReference reference, ProguardKeepRuleBase rule) {
+      reference.accept(
+          this::addClassWithRule, this::addFieldWithRule, this::addMethodWithRule, rule);
+    }
+
+    public void addReferenceWithRules(DexReference reference, Set<ProguardKeepRuleBase> rules) {
+      reference.accept(
+          this::addClassWithRules, this::addFieldWithRules, this::addMethodWithRules, rules);
+    }
+
+    @Override
+    public boolean containsClass(DexType type) {
+      return classesWithRules.containsKey(type);
+    }
+
+    @Override
+    public boolean containsField(DexField field) {
+      return fieldsWithRules.containsKey(field);
+    }
+
+    @Override
+    public boolean containsMethod(DexMethod method) {
+      return methodsWithRules.containsKey(method);
+    }
+
+    @Override
+    public void forEachClass(Consumer<DexType> consumer) {
+      classesWithRules.keySet().forEach(consumer);
+    }
+
+    @Override
+    public void forEachClass(BiConsumer<DexType, Set<ProguardKeepRuleBase>> consumer) {
+      classesWithRules.forEach(consumer);
+    }
+
+    @Override
+    public void forEachField(Consumer<? super DexField> consumer) {
+      fieldsWithRules.keySet().forEach(consumer);
+    }
+
+    @Override
+    public void forEachField(BiConsumer<? super DexField, Set<ProguardKeepRuleBase>> consumer) {
+      fieldsWithRules.forEach(consumer);
+    }
+
+    @Override
+    public void forEachMember(Consumer<DexMember<?, ?>> consumer) {
+      forEachField(consumer);
+      forEachMethod(consumer);
+    }
+
+    @Override
+    public void forEachMember(BiConsumer<DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer) {
+      forEachField(consumer);
+      forEachMethod(consumer);
+    }
+
+    @Override
+    public void forEachMethod(Consumer<? super DexMethod> consumer) {
+      methodsWithRules.keySet().forEach(consumer);
+    }
+
+    @Override
+    public void forEachMethod(BiConsumer<? super DexMethod, Set<ProguardKeepRuleBase>> consumer) {
+      methodsWithRules.forEach(consumer);
+    }
+
+    @Override
+    public Set<ProguardKeepRuleBase> getRulesForClass(DexType type) {
+      return classesWithRules.get(type);
+    }
+
+    @Override
+    public Set<ProguardKeepRuleBase> getRulesForField(DexField field) {
+      return fieldsWithRules.get(field);
+    }
+
+    @Override
+    public Set<ProguardKeepRuleBase> getRulesForMethod(DexMethod method) {
+      return methodsWithRules.get(method);
+    }
+
+    public void removeClass(DexType type) {
+      classesWithRules.remove(type);
+    }
+
+    public void removeField(DexField field) {
+      fieldsWithRules.remove(field);
+    }
+
+    public void removeMethod(DexMethod method) {
+      methodsWithRules.remove(method);
+    }
+
+    public void removeReference(DexReference reference) {
+      reference.accept(this::removeClass, this::removeField, this::removeMethod);
+    }
+
+    public void putAll(ItemsWithRules items) {
+      items.forEachClass(this::putClassWithRules);
+      items.forEachField(this::putFieldWithRules);
+      items.forEachMethod(this::putMethodWithRules);
+    }
+
+    public void putClassWithRules(DexType type, Set<ProguardKeepRuleBase> rules) {
+      classesWithRules.put(type, rules);
+    }
+
+    public void putFieldWithRules(DexField field, Set<ProguardKeepRuleBase> rules) {
+      fieldsWithRules.put(field, rules);
+    }
+
+    public void putMethodWithRules(DexMethod method, Set<ProguardKeepRuleBase> rules) {
+      methodsWithRules.put(method, rules);
+    }
+
+    public void putReferenceWithRules(DexReference reference, Set<ProguardKeepRuleBase> rules) {
+      reference.accept(
+          this::putClassWithRules, this::putFieldWithRules, this::putMethodWithRules, rules);
+    }
+
+    public int size() {
+      return classesWithRules.size() + fieldsWithRules.size() + methodsWithRules.size();
+    }
+  }
+
+  private void checkAssumeNoSideEffectsWarnings(
+      DexDefinition item, ProguardAssumeNoSideEffectRule context, ProguardMemberRule rule) {
+    if (rule.getRuleType() == ProguardMemberType.METHOD && rule.isSpecific()) {
+      return;
+    }
+    if (item.isDexEncodedMethod()) {
+      DexEncodedMethod method = item.asDexEncodedMethod();
+      if (method.holder() == options.itemFactory.objectType) {
+        OriginWithPosition key = new OriginWithPosition(context.getOrigin(), context.getPosition());
+        assumeNoSideEffectsWarnings.computeIfAbsent(key, k -> new ArrayList<>()).add(method.method);
+      }
+    }
+  }
+
+  private boolean isWaitOrNotifyMethod(DexMethod method) {
+    return method.name == options.itemFactory.waitMethodName
+        || method.name == options.itemFactory.notifyMethodName
+        || method.name == options.itemFactory.notifyAllMethodName;
+  }
+
+  private void generateAssumeNoSideEffectsWarnings() {
+    ProguardClassFilter dontWarnPatterns =
+        options.getProguardConfiguration() != null
+            ? options.getProguardConfiguration().getDontWarnPatterns()
+            : ProguardClassFilter.empty();
+    if (dontWarnPatterns.matches(options.itemFactory.objectType)) {
+      return;
+    }
+
+    assumeNoSideEffectsWarnings.forEach(
+        (originWithPosition, methods) -> {
+          boolean waitOrNotifyMethods = methods.stream().anyMatch(this::isWaitOrNotifyMethod);
+          StringBuilder message = new StringBuilder();
+          message.append(
+              "The -assumenosideeffects rule matches methods on `java.lang.Object` with wildcards");
+          if (waitOrNotifyMethods) {
+            message.append(" including the method(s) ");
+            for (int i = 0; i < methods.size(); i++) {
+              if (i > 0) {
+                message.append(i < methods.size() - 1 ? ", " : " and ");
+              }
+              message.append("`");
+              message.append(methods.get(i).toSourceStringWithoutHolder());
+              message.append("`");
+            }
+            message.append(". ");
+            message.append("This will most likely cause problems. ");
+          } else {
+            message.append(". ");
+            message.append("This is most likely not intended. ");
+          }
+          message.append("Consider specifying the methods more precisely.");
+          options.reporter.warning(
+              new StringDiagnostic(
+                  message.toString(),
+                  originWithPosition.getOrigin(),
+                  originWithPosition.getPosition()));
+        });
+  }
+
   public static class RootSet extends RootSetBase {
 
     public final ImmutableList<DexReference> reasonAsked;
@@ -1398,7 +1713,7 @@
     public final Set<ProguardIfRule> ifRules;
 
     private RootSet(
-        Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking,
+        MutableItemsWithRules noShrinking,
         Set<DexReference> noObfuscation,
         ImmutableList<DexReference> reasonAsked,
         ImmutableList<DexReference> checkDiscarded,
@@ -1418,7 +1733,7 @@
         Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
         Map<DexReference, ProguardMemberRule> noSideEffects,
         Map<DexReference, ProguardMemberRule> assumedValues,
-        Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking,
+        Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
         Set<DexReference> identifierNameStrings,
         Set<ProguardIfRule> ifRules,
@@ -1472,8 +1787,7 @@
       neverClassInline.addAll(consequentRootSet.neverClassInline);
       noObfuscation.addAll(consequentRootSet.noObfuscation);
       if (addNoShrinking) {
-        consequentRootSet.noShrinking.forEach(
-            (type, rules) -> noShrinking.computeIfAbsent(type, k -> new HashSet<>()).addAll(rules));
+        noShrinking.addAll(consequentRootSet.noShrinking);
       }
       addDependentItems(consequentRootSet.dependentNoShrinking);
       consequentRootSet.dependentKeepClassCompatRule.forEach(
@@ -1484,18 +1798,17 @@
     }
 
     // Add dependent items that depend on -if rules.
-    private void addDependentItems(
-        Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentItems) {
+    private void addDependentItems(Map<DexReference, ? extends ItemsWithRules> dependentItems) {
       dependentItems.forEach(
           (reference, dependence) ->
               dependentNoShrinking
-                  .computeIfAbsent(reference, x -> new IdentityHashMap<>())
+                  .computeIfAbsent(reference, x -> new MutableItemsWithRules())
                   .putAll(dependence));
     }
 
     public void copy(DexReference original, DexReference rewritten) {
-      if (noShrinking.containsKey(original)) {
-        noShrinking.put(rewritten, noShrinking.get(original));
+      if (noShrinking.containsReference(original)) {
+        noShrinking.putReferenceWithRules(rewritten, noShrinking.getRulesForReference(original));
       }
       if (noObfuscation.contains(original)) {
         noObfuscation.add(rewritten);
@@ -1509,7 +1822,7 @@
     }
 
     public void prune(DexReference reference) {
-      noShrinking.remove(reference);
+      noShrinking.removeReference(reference);
       noObfuscation.remove(reference);
       noSideEffects.remove(reference);
       assumedValues.remove(reference);
@@ -1527,30 +1840,29 @@
         Enqueuer enqueuer) {
       references.removeIf(
           reference -> {
-            if (reference.isDexField()) {
-              DexEncodedField definition = definitions.definitionFor(reference.asDexField());
-              if (definition == null) {
-                return true;
-              }
-              DexClass holder = definitions.definitionFor(definition.holder());
-              if (holder.isProgramClass()) {
-                return !enqueuer.isFieldReferenced(definition);
-              }
-              return !enqueuer.isNonProgramTypeLive(holder);
-            } else if (reference.isDexMethod()) {
-              DexEncodedMethod definition = definitions.definitionFor(reference.asDexMethod());
-              if (definition == null) {
-                return true;
-              }
-              DexClass holder = definitions.definitionFor(definition.holder());
-              if (holder.isProgramClass()) {
-                return !enqueuer.isMethodLive(definition) && !enqueuer.isMethodTargeted(definition);
-              }
-              return !enqueuer.isNonProgramTypeLive(holder);
-            } else {
+            if (reference.isDexType()) {
               DexClass definition = definitions.definitionFor(reference.asDexType());
               return definition == null || !enqueuer.isTypeLive(definition);
             }
+
+            assert reference.isDexMember();
+
+            DexMember<?, ?> member = reference.asDexMember();
+            DexClass holder = definitions.definitionForHolder(member);
+            DexEncodedMember<?, ?> definition = member.lookupOnClass(holder);
+            if (definition == null) {
+              return true;
+            }
+            if (holder.isProgramClass()) {
+              if (definition.isDexEncodedField()) {
+                DexEncodedField field = definition.asDexEncodedField();
+                return !enqueuer.isFieldReferenced(field);
+              }
+              assert definition.isDexEncodedMethod();
+              DexEncodedMethod method = definition.asDexEncodedMethod();
+              return !enqueuer.isMethodLive(method) && !enqueuer.isMethodTargeted(method);
+            }
+            return !enqueuer.isNonProgramTypeLive(holder);
           });
     }
 
@@ -1582,54 +1894,49 @@
     }
 
     public boolean verifyKeptFieldsAreAccessedAndLive(AppInfoWithLiveness appInfo) {
-      for (DexReference reference : noShrinking.keySet()) {
-        if (reference.isDexField()) {
-          DexField field = reference.asDexField();
-          DexEncodedField encodedField = appInfo.definitionFor(field);
-          if (encodedField != null
-              && (encodedField.isStatic() || isKeptDirectlyOrIndirectly(field.holder, appInfo))) {
-            assert appInfo.isFieldRead(encodedField)
-                : "Expected kept field `" + field.toSourceString() + "` to be read";
-            assert appInfo.isFieldWritten(encodedField)
-                : "Expected kept field `" + field.toSourceString() + "` to be written";
-          }
-        }
-      }
+      noShrinking.forEachField(
+          reference -> {
+            DexClass holder = appInfo.definitionForHolder(reference);
+            DexEncodedField field = reference.lookupOnClass(holder);
+            if (field != null
+                && (field.isStatic() || isKeptDirectlyOrIndirectly(field.holder(), appInfo))) {
+              assert appInfo.isFieldRead(field)
+                  : "Expected kept field `" + field.toSourceString() + "` to be read";
+              assert appInfo.isFieldWritten(field)
+                  : "Expected kept field `" + field.toSourceString() + "` to be written";
+            }
+          });
       return true;
     }
 
     public boolean verifyKeptMethodsAreTargetedAndLive(AppInfoWithLiveness appInfo) {
-      for (DexReference reference : noShrinking.keySet()) {
-        if (reference.isDexMethod()) {
-          DexMethod method = reference.asDexMethod();
-          assert appInfo.targetedMethods.contains(method)
-              : "Expected kept method `" + method.toSourceString() + "` to be targeted";
-          DexEncodedMethod encodedMethod = appInfo.definitionFor(method);
-          if (!encodedMethod.accessFlags.isAbstract()
-              && isKeptDirectlyOrIndirectly(method.holder, appInfo)) {
-            assert appInfo.liveMethods.contains(method)
-                : "Expected non-abstract kept method `"
-                    + method.toSourceString()
-                    + "` to be live";
-          }
-        }
-      }
+      noShrinking.forEachMethod(
+          reference -> {
+            assert appInfo.targetedMethods.contains(reference)
+                : "Expected kept method `" + reference.toSourceString() + "` to be targeted";
+            DexEncodedMethod method =
+                appInfo.definitionForHolder(reference).lookupMethod(reference);
+            if (!method.isAbstract() && isKeptDirectlyOrIndirectly(method.holder(), appInfo)) {
+              assert appInfo.liveMethods.contains(reference)
+                  : "Expected non-abstract kept method `"
+                      + reference.toSourceString()
+                      + "` to be live";
+            }
+          });
       return true;
     }
 
     public boolean verifyKeptTypesAreLive(AppInfoWithLiveness appInfo) {
-      for (DexReference reference : noShrinking.keySet()) {
-        if (reference.isDexType()) {
-          DexType type = reference.asDexType();
-          assert appInfo.isLiveProgramType(type)
-              : "Expected kept type `" + type.toSourceString() + "` to be live";
-        }
-      }
+      noShrinking.forEachClass(
+          type -> {
+            assert appInfo.isLiveProgramType(type)
+                : "Expected kept type `" + type.toSourceString() + "` to be live";
+          });
       return true;
     }
 
     private boolean isKeptDirectlyOrIndirectly(DexType type, AppInfoWithLiveness appInfo) {
-      if (noShrinking.containsKey(type)) {
+      if (noShrinking.containsClass(type)) {
         return true;
       }
       DexClass clazz = appInfo.definitionFor(type);
@@ -1643,41 +1950,34 @@
     }
 
     public boolean verifyKeptItemsAreKept(DexApplication application, AppInfo appInfo) {
-      Set<DexReference> pinnedItems =
-          appInfo.hasLiveness() ? appInfo.withLiveness().pinnedItems : null;
-
       // Create a mapping from each required type to the set of required members on that type.
-      Map<DexType, Set<DexReference>> requiredReferencesPerType = new IdentityHashMap<>();
-      for (DexReference reference : noShrinking.keySet()) {
-        // Check that `pinnedItems` is a super set of the root set.
-        assert pinnedItems == null || pinnedItems.contains(reference)
-            : "Expected reference `" + reference.toSourceString() + "` to be pinned";
-        if (reference.isDexType()) {
-          DexType type = reference.asDexType();
-          requiredReferencesPerType.putIfAbsent(type, Sets.newIdentityHashSet());
-        } else {
-          assert reference.isDexField() || reference.isDexMethod();
-          DexType holder =
-              reference.isDexField()
-                  ? reference.asDexField().holder
-                  : reference.asDexMethod().holder;
-          requiredReferencesPerType
-              .computeIfAbsent(holder, key -> Sets.newIdentityHashSet())
-              .add(reference);
-        }
-      }
+      Map<DexType, Set<DexMember<?, ?>>> requiredMembersPerType = new IdentityHashMap<>();
+      noShrinking.forEachClass(
+          type -> {
+            assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(type)
+                : "Expected reference `" + type.toSourceString() + "` to be pinned";
+            requiredMembersPerType.computeIfAbsent(type, key -> Sets.newIdentityHashSet());
+          });
+      noShrinking.forEachMember(
+          member -> {
+            assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(member)
+                : "Expected reference `" + member.toSourceString() + "` to be pinned";
+            requiredMembersPerType
+                .computeIfAbsent(member.holder, key -> Sets.newIdentityHashSet())
+                .add(member);
+          });
 
       // Run through each class in the program and check that it has members it must have.
       for (DexProgramClass clazz : application.classes()) {
-        Set<DexReference> requiredReferences =
-            requiredReferencesPerType.getOrDefault(clazz.type, ImmutableSet.of());
+        Set<DexMember<?, ?>> requiredMembers =
+            requiredMembersPerType.getOrDefault(clazz.type, ImmutableSet.of());
 
         Set<DexField> fields = null;
         Set<DexMethod> methods = null;
 
-        for (DexReference requiredReference : requiredReferences) {
-          if (requiredReference.isDexField()) {
-            DexField requiredField = requiredReference.asDexField();
+        for (DexMember<?, ?> requiredMember : requiredMembers) {
+          if (requiredMember.isDexField()) {
+            DexField requiredField = requiredMember.asDexField();
             if (fields == null) {
               // Create a Set of the fields to avoid quadratic behavior.
               fields =
@@ -1689,8 +1989,8 @@
                 : "Expected field `"
                     + requiredField.toSourceString()
                     + "` from the root set to be present";
-          } else if (requiredReference.isDexMethod()) {
-            DexMethod requiredMethod = requiredReference.asDexMethod();
+          } else {
+            DexMethod requiredMethod = requiredMember.asDexMethod();
             if (methods == null) {
               // Create a Set of the methods to avoid quadratic behavior.
               methods =
@@ -1702,20 +2002,18 @@
                 : "Expected method `"
                     + requiredMethod.toSourceString()
                     + "` from the root set to be present";
-          } else {
-            assert false;
           }
         }
-        requiredReferencesPerType.remove(clazz.type);
+        requiredMembersPerType.remove(clazz.type);
       }
 
       // If the map is non-empty, then a type in the root set was not in the application.
-      if (!requiredReferencesPerType.isEmpty()) {
-        DexType type = requiredReferencesPerType.keySet().iterator().next();
+      if (!requiredMembersPerType.isEmpty()) {
+        DexType type = requiredMembersPerType.keySet().iterator().next();
         DexClass clazz = application.definitionFor(type);
         assert clazz == null || clazz.isProgramClass()
             : "Unexpected library type in root set: `" + type + "`";
-        assert requiredReferencesPerType.isEmpty()
+        assert requiredMembersPerType.isEmpty()
             : "Expected type `" + type.toSourceString() + "` to be present";
       }
 
@@ -1726,7 +2024,6 @@
     public String toString() {
       StringBuilder builder = new StringBuilder();
       builder.append("RootSet");
-
       builder.append("\nnoShrinking: " + noShrinking.size());
       builder.append("\nnoObfuscation: " + noObfuscation.size());
       builder.append("\nreasonAsked: " + reasonAsked.size());
@@ -1736,13 +2033,6 @@
       builder.append("\ndependentNoShrinking: " + dependentNoShrinking.size());
       builder.append("\nidentifierNameStrings: " + identifierNameStrings.size());
       builder.append("\nifRules: " + ifRules.size());
-
-      builder.append("\n\nNo Shrinking:");
-      noShrinking.keySet().stream()
-          .sorted(Comparator.comparing(DexReference::toSourceString))
-          .forEach(a -> builder
-              .append("\n").append(a.toSourceString()).append(" ").append(noShrinking.get(a)));
-      builder.append("\n");
       return builder.toString();
     }
   }
@@ -1754,9 +2044,9 @@
     ConsequentRootSet(
         Set<DexMethod> neverInline,
         Set<DexType> neverClassInline,
-        Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking,
+        MutableItemsWithRules noShrinking,
         Set<DexReference> noObfuscation,
-        Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking,
+        Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
         List<DelayedRootSetActionItem> delayedRootSetActionItems) {
       super(
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 346caa4..ec2ced1 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -178,7 +178,7 @@
       clazz.setStaticFields(reachableStaticFields);
     }
     clazz.removeInnerClasses(this::isAttributeReferencingPrunedType);
-    clazz.removeEnclosingMethod(this::isAttributeReferencingPrunedItem);
+    clazz.removeEnclosingMethodAttribute(this::isAttributeReferencingPrunedItem);
     rewriteNestAttributes(clazz);
     usagePrinter.visited();
     assert verifyNoDeadFields(clazz);
@@ -300,8 +300,7 @@
         // Final classes cannot be abstract, so we have to keep the method in that case.
         // Also some other kinds of methods cannot be abstract, so keep them around.
         boolean allowAbstract =
-            (!options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()
-                    || clazz.isAbstract())
+            (options.canUseAbstractMethodOnNonAbstractClass() || clazz.isAbstract())
                 && !method.isFinal()
                 && !method.accessFlags.isNative()
                 && !method.accessFlags.isStrict()
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index da9038d..bd3b4f8 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -75,14 +75,12 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
-import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 /**
@@ -273,12 +271,12 @@
     // For all pinned fields, also pin the type of the field (because changing the type of the field
     // implicitly changes the signature of the field). Similarly, for all pinned methods, also pin
     // the return type and the parameter types of the method.
-    extractPinnedItems(appInfo.pinnedItems, AbortReason.PINNED_SOURCE);
-
-    // TODO(christofferqa): Remove the invariant that the graph lense should not modify any
-    // methods from the sets alwaysInline and noSideEffects (see use of assertNotModified).
-    extractPinnedItems(appInfo.alwaysInline, AbortReason.ALWAYS_INLINE);
-    extractPinnedItems(appInfo.noSideEffects.keySet(), AbortReason.NO_SIDE_EFFECTS);
+    // TODO(b/156715504): Compute referenced-by-pinned in the keep info objects.
+    List<DexReference> pinnedItems = new ArrayList<>();
+    appInfo.getKeepInfo().forEachPinnedType(pinnedItems::add);
+    appInfo.getKeepInfo().forEachPinnedMethod(pinnedItems::add);
+    appInfo.getKeepInfo().forEachPinnedField(pinnedItems::add);
+    extractPinnedItems(pinnedItems, AbortReason.PINNED_SOURCE);
 
     for (DexProgramClass clazz : classes) {
       for (DexEncodedMethod method : clazz.methods()) {
@@ -396,7 +394,8 @@
     if (result.shouldBreak()) {
       return false;
     }
-    if (sourceClass.getEnclosingMethod() != null || !sourceClass.getInnerClasses().isEmpty()) {
+    if (sourceClass.getEnclosingMethodAttribute() != null
+        || !sourceClass.getInnerClasses().isEmpty()) {
       // TODO(b/147504070): Consider merging of enclosing-method and inner-class attributes.
       if (Log.ENABLED) {
         AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(sourceClass);
@@ -454,7 +453,8 @@
       }
       return false;
     }
-    if (targetClass.getEnclosingMethod() != null || !targetClass.getInnerClasses().isEmpty()) {
+    if (targetClass.getEnclosingMethodAttribute() != null
+        || !targetClass.getInnerClasses().isEmpty()) {
       // TODO(b/147504070): Consider merging of enclosing-method and inner-class attributes.
       if (Log.ENABLED) {
         AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(sourceClass);
@@ -646,14 +646,6 @@
   }
 
   private boolean verifyGraphLens(VerticalClassMergerGraphLense graphLense) {
-    assert graphLense.assertDefinitionsNotModified(
-        appInfo.alwaysInline.stream()
-            .map(appInfo::definitionFor)
-            .filter(Objects::nonNull)
-            .collect(Collectors.toList()));
-
-    assert graphLense.assertReferencesNotModified(appInfo.noSideEffects.keySet());
-
     // Note that the method assertReferencesNotModified() relies on getRenamedFieldSignature() and
     // getRenamedMethodSignature() instead of lookupField() and lookupMethod(). This is important
     // for this check to succeed, since it is not guaranteed that calling lookupMethod() with a
@@ -680,7 +672,7 @@
     // that `invoke-super A.method` instructions, which are in one of the methods from C, needs to
     // be rewritten to `invoke-direct C.method$B`. This is valid even though A.method() is actually
     // pinned, because this rewriting does not affect A.method() in any way.
-    assert graphLense.assertReferencesNotModified(appInfo.pinnedItems);
+    assert graphLense.assertPinnedNotModified(appInfo.getKeepInfo());
 
     for (DexProgramClass clazz : appInfo.classes()) {
       for (DexEncodedMethod encodedMethod : clazz.methods()) {
@@ -1783,10 +1775,10 @@
     private boolean foundIllegalAccess;
     private ProgramMethod context;
 
-    private final AppView<?> appView;
+    private final AppView<AppInfoWithLiveness> appView;
     private final DexClass source;
 
-    public IllegalAccessDetector(AppView<?> appView, DexClass source) {
+    public IllegalAccessDetector(AppView<AppInfoWithLiveness> appView, DexClass source) {
       super(appView.dexItemFactory());
       this.appView = appView;
       this.source = source;
@@ -1808,7 +1800,7 @@
           checkTypeReference(field.holder);
           checkTypeReference(field.type);
 
-          DexEncodedField definition = appView.definitionFor(field);
+          DexEncodedField definition = appView.appInfo().resolveField(field).getResolvedField();
           if (definition == null || !definition.accessFlags.isPublic()) {
             foundIllegalAccess = true;
           }
@@ -1817,7 +1809,7 @@
       return true;
     }
 
-    private boolean checkMethodReference(DexMethod method) {
+    private boolean checkMethodReference(DexMethod method, OptionalBool isInterface) {
       if (!foundIllegalAccess) {
         DexType baseType =
             appView.graphLense().lookupType(method.holder.toBaseType(appView.dexItemFactory()));
@@ -1827,8 +1819,12 @@
           for (DexType type : method.proto.parameters.values) {
             checkTypeReference(type);
           }
-          DexEncodedMethod definition = appView.definitionFor(method);
-          if (definition == null || !definition.accessFlags.isPublic()) {
+          ResolutionResult resolutionResult =
+              isInterface.isUnknown()
+                  ? appView.appInfo().unsafeResolveMethodDueToDexFormat(method)
+                  : appView.appInfo().resolveMethod(method, isInterface.isTrue());
+          if (!resolutionResult.isSingleResolution()
+              || !resolutionResult.asSingleResolution().getResolvedMethod().isPublic()) {
             foundIllegalAccess = true;
           }
         }
@@ -1860,7 +1856,7 @@
       assert context != null;
       GraphLenseLookupResult lookup =
           appView.graphLense().lookupMethod(method, context.getReference(), Type.VIRTUAL);
-      return checkMethodReference(lookup.getMethod());
+      return checkMethodReference(lookup.getMethod(), OptionalBool.FALSE);
     }
 
     @Override
@@ -1868,7 +1864,7 @@
       assert context != null;
       GraphLenseLookupResult lookup =
           appView.graphLense().lookupMethod(method, context.getReference(), Type.DIRECT);
-      return checkMethodReference(lookup.getMethod());
+      return checkMethodReference(lookup.getMethod(), OptionalBool.UNKNOWN);
     }
 
     @Override
@@ -1876,7 +1872,7 @@
       assert context != null;
       GraphLenseLookupResult lookup =
           appView.graphLense().lookupMethod(method, context.getReference(), Type.STATIC);
-      return checkMethodReference(lookup.getMethod());
+      return checkMethodReference(lookup.getMethod(), OptionalBool.UNKNOWN);
     }
 
     @Override
@@ -1884,7 +1880,7 @@
       assert context != null;
       GraphLenseLookupResult lookup =
           appView.graphLense().lookupMethod(method, context.getReference(), Type.INTERFACE);
-      return checkMethodReference(lookup.getMethod());
+      return checkMethodReference(lookup.getMethod(), OptionalBool.TRUE);
     }
 
     @Override
@@ -1892,7 +1888,7 @@
       assert context != null;
       GraphLenseLookupResult lookup =
           appView.graphLense().lookupMethod(method, context.getReference(), Type.SUPER);
-      return checkMethodReference(lookup.getMethod());
+      return checkMethodReference(lookup.getMethod(), OptionalBool.UNKNOWN);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index 1202384..2a0a843 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -17,7 +17,6 @@
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
@@ -55,8 +54,6 @@
   private Set<DexMethod> mergedMethods;
   private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges;
 
-  private Map<DexMethod, Set<DexType>> contextsForContextSensitiveMethods;
-
   private VerticalClassMergerGraphLense(
       AppView<?> appView,
       Map<DexType, DexType> typeMap,
@@ -82,24 +79,6 @@
     this.originalMethodSignaturesForBridges = originalMethodSignaturesForBridges;
   }
 
-  public void initializeCacheForLookupMethodInAllContexts() {
-    assert contextsForContextSensitiveMethods == null;
-    contextsForContextSensitiveMethods = new IdentityHashMap<>();
-    contextualVirtualToDirectMethodMaps.forEach(
-        (type, virtualToDirectMethodMap) -> {
-          for (DexMethod method : virtualToDirectMethodMap.keySet()) {
-            contextsForContextSensitiveMethods
-                .computeIfAbsent(method, ignore -> Sets.newIdentityHashSet())
-                .add(type);
-          }
-        });
-  }
-
-  public void unsetCacheForLookupMethodInAllContexts() {
-    assert contextsForContextSensitiveMethods != null;
-    contextsForContextSensitiveMethods = null;
-  }
-
   @Override
   public DexType getOriginalType(DexType type) {
     return previousLense.getOriginalType(type);
@@ -145,22 +124,6 @@
   }
 
   @Override
-  public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
-    assert contextsForContextSensitiveMethods != null;
-    ImmutableSet.Builder<DexMethod> builder = ImmutableSet.builder();
-    for (DexMethod previous : previousLense.lookupMethodInAllContexts(method)) {
-      builder.add(methodMap.getOrDefault(previous, previous));
-      Set<DexType> contexts = contextsForContextSensitiveMethods.get(previous);
-      if (contexts != null) {
-        for (DexType context : contexts) {
-          builder.add(contextualVirtualToDirectMethodMaps.get(context).get(previous).getMethod());
-        }
-      }
-    }
-    return builder.build();
-  }
-
-  @Override
   public boolean isContextFreeForMethods() {
     return contextualVirtualToDirectMethodMaps.isEmpty() && previousLense.isContextFreeForMethods();
   }
diff --git a/src/main/java/com/android/tools/r8/utils/AssertionUtils.java b/src/main/java/com/android/tools/r8/utils/AssertionUtils.java
new file mode 100644
index 0000000..c185096
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/AssertionUtils.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+public class AssertionUtils {
+
+  public static boolean assertNotNull(Object o) {
+    assert o != null;
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index 196091e..2d78c0d 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -369,13 +369,6 @@
     return 'L' + className.replace(JAVA_PACKAGE_SEPARATOR, INNER_CLASS_SEPARATOR) + ';';
   }
 
-  // TODO(b/151195430): Remove once a new version of kotlinx-metadata is released.
-  // Kotlin @Metadata deserialization has plain "kotlin", which will be relocated in r8lib.
-  // See b/70169921#comment25 for more details.
-  private static String backwardRelocatedName(String name) {
-    return name.replace("com/android/tools/r8/jetbrains/", "");
-  }
-
   /**
    * Get unqualified class name from its binary name.
    *
@@ -512,6 +505,11 @@
     return 'L' + descriptor + ';';
   }
 
+  public static boolean isValidBinaryName(String binaryName) {
+    return isValidJavaType(
+        binaryName.replace(DESCRIPTOR_PACKAGE_SEPARATOR, JAVA_PACKAGE_SEPARATOR));
+  }
+
   public static class ModuleAndDescriptor {
     private final String module;
     private final String descriptor;
diff --git a/src/main/java/com/android/tools/r8/utils/FunctionUtils.java b/src/main/java/com/android/tools/r8/utils/FunctionUtils.java
new file mode 100644
index 0000000..64e8537
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/FunctionUtils.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class FunctionUtils {
+
+  public static <T, R> void forEachApply(
+      Iterable<T> list, Function<T, Consumer<R>> func, R argument) {
+    for (T t : list) {
+      func.apply(t).accept(argument);
+    }
+  }
+}
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 8c92acf..00a2136 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -172,14 +172,12 @@
 
   void disableAllOptimizations() {
     disableGlobalOptimizations();
-    enableNonNullTracking = false;
     enableNameReflectionOptimization = false;
     enableStringConcatenationOptimization = false;
   }
 
   public void disableGlobalOptimizations() {
     enableArgumentRemoval = false;
-    enableDynamicTypeOptimization = false;
     enableInlining = false;
     enableClassInlining = false;
     enableClassStaticizer = false;
@@ -215,7 +213,6 @@
   public boolean libraryInterfacesMayHaveStaticInitialization = false;
 
   // Optimization-related flags. These should conform to -dontoptimize and disableAllOptimizations.
-  public boolean enableDynamicTypeOptimization = true;
   public boolean enableFieldAssignmentTracker = true;
   public boolean enableFieldBitAccessAnalysis =
       System.getProperty("com.android.tools.r8.fieldBitAccessAnalysis") != null;
@@ -224,7 +221,6 @@
   public boolean enableArgumentRemoval = true;
   public boolean enableUnusedInterfaceRemoval = true;
   public boolean enableDevirtualization = true;
-  public boolean enableNonNullTracking = true;
   public boolean enableInlining =
       !Version.isDevelopmentVersion()
           || System.getProperty("com.android.tools.r8.disableinlining") == null;
@@ -1250,6 +1246,22 @@
     return minApiLevel >= level.getLevel();
   }
 
+  /**
+   * Dex2Oat issues a warning for abstract methods on non-abstract classes, so we never allow this.
+   *
+   * <p>Note that having an invoke instruction that targets an abstract method on a non-abstract
+   * class will fail with a verification error on Dalvik. Therefore, this must not be more
+   * permissive than {@code return minApiLevel >= AndroidApiLevel.L.getLevel()}.
+   *
+   * <p>See b/132953944.
+   */
+  @SuppressWarnings("ConstantConditions")
+  public boolean canUseAbstractMethodOnNonAbstractClass() {
+    boolean result = false;
+    assert !(result && canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug());
+    return result;
+  }
+
   public boolean canUseConstClassInstructions(int cfVersion) {
     assert isGeneratingClassFiles();
     return cfVersion >= requiredCfVersionForConstClassInstructions();
@@ -1673,7 +1685,7 @@
   //
   // See b/132953944.
   public boolean canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug() {
-    return minApiLevel < AndroidApiLevel.L.getLevel();
+    return isGeneratingDex() && minApiLevel < AndroidApiLevel.L.getLevel();
   }
 
   // On dalvik we see issues when using an int value in places where a boolean, byte, char, or short
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 a785262b..e1572f5 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -408,7 +408,14 @@
                   || !Objects.equals(mp.caller, lastPosition.caller)) {
                 break;
               }
-              lastPosition = mp;
+              // The mapped positions are not guaranteed to be in order, so maintain first and last
+              // position.
+              if (firstPosition.obfuscatedLine > mp.obfuscatedLine) {
+                firstPosition = mp;
+              }
+              if (lastPosition.obfuscatedLine < mp.obfuscatedLine) {
+                lastPosition = mp;
+              }
             }
             Range obfuscatedRange =
                 new Range(firstPosition.obfuscatedLine, lastPosition.obfuscatedLine);
diff --git a/src/main/java/com/android/tools/r8/utils/OriginWithPosition.java b/src/main/java/com/android/tools/r8/utils/OriginWithPosition.java
new file mode 100644
index 0000000..8459d19
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/OriginWithPosition.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.Objects;
+
+public class OriginWithPosition {
+  private final Origin origin;
+  private final Position position;
+
+  public OriginWithPosition(Origin origin, Position position) {
+    this.origin = origin;
+    this.position = position;
+  }
+
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  public Position getPosition() {
+    return position;
+  }
+
+  @Override
+  public int hashCode() {
+    return origin.hashCode() * 13 + position.hashCode();
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (other instanceof OriginWithPosition) {
+      return Objects.equals(((OriginWithPosition) other).origin, origin)
+          && Objects.equals(((OriginWithPosition) other).position, position);
+    }
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/SetUtils.java b/src/main/java/com/android/tools/r8/utils/SetUtils.java
index c961003..89880cc 100644
--- a/src/main/java/com/android/tools/r8/utils/SetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -24,6 +24,13 @@
     return result;
   }
 
+  public static <T> Set<T> newIdentityHashSet(Iterable<T> c1, Iterable<T> c2) {
+    Set<T> result = Sets.newIdentityHashSet();
+    c1.forEach(result::add);
+    c2.forEach(result::add);
+    return result;
+  }
+
   public static <T> Set<T> newIdentityHashSet(int capacity) {
     return Collections.newSetFromMap(new IdentityHashMap<>(capacity));
   }
diff --git a/src/main/java/com/android/tools/r8/utils/TriPredicate.java b/src/main/java/com/android/tools/r8/utils/TriPredicate.java
new file mode 100644
index 0000000..42b76a7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/TriPredicate.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+public interface TriPredicate<S, T, U> {
+
+  boolean test(S s, T t, U u);
+}
diff --git a/src/test/examplesProto/proto2/BuilderOnlyReferencedFromDynamicMethodTestClass.java b/src/test/examplesProto/proto2/BuilderOnlyReferencedFromDynamicMethodTestClass.java
new file mode 100644
index 0000000..e3707dc
--- /dev/null
+++ b/src/test/examplesProto/proto2/BuilderOnlyReferencedFromDynamicMethodTestClass.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package proto2;
+
+import com.android.tools.r8.proto2.TestProto.Primitives;
+import com.google.protobuf.GeneratedMessageLite;
+import com.google.protobuf.InvalidProtocolBufferException;
+
+public class BuilderOnlyReferencedFromDynamicMethodTestClass {
+
+  public static void main(String[] args) {
+    GeneratedMessageLite<?, ?> primitivesInDisguise;
+    try {
+      primitivesInDisguise = Primitives.parseFrom(new byte[0]);
+    } catch (InvalidProtocolBufferException e) {
+      System.out.println("Unexpected exception: " + e);
+      throw new RuntimeException(e);
+    }
+    Primitives primitives = (Primitives) primitivesInDisguise.toBuilder().build();
+    Printer.print(primitives);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/AssumeNoSideEffects.java b/src/test/java/com/android/tools/r8/AssumeNoSideEffects.java
new file mode 100644
index 0000000..0513e17
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/AssumeNoSideEffects.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+public @interface AssumeNoSideEffects {}
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index ce051a5..f395118 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -37,14 +37,20 @@
 import java.util.List;
 import java.util.Set;
 import java.util.zip.ZipFile;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
-public class D8CommandTest {
+@RunWith(Parameterized.class)
+public class D8CommandTest extends TestBase {
 
-  @Rule
-  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public D8CommandTest(TestParameters parameters) {}
 
   @Test(expected = CompilationFailedException.class)
   public void emptyBuilder() throws Throwable {
@@ -267,6 +273,12 @@
     parse("--main-dex-list", mainDexList.toString());
   }
 
+  @Test
+  public void testFlagFilePerClass() throws Throwable {
+    D8Command command = parse("--file-per-class");
+    assertTrue(command.getProgramConsumer() instanceof DexFilePerClassFileConsumer);
+  }
+
   @Test(expected = CompilationFailedException.class)
   public void mainDexListWithFilePerClass() throws Throwable {
     Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
@@ -274,6 +286,19 @@
     assertTrue(ToolHelper.getApp(command).hasMainDexListResources());
   }
 
+  @Test
+  public void testFlagFilePerClassFile() throws Throwable {
+    D8Command command = parse("--file-per-class-file");
+    assertTrue(command.getProgramConsumer() instanceof DexFilePerClassFileConsumer);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void mainDexListWithFilePerClassFile() throws Throwable {
+    Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
+    D8Command command = parse("--main-dex-list", mainDexList.toString(), "--file-per-class-file");
+    assertTrue(ToolHelper.getApp(command).hasMainDexListResources());
+  }
+
   @Test(expected = CompilationFailedException.class)
   public void mainDexListWithIntermediate() throws Throwable {
     Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
index 5f6b792..70518d7 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
@@ -64,7 +65,21 @@
 
       @Override
       protected void explain(Description description) {
-        description.appendText("orgin ").appendText(origin.toString());
+        description.appendText("origin ").appendText(origin.toString());
+      }
+    };
+  }
+
+  public static Matcher<Diagnostic> diagnosticPosition(Position position) {
+    return new DiagnosticsMatcher() {
+      @Override
+      protected boolean eval(Diagnostic diagnostic) {
+        return diagnostic.getPosition().equals(position);
+      }
+
+      @Override
+      protected void explain(Description description) {
+        description.appendText("position ").appendText(position.getDescription());
       }
     };
   }
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index 8bc4820..18571d0 100644
--- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.debug.CfDebugTestConfig;
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.FileUtils;
 import com.google.common.collect.ObjectArrays;
@@ -150,4 +151,9 @@
   public JvmTestBuilder addVmArguments(String... arguments) {
     return addVmArguments(Arrays.asList(arguments));
   }
+
+  public JvmTestBuilder addAndroidBuildVersion() {
+    addVmArguments("-D" + AndroidBuildVersion.PROPERTY + "=10000");
+    return addProgramClasses(AndroidBuildVersion.class);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/L8CommandTest.java b/src/test/java/com/android/tools/r8/L8CommandTest.java
index 705ecff..1631830 100644
--- a/src/test/java/com/android/tools/r8/L8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/L8CommandTest.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -15,6 +17,7 @@
 import com.android.tools.r8.origin.EmbeddedOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
@@ -24,14 +27,20 @@
 import java.util.Collections;
 import java.util.List;
 import org.junit.Ignore;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
-public class L8CommandTest {
+@RunWith(Parameterized.class)
+public class L8CommandTest extends TestBase {
 
-  @Rule
-  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public L8CommandTest(TestParameters parameters) {}
 
   @Test(expected = CompilationFailedException.class)
   public void emptyBuilder() throws Throwable {
@@ -95,6 +104,34 @@
     Marker marker = markers.iterator().next();
   }
 
+  @Test
+  public void testFlagPgConf() throws Exception {
+    TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
+    Path pgconf = temp.newFolder().toPath().resolve("pg.conf");
+    FileUtils.writeTextFile(pgconf, "");
+    parse(
+        diagnostics,
+        "--desugared-lib",
+        ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString(),
+        "--pg-conf",
+        pgconf.toString());
+  }
+
+  @Test
+  public void testFlagPgConfMissingParameter() {
+    TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
+    try {
+      parse(
+          diagnostics,
+          "--desugared-lib",
+          ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString(),
+          "--pg-conf");
+      fail("Expected parse error");
+    } catch (CompilationFailedException e) {
+      diagnostics.assertErrorsMatch(diagnosticMessage(containsString("Missing parameter")));
+    }
+  }
+
   private L8Command.Builder prepareBuilder(DiagnosticsHandler handler) {
     return L8Command.builder(handler)
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index b5c518c..149c3c3 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -3,7 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.ToolHelper.EXAMPLES_BUILD_DIR;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -37,14 +39,20 @@
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 import java.util.zip.ZipOutputStream;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
-public class R8CommandTest {
+@RunWith(Parameterized.class)
+public class R8CommandTest extends TestBase {
 
-  @Rule
-  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public R8CommandTest(TestParameters parameters) {}
 
   @Test(expected = CompilationFailedException.class)
   public void emptyBuilder() throws Throwable {
@@ -759,6 +767,34 @@
   }
 
   @Test
+  public void desugaredLibraryWithOutputConf() throws CompilationFailedException {
+    Path pgout = temp.getRoot().toPath().resolve("pgout.conf");
+    R8Command r8Command =
+        parse(
+            "--desugared-lib",
+            "src/library_desugar/desugar_jdk_libs.json",
+            "--desugared-lib-pg-conf-output",
+            pgout.toString());
+    assertFalse(
+        r8Command.getInternalOptions().desugaredLibraryConfiguration.getRewritePrefix().isEmpty());
+  }
+
+  @Test
+  public void desugaredLibraryWithOutputConfMissingArg() {
+    TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
+    try {
+      parse(
+          diagnostics,
+          "--desugared-lib",
+          "src/library_desugar/desugar_jdk_libs.json",
+          "--desugared-lib-pg-conf-output");
+      fail("Expected parse error");
+    } catch (CompilationFailedException e) {
+      diagnostics.assertErrorsMatch(diagnosticMessage(containsString("Missing parameter")));
+    }
+  }
+
+  @Test
   public void numThreadsOption() throws Exception {
     assertEquals(ThreadUtils.NOT_SPECIFIED, parse().getThreadCount());
     assertEquals(1, parse("--thread-count", "1").getThreadCount());
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 6326718..eddf1ee 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -513,9 +513,6 @@
             // Addition of checks for super-class-initialization cause this to abort on non-ToT art.
             "008-exceptions",
 
-            // Fails due to non-matching Exception messages.
-            "201-built-in-except-detail-messages",
-
             // Generally fails on non-R8/D8 running.
             "156-register-dex-file-multi-loader",
             "412-new-array",
@@ -525,9 +522,6 @@
             // Addition of checks for super-class-initialization cause this to abort on non-ToT art.
             "008-exceptions",
 
-            // Fails due to non-matching Exception messages.
-            "201-built-in-except-detail-messages",
-
             // Generally fails on non-R8/D8 running.
             "004-checker-UnsafeTest18",
             "005-annotations",
@@ -544,9 +538,6 @@
             // Addition of checks for super-class-initialization cause this to abort on non-ToT art.
             "008-exceptions",
 
-            // Fails due to non-matching Exception messages.
-            "201-built-in-except-detail-messages",
-
             // Generally fails on non R8/D8 running.
             "004-checker-UnsafeTest18",
             "004-NativeAllocations",
@@ -567,9 +558,6 @@
             // Addition of checks for super-class-initialization cause this to abort on non-ToT art.
             "008-exceptions",
 
-            // Fails due to non-matching Exception messages.
-            "201-built-in-except-detail-messages",
-
             // Generally fails on non R8/D8 running.
             "004-checker-UnsafeTest18",
             "004-NativeAllocations",
@@ -590,9 +578,6 @@
             // Addition of checks for super-class-initialization cause this to abort on non-ToT art.
             "008-exceptions",
 
-            // Fails due to non-matching Exception messages.
-            "201-built-in-except-detail-messages",
-
             // Generally fails on non R8/D8 running.
             "004-checker-UnsafeTest18",
             "004-NativeAllocations",
@@ -821,6 +806,24 @@
           .put("138-duplicate-classes-check", TestCondition.any())
           // Array index out of bounds exception.
           .put("150-loadlibrary", TestCondition.any())
+          // Fails due to non-matching Exception messages.
+          .put(
+              "201-built-in-except-detail-messages",
+              TestCondition.or(
+                  TestCondition.match(
+                      TestCondition.compilers(
+                          CompilerUnderTest.D8, CompilerUnderTest.D8_AFTER_R8CF),
+                      TestCondition.runtimes(
+                          DexVm.Version.V4_0_4,
+                          DexVm.Version.V4_4_4,
+                          DexVm.Version.V5_1_1,
+                          DexVm.Version.V6_0_1,
+                          DexVm.Version.V7_0_0)),
+                  TestCondition.match(
+                      TestCondition.compilers(
+                          CompilerUnderTest.R8,
+                          CompilerUnderTest.R8CF,
+                          CompilerUnderTest.R8_AFTER_D8))))
           // Uses dex file version 37 and therefore only runs on Android N and above.
           .put(
               "370-dex-v37",
@@ -1170,13 +1173,6 @@
       "435-new-instance"
   );
 
-  private static List<String> requireUninstantiatedTypeOptimizationToBeDisabled = ImmutableList.of(
-      // This test inspects the message of the exception that is thrown when calling a virtual
-      // method with a null-receiver. This message changes when the invocation is rewritten to
-      // "throw null".
-      "201-built-in-except-detail-messages"
-  );
-
   private static List<String> hasMissingClasses = ImmutableList.of(
       "091-override-package-private-method",
       "003-omnibus-opcodes",
@@ -1279,8 +1275,6 @@
     private final boolean disableInlining;
     // Whether to disable class inlining
     private final boolean disableClassInlining;
-    // Whether to disable the uninitialized type optimization.
-    private final boolean disableUninstantiatedTypeOptimization;
     // Has missing classes.
     private final boolean hasMissingClasses;
     // Explicitly disable desugaring.
@@ -1304,7 +1298,6 @@
         boolean outputMayDiffer,
         boolean disableInlining,
         boolean disableClassInlining,
-        boolean disableUninstantiatedTypeOptimization,
         boolean hasMissingClasses,
         boolean disableDesugaring,
         List<String> keepRules,
@@ -1323,7 +1316,6 @@
       this.outputMayDiffer = outputMayDiffer;
       this.disableInlining = disableInlining;
       this.disableClassInlining = disableClassInlining;
-      this.disableUninstantiatedTypeOptimization = disableUninstantiatedTypeOptimization;
       this.hasMissingClasses = hasMissingClasses;
       this.disableDesugaring = disableDesugaring;
       this.keepRules = keepRules;
@@ -1354,7 +1346,6 @@
           disableInlining,
           true, // Disable class inlining for JCTF tests.
           false,
-          false,
           true, // Disable desugaring for JCTF tests.
           ImmutableList.of(),
           null);
@@ -1383,7 +1374,6 @@
           disableInlining,
           true, // Disable class inlining for JCTF tests.
           false,
-          false,
           true, // Disable desugaring for JCTF tests.
           ImmutableList.of(),
           null);
@@ -1548,7 +1538,6 @@
                 outputMayDiffer.contains(name),
                 requireInliningToBeDisabled.contains(name),
                 requireClassInliningToBeDisabled.contains(name),
-                requireUninstantiatedTypeOptimizationToBeDisabled.contains(name),
                 hasMissingClasses.contains(name),
                 false,
                 keepRules.getOrDefault(name, ImmutableList.of()),
@@ -1619,7 +1608,6 @@
 
     private final boolean disableInlining;
     private final boolean disableClassInlining;
-    private final boolean disableUninstantiatedTypeOptimization;
     private final boolean hasMissingClasses;
     private final boolean disableDesugaring;
     private final List<String> keepRules;
@@ -1628,7 +1616,6 @@
     private CompilationOptions(TestSpecification spec) {
       this.disableInlining = spec.disableInlining;
       this.disableClassInlining = spec.disableClassInlining;
-      this.disableUninstantiatedTypeOptimization = spec.disableUninstantiatedTypeOptimization;
       this.hasMissingClasses = spec.hasMissingClasses;
       this.disableDesugaring = spec.disableDesugaring;
       this.keepRules = spec.keepRules;
@@ -1643,9 +1630,6 @@
       if (disableClassInlining) {
         options.enableClassInlining = false;
       }
-      if (disableUninstantiatedTypeOptimization) {
-        options.enableUninstantiatedTypeOptimization = false;
-      }
       // Some tests actually rely on missing classes for what they test.
       options.ignoreMissingClasses = hasMissingClasses;
       if (configuration != null) {
@@ -1664,7 +1648,6 @@
       CompilationOptions options = (CompilationOptions) o;
       return disableInlining == options.disableInlining
           && disableClassInlining == options.disableClassInlining
-          && disableUninstantiatedTypeOptimization == options.disableUninstantiatedTypeOptimization
           && hasMissingClasses == options.hasMissingClasses;
     }
 
@@ -1673,7 +1656,6 @@
       return Objects.hash(
           disableInlining,
           disableClassInlining,
-          disableUninstantiatedTypeOptimization,
           hasMissingClasses);
     }
   }
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index a109e20..9d0684d 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -48,6 +48,7 @@
 
   private AllowedDiagnosticMessages allowedDiagnosticMessages = AllowedDiagnosticMessages.NONE;
   private boolean allowUnusedProguardConfigurationRules = false;
+  private boolean enableAssumeNoSideEffectsAnnotations = false;
   private boolean enableConstantArgumentAnnotations = false;
   private boolean enableInliningAnnotations = false;
   private boolean enableMemberValuePropagationAnnotations = false;
@@ -135,7 +136,8 @@
             box.proguardConfiguration,
             box.syntheticProguardRules,
             proguardMapBuilder.toString(),
-            graphConsumer);
+            graphConsumer,
+            builder.getMinApiLevel());
     switch (allowedDiagnosticMessages) {
       case ALL:
         compileResult.assertDiagnosticThatMatches(new IsAnything<>());
@@ -349,6 +351,21 @@
     return self();
   }
 
+  public T enableAssumeNoSideEffectsAnnotations() {
+    return enableAssumeNoSideEffectsAnnotations(AssumeNoSideEffects.class.getPackage().getName());
+  }
+
+  public T enableAssumeNoSideEffectsAnnotations(String annotationPackageName) {
+    if (!enableAssumeNoSideEffectsAnnotations) {
+      enableAssumeNoSideEffectsAnnotations = true;
+      addInternalKeepRules(
+          "-assumenosideeffects class * { @"
+              + annotationPackageName
+              + ".AssumeNoSideEffects <methods>; }");
+    }
+    return self();
+  }
+
   public T enableInliningAnnotations() {
     return enableInliningAnnotations(NeverInline.class.getPackage().getName());
   }
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index 3a16807..5dac954 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -14,7 +14,6 @@
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.List;
-import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 
 public class R8TestCompileResult extends TestCompileResult<R8TestCompileResult, R8TestRunResult> {
@@ -23,6 +22,7 @@
   private final List<ProguardConfigurationRule> syntheticProguardRules;
   private final String proguardMap;
   private final CollectingGraphConsumer graphConsumer;
+  private final int minApiLevel;
 
   R8TestCompileResult(
       TestState state,
@@ -31,12 +31,14 @@
       ProguardConfiguration proguardConfiguration,
       List<ProguardConfigurationRule> syntheticProguardRules,
       String proguardMap,
-      CollectingGraphConsumer graphConsumer) {
+      CollectingGraphConsumer graphConsumer,
+      int minApiLevel) {
     super(state, app, outputMode);
     this.proguardConfiguration = proguardConfiguration;
     this.syntheticProguardRules = syntheticProguardRules;
     this.proguardMap = proguardMap;
     this.graphConsumer = graphConsumer;
+    this.minApiLevel = minApiLevel;
   }
 
   @Override
@@ -65,11 +67,11 @@
   }
 
   @Override
-  public CodeInspector inspector() throws IOException, ExecutionException {
+  public CodeInspector inspector() throws IOException {
     return new CodeInspector(app, proguardMap);
   }
 
-  public GraphInspector graphInspector() throws IOException, ExecutionException {
+  public GraphInspector graphInspector() throws IOException {
     assert graphConsumer != null;
     return new GraphInspector(graphConsumer, inspector());
   }
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 39356e1..0816496 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -243,6 +243,11 @@
     return self();
   }
 
+  public CR setSystemProperty(String name, String value) {
+    vmArguments.add("-D" + name + "=" + value);
+    return self();
+  }
+
   public Path writeToZip() throws IOException {
     Path file = state.getNewTempFolder().resolve("out.zip");
     writeToZip(file);
@@ -429,6 +434,13 @@
         withArt6Plus64BitsLib && vm.getVersion().isAtLeast(DexVm.Version.V6_0_1)
             ? builder -> builder.appendArtOption("--64")
             : builder -> {};
+    commandConsumer =
+        commandConsumer.andThen(
+            builder -> {
+              for (String vmArgument : vmArguments) {
+                builder.appendArtOption(vmArgument);
+              }
+            });
     ProcessResult result =
         ToolHelper.runArtRaw(
             classPath, mainClass, commandConsumer, vm, withArtFrameworks, arguments);
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index b7869e4..490cf54 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
@@ -62,6 +63,14 @@
   private PrintStream oldStderr = null;
   protected OutputMode outputMode = OutputMode.DexIndexed;
 
+  private boolean isAndroidBuildVersionAdded = false;
+
+  public T addAndroidBuildVersion() {
+    addProgramClasses(AndroidBuildVersion.class);
+    isAndroidBuildVersionAdded = true;
+    return self();
+  }
+
   TestCompilerBuilder(TestState state, B builder, Backend backend) {
     super(state, builder);
     this.backend = backend;
@@ -133,6 +142,9 @@
       cr =
           internalCompile(builder, optionsConsumer, Suppliers.memoize(sink::build))
               .addRunClasspathFiles(additionalRunClassPath);
+      if (isAndroidBuildVersionAdded) {
+        cr.setSystemProperty(AndroidBuildVersion.PROPERTY, "" + builder.getMinApiLevel());
+      }
       return cr;
     } finally {
       if (stdout != null) {
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
index ac3ef82..ed0e8f1 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
@@ -36,7 +36,7 @@
 
   // Match exact.
 
-  default TestDiagnosticMessages assertDiagnosticsMatch(Matcher matcher) {
+  default TestDiagnosticMessages assertDiagnosticsMatch(Matcher<Diagnostic> matcher) {
     return assertDiagnosticsMatch(Collections.singletonList(matcher));
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
index 19a700d..54ad442 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
@@ -201,7 +201,8 @@
         if (!matchedDiagnostics.contains(diagnostic)) {
           builder
               .append("\n  - ")
-              .append(diagnostics.getClass().getName())
+              .append(diagnostic.getClass().getName())
+              .append(": ")
               .append(diagnostic.getDiagnosticMessage());
         }
       }
@@ -220,7 +221,8 @@
       for (Diagnostic diagnostic : diagnostics) {
         builder
             .append("\n  - ")
-            .append(diagnostics.getClass().getName())
+            .append(diagnostic.getClass().getName())
+            .append(": ")
             .append(diagnostic.getDiagnosticMessage());
       }
       builder.append("\nAll matchers:");
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index bf3bb9d..16fd725 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -113,6 +113,10 @@
     return self();
   }
 
+  public RR assertSuccessWithEmptyOutput() {
+    return assertSuccessWithOutput("");
+  }
+
   public RR assertSuccessWithOutputLines(String... expected) {
     return assertSuccessWithOutputLines(Arrays.asList(expected));
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java
index 4c1caf7..9a29671 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java
@@ -5,66 +5,155 @@
 package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
+import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
+import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
 import java.nio.file.Path;
 import java.util.Arrays;
+import java.util.Set;
 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 WrapperMergeTest extends DesugaredLibraryTestBase {
 
+  private static final String EXPECTED = StringUtils.lines("[1, 2, 3]", "[2, 3, 4]");
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public WrapperMergeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addAndroidBuildVersion()
+        .addProgramClassesAndInnerClasses(MyArrays1.class)
+        .addProgramClassesAndInnerClasses(MyArrays2.class)
+        .addProgramClasses(TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
   @Test
   public void testWrapperMerge() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
     // Multiple wrapper classes have to be merged here.
-    Path path1 = testForD8()
-        .addProgramClasses(Executor1.class)
-        .setMinApi(AndroidApiLevel.B)
-        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
-        .compile()
-        .inspect(this::assertWrappers)
-        .writeToZip();
-    Path path2 = testForD8()
-        .addProgramClasses(Executor2.class)
-        .setMinApi(AndroidApiLevel.B)
-        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
-        .compile()
-        .inspect(this::assertWrappers)
-        .writeToZip();
+    Path path1 = compileWithCoreLibraryDesugaring(MyArrays1.class);
+    Path path2 = compileWithCoreLibraryDesugaring(MyArrays2.class);
     testForD8()
         .addProgramFiles(path1, path2)
+        .addProgramClasses(TestClass.class)
+        .addAndroidBuildVersion()
+        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .setMinApi(parameters.getApiLevel())
         .compile()
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
-        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor1.class)
-        .assertSuccessWithOutput(StringUtils.lines("[1, 2, 3]"));
+        .inspect(this::assertWrappers)
+        .inspect(this::assertNoDuplicates)
+        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  private Path compileWithCoreLibraryDesugaring(Class<?> clazz) throws Exception {
+    return testForD8()
+        .addProgramClassesAndInnerClasses(clazz)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .compile()
+        .inspect(this::assertWrappers)
+        .writeToZip();
+  }
+
+  private void assertNoDuplicates(CodeInspector inspector) {
+    Object2ReferenceMap<String, Set<FoundClassSubject>> map = new Object2ReferenceOpenHashMap<>();
+    for (FoundClassSubject clazz : inspector.allClasses()) {
+      map.computeIfAbsent(clazz.getFinalName(), k -> Sets.newIdentityHashSet()).add(clazz);
+    }
+    for (Set<FoundClassSubject> duplicates : map.values()) {
+      if (duplicates.size() > 1) {
+        fail("Unexpected duplicates: " + duplicates);
+      }
+    }
+  }
+
+  private boolean hasNativeIntUnaryOperator() {
+    return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
   }
 
   private void assertWrappers(CodeInspector inspector) {
-    assertEquals(2,inspector.allClasses().stream().filter(c -> c.getOriginalName().contains(
-        DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX)).count());
+    assertEquals(
+        hasNativeIntUnaryOperator() ? 0 : 2,
+        inspector.allClasses().stream()
+            .filter(
+                c ->
+                    c.getOriginalName().contains(DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX))
+            .count());
   }
 
-  static class Executor1 {
+  static class MyArrays1 {
 
-    public static void main(String[] args) {
-      int[] ints = new int[3];
-      Arrays.setAll(ints,x->x+1);
-      System.out.println(Arrays.toString(ints));
+    interface IntGenerator {
+      int generate(int index);
+    }
+
+    public static void setAll(int[] ints, IntGenerator generator) {
+      if (AndroidBuildVersion.VERSION >= 24) {
+        java.util.Arrays.setAll(ints, generator::generate);
+      } else {
+        for (int i = 0; i < ints.length; i++) {
+          ints[i] = generator.generate(i);
+        }
+      }
     }
   }
 
-  static class Executor2 {
+  static class MyArrays2 {
 
-    public static void main(String[] args) {
-      int[] ints = new int[3];
-      Arrays.setAll(ints,x->x+2);
-      System.out.println(Arrays.toString(ints));
+    interface IntGenerator {
+      int generate(int index);
+    }
+
+    public static void setAll(int[] ints, IntGenerator generator) {
+      if (AndroidBuildVersion.VERSION >= 24) {
+        java.util.Arrays.setAll(ints, generator::generate);
+      } else {
+        for (int i = 0; i < ints.length; i++) {
+          ints[i] = generator.generate(i);
+        }
+      }
     }
   }
 
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      int[] ints = new int[3];
+      MyArrays1.setAll(ints, x -> x + 1);
+      System.out.println(Arrays.toString(ints));
+      MyArrays2.setAll(ints, x -> x + 2);
+      System.out.println(Arrays.toString(ints));
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java
index 03e7f70..3b9b321 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -20,15 +19,11 @@
 @RunWith(Parameterized.class)
 public class Java11D8CompilationTest extends TestBase {
 
-  public Java11D8CompilationTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  private final TestParameters parameters;
+  public Java11D8CompilationTest(TestParameters parameters) {}
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimesStartingFromIncluding(Version.V5_1_1).build();
+    return getTestParameters().withNoneRuntime().build();
   }
 
   private static void assertNoNests(CodeInspector inspector) {
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 2cfe861..f828413 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
@@ -4,21 +4,13 @@
 
 package com.android.tools.r8.enumunboxing;
 
-import static junit.framework.TestCase.assertTrue;
-import static org.junit.Assert.assertEquals;
-
 import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumInstanceFieldMain.EnumInstanceField;
-import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumInterfaceMain.EnumInterface;
 import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumStaticFieldMain.EnumStaticField;
-import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumStaticMethodMain.EnumStaticMethod;
-import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumVirtualMethodMain.EnumVirtualMethod;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -29,11 +21,8 @@
 public class FailingEnumUnboxingTest extends EnumUnboxingTestBase {
 
   private static final Class<?>[] FAILURES = {
-    EnumInterface.class,
     EnumStaticField.class,
     EnumInstanceField.class,
-    EnumStaticMethod.class,
-    EnumVirtualMethod.class
   };
 
   private final TestParameters parameters;
@@ -61,15 +50,12 @@
     }
     R8TestCompileResult compile =
         r8FullTestBuilder
-            .noTreeShaking() // Disabled to avoid merging Itf into EnumInterface.
-            .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
             .addKeepRules(enumKeepRules.getKeepRule())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspect(this::assertEnumsAsExpected);
+            .compile();
     for (Class<?> failure : FAILURES) {
       R8TestRunResult run =
           compile
@@ -81,44 +67,6 @@
     }
   }
 
-  private void assertEnumsAsExpected(CodeInspector inspector) {
-    assertEquals(1, inspector.clazz(EnumInterface.class).getDexProgramClass().interfaces.size());
-
-    assertTrue(inspector.clazz(EnumStaticField.class).uniqueFieldWithName("X").isPresent());
-    assertTrue(inspector.clazz(EnumInstanceField.class).uniqueFieldWithName("a").isPresent());
-
-    assertEquals(
-        5,
-        inspector
-            .clazz(EnumStaticMethod.class)
-            .getDexProgramClass()
-            .getMethodCollection()
-            .numberOfDirectMethods());
-    assertEquals(1, inspector.clazz(EnumVirtualMethod.class).virtualMethods().size());
-  }
-
-  static class EnumInterfaceMain {
-
-    public static void main(String[] args) {
-      System.out.println(EnumInterface.A.ordinal());
-      System.out.println(0);
-    }
-
-    @NeverClassInline
-    enum EnumInterface implements Itf {
-      A,
-      B,
-      C
-    }
-
-    interface Itf {
-
-      default int ordinal() {
-        return -1;
-      }
-    }
-  }
-
   static class EnumStaticFieldMain {
 
     public static void main(String[] args) {
@@ -158,53 +106,4 @@
       System.out.println(10);
     }
   }
-
-  static class EnumStaticMethodMain {
-
-    @NeverClassInline
-    enum EnumStaticMethod {
-      A,
-      B,
-      C;
-
-      // Enum cannot be unboxed if it has a static method, we do not inline so the method is
-      // present.
-      @NeverInline
-      static int foo() {
-        return Math.addExact(-1, 0);
-      }
-    }
-
-    public static void main(String[] args) {
-      System.out.println(EnumStaticMethod.A.ordinal());
-      System.out.println(0);
-      System.out.println(EnumStaticMethod.foo());
-      System.out.println(-1);
-    }
-  }
-
-  static class EnumVirtualMethodMain {
-
-    public static void main(String[] args) {
-      EnumVirtualMethod e1 = EnumVirtualMethod.A;
-      System.out.println(e1.ordinal());
-      System.out.println(0);
-      System.out.println(e1.valueOf());
-      System.out.println(-1);
-    }
-
-    @NeverClassInline
-    enum EnumVirtualMethod {
-      A,
-      B,
-      C;
-
-      // Enum cannot be unboxed if it has a virtual method, we do not inline so the method is
-      // present.
-      @NeverInline
-      int valueOf() {
-        return Math.addExact(-1, 0);
-      }
-    }
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java
new file mode 100644
index 0000000..5dcf75d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java
@@ -0,0 +1,270 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+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;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InterfaceEnumUnboxingTest extends EnumUnboxingTestBase {
+
+  private static final Class<?>[] FAILURES = {
+    FailureDefaultMethodUsed.class, FailureUsedAsInterface.class,
+  };
+
+  private static final Class<?>[] SUCCESSES = {
+    SuccessAbstractMethod.class,
+    SuccessEmptyInterface.class,
+    SuccessUnusedDefaultMethod.class,
+    SuccessUnusedDefaultMethodOverride.class,
+    SuccessUnusedDefaultMethodOverrideEnum.class
+  };
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final KeepRule enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public InterfaceEnumUnboxingTest(
+      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxingFailure() throws Exception {
+    R8TestCompileResult compile =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(InterfaceEnumUnboxingTest.class)
+            .addKeepMainRules(SUCCESSES)
+            .addKeepMainRules(FAILURES)
+            .noMinification()
+            .enableMergeAnnotations()
+            .enableInliningAnnotations()
+            .enableNeverClassInliningAnnotations()
+            .addKeepRules(enumKeepRules.getKeepRule())
+            .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);
+    }
+  }
+
+  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());
+  }
+
+  static class SuccessEmptyInterface {
+
+    public static void main(String[] args) {
+      System.out.println(EnumInterface.A.ordinal());
+      System.out.println(0);
+    }
+
+    @NeverClassInline
+    enum EnumInterface implements Itf {
+      A,
+      B,
+      C
+    }
+
+    @NeverMerge
+    interface Itf {}
+  }
+
+  static class SuccessUnusedDefaultMethodOverrideEnum {
+
+    public static void main(String[] args) {
+      System.out.println(EnumInterface.A.ordinal());
+      System.out.println(0);
+    }
+
+    @NeverClassInline
+    enum EnumInterface implements Itf {
+      A,
+      B,
+      C
+    }
+
+    @NeverMerge
+    interface Itf {
+      @NeverInline
+      default int ordinal() {
+        return System.currentTimeMillis() > 0 ? 3 : -3;
+      }
+    }
+  }
+
+  static class SuccessUnusedDefaultMethodOverride {
+
+    public static void main(String[] args) {
+      System.out.println(EnumInterface.A.method());
+      System.out.println(5);
+    }
+
+    @NeverClassInline
+    enum EnumInterface implements Itf {
+      A,
+      B,
+      C;
+
+      @Override
+      @NeverInline
+      public int method() {
+        return System.currentTimeMillis() > 0 ? 5 : -5;
+      }
+    }
+
+    @NeverMerge
+    interface Itf {
+      @NeverInline
+      default int method() {
+        return System.currentTimeMillis() > 0 ? 3 : -3;
+      }
+    }
+  }
+
+  static class SuccessUnusedDefaultMethod {
+
+    public static void main(String[] args) {
+      System.out.println(EnumInterface.A.ordinal());
+      System.out.println(0);
+    }
+
+    @NeverClassInline
+    enum EnumInterface implements Itf {
+      A,
+      B,
+      C
+    }
+
+    @NeverMerge
+    interface Itf {
+      @NeverInline
+      default int method() {
+        return System.currentTimeMillis() > 0 ? 3 : -3;
+      }
+    }
+  }
+
+  static class SuccessAbstractMethod {
+
+    public static void main(String[] args) {
+      System.out.println(EnumInterface.A.method());
+      System.out.println(5);
+    }
+
+    @NeverClassInline
+    enum EnumInterface implements Itf {
+      A,
+      B,
+      C;
+
+      @Override
+      @NeverInline
+      public int method() {
+        return System.currentTimeMillis() > 0 ? 5 : -5;
+      }
+    }
+
+    @NeverMerge
+    interface Itf {
+      int method();
+    }
+  }
+
+  static class FailureDefaultMethodUsed {
+
+    public static void main(String[] args) {
+      System.out.println(EnumInterface.A.method());
+      System.out.println(3);
+    }
+
+    @NeverClassInline
+    enum EnumInterface implements Itf {
+      A,
+      B,
+      C
+    }
+
+    @NeverMerge
+    interface Itf {
+      @NeverInline
+      default int method() {
+        return System.currentTimeMillis() > 0 ? 3 : -3;
+      }
+    }
+  }
+
+  static class FailureUsedAsInterface {
+
+    public static void main(String[] args) {
+      print(EnumInterface.A);
+      System.out.println(5);
+    }
+
+    @NeverInline
+    public static void print(Itf itf) {
+      System.out.println(itf.method());
+    }
+
+    @NeverClassInline
+    enum EnumInterface implements Itf {
+      A,
+      B,
+      C;
+
+      @Override
+      @NeverInline
+      public int method() {
+        return System.currentTimeMillis() > 0 ? 5 : -5;
+      }
+    }
+
+    @NeverMerge
+    interface Itf {
+      @NeverInline
+      default int method() {
+        return System.currentTimeMillis() > 0 ? 3 : -3;
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java
similarity index 88%
rename from src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingTest.java
rename to src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java
index b388ed7..8bd41c3 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java
@@ -14,7 +14,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class OrdinalEnumUnboxingTest extends EnumUnboxingTestBase {
+public class OrdinalHashCodeEnumUnboxingTest extends EnumUnboxingTestBase {
 
   private static final Class<?> ENUM_CLASS = MyEnum.class;
 
@@ -27,7 +27,7 @@
     return enumUnboxingTestParameters();
   }
 
-  public OrdinalEnumUnboxingTest(
+  public OrdinalHashCodeEnumUnboxingTest(
       TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
@@ -36,7 +36,7 @@
 
   @Test
   public void testEnumUnboxing() throws Exception {
-    Class<Ordinal> classToTest = Ordinal.class;
+    Class<?> classToTest = OrdinalHashCode.class;
     R8TestRunResult run =
         testForR8(parameters.getBackend())
             .addProgramClasses(classToTest, ENUM_CLASS)
@@ -61,11 +61,13 @@
     C
   }
 
-  static class Ordinal {
+  static class OrdinalHashCode {
 
     public static void main(String[] args) {
       System.out.println(MyEnum.A.ordinal());
       System.out.println(0);
+      System.out.println(MyEnum.A.hashCode());
+      System.out.println(0);
     }
   }
 }
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 d8d781e..b4f97ab 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ToStringEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ToStringEnumUnboxingTest.java
@@ -65,9 +65,13 @@
     @SuppressWarnings("ConstantConditions")
     public static void main(String[] args) {
       System.out.println(MyEnum.A.toString());
+      System.out.println("A");
       System.out.println(MyEnum.A.name());
+      System.out.println("A");
       System.out.println(MyEnum.B.toString());
+      System.out.println("B");
       System.out.println(MyEnum.B.name());
+      System.out.println("B");
       try {
         System.out.println(((MyEnum) null).toString());
       } catch (NullPointerException e) {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
new file mode 100644
index 0000000..6496c7a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
@@ -0,0 +1,166 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+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;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class VirtualMethodsEnumUnboxingTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final KeepRule enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public VirtualMethodsEnumUnboxingTest(
+      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    Class<?> classToTest = VirtualMethods.class;
+    R8TestRunResult run =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(VirtualMethodsEnumUnboxingTest.class)
+            .addKeepMainRule(classToTest)
+            .addKeepRules(enumKeepRules.getKeepRule())
+            .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());
+  }
+
+  @SuppressWarnings("SameParameterValue")
+  @NeverClassInline
+  enum MyEnum {
+    A,
+    B,
+    C;
+
+    @NeverInline
+    public void print(Object o) {
+      System.out.println(o);
+    }
+
+    @NeverInline
+    public void printEnum(MyEnum e) {
+      System.out.println(e.ordinal());
+    }
+
+    @NeverInline
+    public MyEnum returnEnum(boolean bool) {
+      return bool ? MyEnum.A : MyEnum.B;
+    }
+
+    @NeverInline
+    protected void printProtected() {
+      System.out.println("protected");
+    }
+
+    @NeverInline
+    void printPackagePrivate() {
+      System.out.println("package-private");
+    }
+
+    @NeverInline
+    private void printPrivate() {
+      System.out.println("private");
+    }
+
+    @NeverInline
+    public void callPrivate() {
+      System.out.print("call: ");
+      printPrivate();
+    }
+  }
+
+  // Use two enums to test collision.
+  enum MyEnum2 {
+    A,
+    B,
+    C;
+
+    @NeverInline
+    public void print(Object o) {
+      System.out.println("2" + o);
+    }
+
+    @NeverInline
+    public void printEnum(MyEnum e) {
+      System.out.println("2" + e.ordinal());
+    }
+
+    @NeverInline
+    public MyEnum returnEnum(boolean bool) {
+      return bool ? MyEnum.B : MyEnum.C;
+    }
+  }
+
+  static class VirtualMethods {
+
+    public static void main(String[] args) {
+      testCustomMethods();
+      testCustomMethods2();
+      testNonPublicMethods();
+    }
+
+    @NeverInline
+    private static void testNonPublicMethods() {
+      MyEnum.A.printPrivate();
+      System.out.println("private");
+      MyEnum.A.printPackagePrivate();
+      System.out.println("package-private");
+      MyEnum.A.printProtected();
+      System.out.println("protected");
+      MyEnum.A.callPrivate();
+      System.out.println("call: private");
+    }
+
+    @NeverInline
+    private static void testCustomMethods() {
+      MyEnum.A.print("print");
+      System.out.println("print");
+      MyEnum.A.printEnum(MyEnum.A);
+      System.out.println(0);
+      System.out.println((MyEnum.A.returnEnum(true).ordinal()));
+      System.out.println(0);
+    }
+
+    @NeverInline
+    private static void testCustomMethods2() {
+      MyEnum2.A.print("print");
+      System.out.println("2print");
+      MyEnum2.A.printEnum(MyEnum.A);
+      System.out.println(20);
+      System.out.println((MyEnum2.A.returnEnum(true).ordinal()));
+      System.out.println(1);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
index 183ee3e..118bcce 100644
--- a/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
@@ -51,7 +51,9 @@
             .assertAllInfoMessagesMatch(
                 anyOf(
                     equalTo("Ignoring option: -optimizations"),
-                    containsString("Proguard configuration rule does not match anything")))
+                    containsString("Proguard configuration rule does not match anything"),
+                    containsString("Invalid parameter counts in MethodParameter attributes"),
+                    containsString("Methods with invalid MethodParameter attributes")))
             .assertAllWarningMessagesMatch(containsString("Ignoring option:"));
 
     int appSize = compileResult.app.applicationSize();
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java
new file mode 100644
index 0000000..6405047
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java
@@ -0,0 +1,78 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.internal.proto;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+
+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.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class Proto2BuilderOnlyReferencedFromDynamicMethodTest extends ProtoShrinkingTestBase {
+
+  private static final String MAIN = "proto2.BuilderOnlyReferencedFromDynamicMethodTestClass";
+
+  private static List<Path> PROGRAM_FILES =
+      ImmutableList.of(PROTO2_EXAMPLES_JAR, PROTO2_PROTO_JAR, PROTOBUF_LITE_JAR);
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public Proto2BuilderOnlyReferencedFromDynamicMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(PROGRAM_FILES)
+        .addKeepMainRule(MAIN)
+        .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
+        .allowAccessModification()
+        .allowDiagnosticMessages()
+        .allowUnusedProguardConfigurationRules()
+        .enableProtoShrinking()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .assertAllInfoMessagesMatch(
+            containsString("Proguard configuration rule does not match anything"))
+        .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines(
+            "false", "0", "false", "", "false", "0", "false", "0", "false", "");
+  }
+
+  private void inspect(CodeInspector outputInspector) {
+    verifyBuilderIsAbsent(outputInspector);
+  }
+
+  private void verifyBuilderIsAbsent(CodeInspector outputInspector) {
+    ClassSubject generatedMessageLiteBuilder =
+        outputInspector.clazz("com.google.protobuf.GeneratedMessageLite$Builder");
+    assertThat(generatedMessageLiteBuilder, isPresent());
+    assertFalse(generatedMessageLiteBuilder.isAbstract());
+    assertThat(
+        outputInspector.clazz("com.android.tools.r8.proto2.TestProto$Primitives$Builder"),
+        not(isPresent()));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
index 8c98b46..dc2b296 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
@@ -171,9 +171,7 @@
     assertThat(
         outputInspector.clazz(
             "com.android.tools.r8.proto2.Shrinking$HasFlaggedOffExtension$Builder"),
-        mains.equals(ImmutableList.of("proto2.HasFlaggedOffExtensionBuilderTestClass"))
-            ? isPresent()
-            : not(isPresent()));
+        not(isPresent()));
     assertThat(
         outputInspector.clazz("com.android.tools.r8.proto2.TestProto$Primitives$Builder"),
         not(isPresent()));
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/DeadConstructorWithCycleTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/DeadConstructorWithCycleTest.java
new file mode 100644
index 0000000..e64fedd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/DeadConstructorWithCycleTest.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.sideeffect;
+
+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.NeverClassInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DeadConstructorWithCycleTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public DeadConstructorWithCycleTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(DeadConstructorWithCycleTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(inspector -> assertThat(inspector.clazz(A.class), not(isPresent())))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithEmptyOutput();
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Object o1 = null;
+      Object o2 = null;
+      for (int i = 1; i <= 2; i++) {
+        o1 = new A(o1, o2);
+        o2 = new A(o1, o2);
+      }
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    Object f1;
+    Object f2;
+
+    A(Object o1, Object o2) {
+      this.f1 = o1;
+      this.f2 = o2;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java
index a46524a..3ce1ee1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java
@@ -166,7 +166,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -228,7 +228,7 @@
         testForD8()
             .release()
             .addProgramClassesAndInnerClasses(MAIN)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
     test(result, false);
@@ -244,10 +244,9 @@
             .enableMemberValuePropagationAnnotations()
             .addKeepMainRule(MAIN)
             .noMinification()
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
     test(result, true);
   }
-
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index dac8b92..a9f77ce 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -62,7 +62,7 @@
     while (it.hasNext()) {
       prev = curr != null && !curr.isGoto() ? curr : prev;
       curr = it.next();
-      if (curr.isAssumeNonNull()) {
+      if (curr.isAssumeWithNonNullAssumption()) {
         // Make sure non-null is added to the right place.
         assertTrue(prev == null
             || prev.throwsOnNullInput()
@@ -160,11 +160,11 @@
             if (count == 0) {
               // First one in the very first line: its value should not be replaced by NonNullMarker
               // because this instruction will happen _before_ non-null.
-              assertFalse(iput.value().definition.isAssumeNonNull());
+              assertFalse(iput.value().definition.isAssumeWithNonNullAssumption());
             } else if (count == 1) {
               // Second one after a safe invocation, which should use the value added by
               // NonNullMarker.
-              assertTrue(iput.object().definition.isAssumeNonNull());
+              assertTrue(iput.object().definition.isAssumeWithNonNullAssumption());
             }
             count++;
           }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
index a1c69fa..83c5243 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
@@ -141,7 +141,9 @@
             .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 0, 0);
+    // TODO(b/157427150): would be able to remove the call to requireNonNull() if we knew that it
+    //  throws an NullPointerException that does not have a message.
+    test(result, 0, 1);
   }
 
   static class ObjectsRequireNonNullTestMain {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ThrowNPEWithMessageIfParameterIsNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ThrowNPEWithMessageIfParameterIsNullTest.java
new file mode 100644
index 0000000..924278e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ThrowNPEWithMessageIfParameterIsNullTest.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ThrowNPEWithMessageIfParameterIsNullTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ThrowNPEWithMessageIfParameterIsNullTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ThrowNPEWithMessageIfParameterIsNullTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      try {
+        checkNotNull(null);
+      } catch (Exception e) {
+        System.out.println(e.getMessage());
+      }
+    }
+
+    @NeverInline
+    static void checkNotNull(Object o) {
+      if (o == null) {
+        throw new NullPointerException("Hello world!");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java
index 4cab645..52736da 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java
@@ -7,13 +7,11 @@
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -26,17 +24,14 @@
 @RunWith(Parameterized.class)
 public class DynamicTypeOptimizationTest extends TestBase {
 
-  private final boolean enableDynamicTypeOptimization;
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{1}, enable dynamic type optimization: {0}")
+  @Parameterized.Parameters(name = "{0}")
   public static List<Object[]> data() {
-    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+    return buildParameters(getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
-  public DynamicTypeOptimizationTest(
-      boolean enableDynamicTypeOptimization, TestParameters parameters) {
-    this.enableDynamicTypeOptimization = enableDynamicTypeOptimization;
+  public DynamicTypeOptimizationTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
@@ -47,10 +42,8 @@
         .addKeepMainRule(TestClass.class)
         // Keep B to ensure that we will treat it as being instantiated.
         .addKeepClassRulesWithAllowObfuscation(B.class)
-        .addOptionsModification(
-            options -> options.enableDynamicTypeOptimization = enableDynamicTypeOptimization)
         .enableInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
@@ -81,8 +74,7 @@
     MethodSubject testInstanceOfRemovalMethod =
         mainClassSubject.uniqueMethodWithName("testInstanceOfRemoval");
     assertThat(testInstanceOfRemovalMethod, isPresent());
-    assertEquals(
-        enableDynamicTypeOptimization,
+    assertTrue(
         testInstanceOfRemovalMethod
             .streamInstructions()
             .noneMatch(instruction -> instruction.isInstanceOf(aClassSubject.getFinalName())));
@@ -92,27 +84,16 @@
     MethodSubject testMethodInliningMethod =
         mainClassSubject.uniqueMethodWithName("testMethodInlining");
     assertThat(testMethodInliningMethod, isPresent());
-    assertEquals(
-        enableDynamicTypeOptimization, interfaceSubject.uniqueMethodWithName("world").isAbsent());
-    if (!enableDynamicTypeOptimization) {
-      assertThat(
-          testMethodInliningMethod, invokesMethod(interfaceSubject.uniqueMethodWithName("world")));
-    }
+    assertTrue(interfaceSubject.uniqueMethodWithName("world").isAbsent());
 
     // Verify that exclamationMark() has been rebound in testMethodRebinding() unless the dynamic
     // type optimization is disabled.
     MethodSubject testMethodRebindingMethod =
         mainClassSubject.uniqueMethodWithName("testMethodRebinding");
     assertThat(testMethodRebindingMethod, isPresent());
-    if (enableDynamicTypeOptimization) {
-      assertThat(
-          testMethodRebindingMethod,
-          invokesMethod(aClassSubject.uniqueMethodWithName("exclamationMark")));
-    } else {
-      assertThat(
-          testMethodRebindingMethod,
-          invokesMethod(interfaceSubject.uniqueMethodWithName("exclamationMark")));
-    }
+    assertThat(
+        testMethodRebindingMethod,
+        invokesMethod(aClassSubject.uniqueMethodWithName("exclamationMark")));
   }
 
   static class TestClass {
@@ -170,6 +151,7 @@
 
   static class A implements I {
 
+    @NeverInline
     @Override
     public void hello() {
       System.out.print("Hello");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
index 1cbeaae..791bbae 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -111,16 +110,14 @@
     }
   }
 
-  @Parameters(name = "{1}, enable dynamic type optimization: {0}")
+  @Parameters(name = "{0}")
   public static List<Object[]> data() {
-    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+    return buildParameters(getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
-  private final boolean enableDynamicTypeOptimization;
   private final TestParameters parameters;
 
-  public InstanceOfRemovalTest(boolean enableDynamicTypeOptimization, TestParameters parameters) {
-    this.enableDynamicTypeOptimization = enableDynamicTypeOptimization;
+  public InstanceOfRemovalTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
@@ -161,10 +158,8 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(A.class, B.class, TestClass.class)
             .addKeepMainRule(TestClass.class)
-            .addOptionsModification(
-                options -> options.enableDynamicTypeOptimization = enableDynamicTypeOptimization)
             .enableInliningAnnotations()
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expected)
             .inspector();
@@ -182,7 +177,6 @@
     MethodSubject barMethodSubject = testClass.uniqueMethodWithName("bar");
     Iterator<InstructionSubject> barInstructionIterator =
         barMethodSubject.iterateInstructions(InstructionSubject::isInstanceOf);
-    assertEquals(
-        enableDynamicTypeOptimization ? 4 : 6, Streams.stream(barInstructionIterator).count());
+    assertEquals(4, Streams.stream(barInstructionIterator).count());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java
index 289e095..3940548 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.uninstantiatedtypes;
 
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -46,7 +47,7 @@
         .addProgramClasses(I.class, J.class, Main.class)
         .addProgramClassFileData(getAimplementsI())
         .addKeepMainRule(Main.class)
-        .addKeepRules("-keep class **A { createA(); }")
+        .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .addOptionsModification(
             options -> options.enableUninstantiatedTypeOptimizationForInterfaces = true)
@@ -62,7 +63,7 @@
         .addProgramClasses(I.class, J.class, Main.class)
         .addProgramClassFileData(getAimplementsI())
         .addKeepMainRule(Main.class)
-        .addKeepRules("-keep class **A { createA(); }")
+        .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .addOptionsModification(
             options -> options.enableUninstantiatedTypeOptimizationForInterfaces = false)
@@ -76,10 +77,13 @@
   public interface J extends I {}
 
   public static class A implements J {
+
+    @NeverInline
     public static J createA() {
       return new A();
     }
 
+    @NeverInline
     public void f() {
       System.out.println("In A.f()");
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithNonNullParamCheckTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithNonNullParamCheckTest.java
index 854ac14..828f4c0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithNonNullParamCheckTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithNonNullParamCheckTest.java
@@ -7,7 +7,6 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.IsNot.not;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
@@ -107,15 +106,16 @@
                 .anyMatch(InstructionSubject::isThrow));
 
         if (shouldHaveThrow) {
-          // Check that there are no invoke instructions targeting the methods on `Static` and
-          // `Virtual`.
+          // TODO(b/157427150): Check that there are no invoke instructions targeting the methods on
+          //  `Static` and `Virtual`. This requires that we know that their methods throw
+          //  NullPointerExceptions without messages.
           Streams.stream(methodSubject.iterateInstructions())
               .filter(InstructionSubject::isInvoke)
               .forEach(
                   ins -> {
                     ClassSubject clazz = inspector.clazz(ins.getMethod().holder.toSourceString());
-                    assertNotEquals(clazz.getOriginalName(), Static.class.getTypeName());
-                    assertNotEquals(clazz.getOriginalName(), Virtual.class.getTypeName());
+                    // assertNotEquals(clazz.getOriginalName(), Static.class.getTypeName());
+                    // assertNotEquals(clazz.getOriginalName(), Virtual.class.getTypeName());
                   });
         }
 
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index 8ea5334..b4d2c3a 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -132,7 +132,8 @@
     }
 
     @Override
-    public BasicBlock split(IRCode code, ListIterator<BasicBlock> blockIterator) {
+    public BasicBlock split(
+        IRCode code, ListIterator<BasicBlock> blockIterator, boolean keepCatchHandlers) {
       throw new Unimplemented();
     }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java b/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java
index 904f69f..6df1462 100644
--- a/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java
+++ b/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java
@@ -7,12 +7,13 @@
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.kotlin.metadata.KotlinMetadataTestBase;
 import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.io.IOException;
@@ -27,7 +28,7 @@
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class KotlinxCoroutinesTestRunner extends KotlinTestBase {
+public class KotlinxCoroutinesTestRunner extends KotlinMetadataTestBase {
 
   private static final String PKG = "kotlinx-coroutines-1.3.6";
   private static final Path BASE_LIBRARY =
@@ -63,22 +64,44 @@
 
   @Test
   public void runKotlinxCoroutinesTests_smoke() throws Exception {
-    Path baseJar =
-        kotlinc(KOTLINC, targetVersion)
-            .addArguments(
-                "-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi",
-                "-Xuse-experimental=kotlinx.coroutines.ObsoleteCoroutinesApi",
-                "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi")
-            .addClasspathFiles(DEPENDENCIES)
-            .addClasspathFiles(BASE_LIBRARY)
-            .addSourceFiles(TEST_SOURCES)
-            .compile();
-    runTestsInJar(baseJar, BASE_LIBRARY);
+    runTestsInJar(compileTestSources(BASE_LIBRARY), BASE_LIBRARY);
   }
 
-  private void runTestsInJar(Path testJar, Path deps) throws Exception {
+  @Test
+  public void runKotlinxCoroutinesTests_r8() throws Exception {
+    Path baseJar =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(BASE_LIBRARY)
+            .addKeepAllClassesRule()
+            .addKeepAllAttributes()
+            // The BASE_LIBRARY contains proguard rules that do not match.
+            .allowUnusedProguardConfigurationRules()
+            .addKeepRules(
+                "-dontwarn reactor.blockhound.integration.BlockHoundIntegration",
+                "-dontwarn org.junit.runners.model.Statement",
+                "-dontwarn org.junit.rules.TestRule")
+            .compile()
+            .inspect(inspector -> assertEqualMetadata(new CodeInspector(BASE_LIBRARY), inspector))
+            .writeToZip();
+    Path testJar = compileTestSources(baseJar);
+    runTestsInJar(testJar, baseJar);
+  }
+
+  private Path compileTestSources(Path baseJar) throws Exception {
+    return kotlinc(KOTLINC, targetVersion)
+        .addArguments(
+            "-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi",
+            "-Xuse-experimental=kotlinx.coroutines.ObsoleteCoroutinesApi",
+            "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi")
+        .addClasspathFiles(DEPENDENCIES)
+        .addClasspathFiles(baseJar)
+        .addSourceFiles(TEST_SOURCES)
+        .compile();
+  }
+
+  private void runTestsInJar(Path testJar, Path baseJar) throws Exception {
     List<Path> dependencies = new ArrayList<>(DEPENDENCIES);
-    dependencies.add(deps);
+    dependencies.add(baseJar);
     dependencies.add(testJar);
     ZipUtils.iter(
         testJar.toString(),
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
index 87b5d10..99aaf1b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
@@ -3,13 +3,25 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertNotNull;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNull;
+
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
+import com.android.tools.r8.kotlin.KotlinMetadataWriter;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import junit.framework.TestCase;
+import kotlinx.metadata.jvm.KotlinClassHeader;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
 
-abstract class KotlinMetadataTestBase extends AbstractR8KotlinTestBase {
+public abstract class KotlinMetadataTestBase extends AbstractR8KotlinTestBase {
 
-  KotlinMetadataTestBase(KotlinTargetVersion targetVersion) {
+  public KotlinMetadataTestBase(KotlinTargetVersion targetVersion) {
     super(targetVersion);
   }
 
@@ -27,4 +39,29 @@
 
   static final String KT_FUNCTION1 = "Lkotlin/Function1;";
   static final String KT_COMPARABLE = "Lkotlin/Comparable;";
+
+  public void assertEqualMetadata(CodeInspector originalInspector, CodeInspector rewrittenInspector)
+      throws Exception {
+    for (FoundClassSubject clazzSubject : originalInspector.allClasses()) {
+      ClassSubject r8Clazz = rewrittenInspector.clazz(clazzSubject.getOriginalName());
+      assertThat(r8Clazz, isPresent());
+      KotlinClassMetadata originalMetadata = clazzSubject.getKotlinClassMetadata();
+      KotlinClassMetadata rewrittenMetadata = r8Clazz.getKotlinClassMetadata();
+      if (originalMetadata == null) {
+        assertNull(rewrittenMetadata);
+        continue;
+      }
+      assertNotNull(rewrittenMetadata);
+      KotlinClassHeader originalHeader = originalMetadata.getHeader();
+      KotlinClassHeader rewrittenHeader = rewrittenMetadata.getHeader();
+      TestCase.assertEquals(originalHeader.getKind(), rewrittenHeader.getKind());
+      // TODO(b/154199572): Should we check for meta-data version?
+      TestCase.assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName());
+      // We cannot assert equality of the data since it may be ordered differently. Instead we use
+      // the KotlinMetadataWriter.
+      String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata);
+      String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata);
+      TestCase.assertEquals(expected, actual);
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
new file mode 100644
index 0000000..c6dd4fd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
@@ -0,0 +1,96 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteCrossinlineAnonFunctionTest extends KotlinMetadataTestBase {
+
+  private final String EXPECTED = StringUtils.lines("foo");
+  private static final String PKG_LIB = PKG + ".crossinline_anon_lib";
+  private static final String PKG_APP = PKG + ".crossinline_anon_app";
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  public MetadataRewriteCrossinlineAnonFunctionTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+  private final TestParameters parameters;
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+      Path baseLibJar =
+          kotlinc(KOTLINC, targetVersion)
+              .addSourceFiles(
+                  getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib"))
+              .compile();
+      libJars.put(targetVersion, baseLibJar);
+    }
+  }
+
+  @Test
+  public void smokeTest() throws Exception {
+    Path libJar = libJars.get(targetVersion);
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(
+                getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testMetadataForLib() throws Exception {
+    Path libJar =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(libJars.get(targetVersion))
+            // Allow renaming A to ensure that we rename in the flexible upper bound type.
+            .addKeepAllClassesRule()
+            .addKeepAllAttributes()
+            .compile()
+            .writeToZip();
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(
+                getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addProgramFiles(output)
+        .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineConcreteFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineConcreteFunctionTest.java
new file mode 100644
index 0000000..ce26b77
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineConcreteFunctionTest.java
@@ -0,0 +1,96 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteCrossinlineConcreteFunctionTest extends KotlinMetadataTestBase {
+
+  private final String EXPECTED = StringUtils.lines("foo");
+  private static final String PKG_LIB = PKG + ".crossinline_concrete_lib";
+  private static final String PKG_APP = PKG + ".crossinline_concrete_app";
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  public MetadataRewriteCrossinlineConcreteFunctionTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+  private final TestParameters parameters;
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+      Path baseLibJar =
+          kotlinc(KOTLINC, targetVersion)
+              .addSourceFiles(
+                  getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib"))
+              .compile();
+      libJars.put(targetVersion, baseLibJar);
+    }
+  }
+
+  @Test
+  public void smokeTest() throws Exception {
+    Path libJar = libJars.get(targetVersion);
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(
+                getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testMetadataForLib() throws Exception {
+    Path libJar =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(libJars.get(targetVersion))
+            .addKeepAllClassesRule()
+            .addKeepAllAttributes()
+            .compile()
+            .writeToZip();
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(
+                getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
new file mode 100644
index 0000000..50007fb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
@@ -0,0 +1,174 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteDelegatedPropertyTest extends KotlinMetadataTestBase {
+
+  private static final String PKG_LIB = PKG + ".delegated_property_lib";
+  private static final String PKG_APP = PKG + ".delegated_property_app";
+  private static final String EXPECTED_MAIN =
+      StringUtils.lines(
+          "foo has been assigned to 'customDelegate' in"
+              + " com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates",
+          "foo has been read in CustomDelegate from 'customDelegate' in"
+              + " com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates",
+          "foo",
+          "read-only has been read in CustomReadOnlyDelegate from 'customReadOnlyDelegate' in"
+              + " com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates",
+          "read-only",
+          "Generating lazy string",
+          "42",
+          "Hello World!",
+          "Hello World!",
+          "Jane Doe",
+          "42",
+          "Checking property for image",
+          "Checking property for text",
+          "image_id",
+          "text_id");
+  private static final String EXPECTED_REFLECT =
+      StringUtils.lines(
+          "foo has been assigned to 'customDelegate' in"
+              + " com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates",
+          "foo");
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  public MetadataRewriteDelegatedPropertyTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  private final TestParameters parameters;
+  private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+      Path baseLibJar =
+          kotlinc(KOTLINC, targetVersion)
+              .addSourceFiles(
+                  getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib"))
+              .compile();
+      libJars.put(targetVersion, baseLibJar);
+    }
+  }
+
+  @Test
+  public void smokeTest() throws Exception {
+    Path libJar = libJars.get(targetVersion);
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(
+                getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+        .assertSuccessWithOutput(EXPECTED_MAIN);
+  }
+
+  @Test
+  public void smokeTestReflect() throws Exception {
+    Path libJar = libJars.get(targetVersion);
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(
+                getKotlinFileInTest(
+                    DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main_reflect"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(
+            ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG_APP + ".Main_reflectKt")
+        .assertSuccessWithOutput(EXPECTED_REFLECT);
+  }
+
+  @Test
+  public void testMetadataForLib() throws Exception {
+    Path libJar =
+        testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
+            .addProgramFiles(libJars.get(targetVersion))
+            .addKeepRules("-keep class " + PKG_LIB + ".Delegates { *; }")
+            .addKeepRules("-keep class " + PKG_LIB + ".Resource { *; }")
+            .addKeepRules("-keep class " + PKG_LIB + ".User { *; }")
+            .addKeepRules("-keep class " + PKG_LIB + ".ProvidedDelegates { *; }")
+            .compile()
+            // TODO(b/157988734): When we start modeling localDelegatedProperties, inspect the code.
+            .writeToZip();
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(
+                getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+        .assertSuccessWithOutput(EXPECTED_MAIN);
+  }
+
+  @Test
+  public void testMetadataForReflect() throws Exception {
+    Path libJar =
+        testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
+            .addProgramFiles(libJars.get(targetVersion))
+            .addKeepRules("-keep class " + PKG_LIB + ".Delegates { *; }")
+            .addKeepRules("-keep class " + PKG_LIB + ".Resource { *; }")
+            .addKeepRules("-keep class " + PKG_LIB + ".CustomDelegate { *; }")
+            .compile()
+            .writeToZip();
+    ProcessResult result =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(
+                getKotlinFileInTest(
+                    DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main_reflect"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compileRaw();
+    assertEquals(1, result.exitCode);
+    assertThat(
+        result.stderr,
+        containsString(
+            "unsupported [reference to the synthetic extension property for a Java get/set"
+                + " method]"));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
index a17f7dc..71d888c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
@@ -4,25 +4,12 @@
 
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertNotNull;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNull;
-
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.kotlin.KotlinMetadataWriter;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
-import java.io.IOException;
 import java.util.Collection;
-import java.util.concurrent.ExecutionException;
-import kotlinx.metadata.jvm.KotlinClassHeader;
-import kotlinx.metadata.jvm.KotlinClassMetadata;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -53,31 +40,8 @@
         .addKeepKotlinMetadata()
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .compile()
-        .inspect(this::inspect);
-  }
-
-  public void inspect(CodeInspector inspector) throws IOException, ExecutionException {
-    CodeInspector stdLibInspector = new CodeInspector(ToolHelper.getKotlinStdlibJar());
-    for (FoundClassSubject clazzSubject : stdLibInspector.allClasses()) {
-      ClassSubject r8Clazz = inspector.clazz(clazzSubject.getOriginalName());
-      assertThat(r8Clazz, isPresent());
-      KotlinClassMetadata originalMetadata = clazzSubject.getKotlinClassMetadata();
-      KotlinClassMetadata rewrittenMetadata = r8Clazz.getKotlinClassMetadata();
-      if (originalMetadata == null) {
-        assertNull(rewrittenMetadata);
-        continue;
-      }
-      assertNotNull(rewrittenMetadata);
-      KotlinClassHeader originalHeader = originalMetadata.getHeader();
-      KotlinClassHeader rewrittenHeader = rewrittenMetadata.getHeader();
-      assertEquals(originalHeader.getKind(), rewrittenHeader.getKind());
-      // TODO(b/154199572): Should we check for meta-data version?
-      assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName());
-      // We cannot assert equality of the data since it may be ordered differently. Instead we use
-      // the KotlinMetadataWriter.
-      String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata);
-      String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata);
-      assertEquals(expected, actual);
-    }
+        .inspect(
+            inspector ->
+                assertEqualMetadata(new CodeInspector(ToolHelper.getKotlinStdlibJar()), inspector));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_anon_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_anon_app/main.kt
new file mode 100644
index 0000000..99a3882
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_anon_app/main.kt
@@ -0,0 +1,18 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.crossinline_anon_app
+
+import com.android.tools.r8.kotlin.metadata.crossinline_anon_lib.Context
+import com.android.tools.r8.kotlin.metadata.crossinline_anon_lib.Handler
+
+fun main() {
+  Handler({ context, throwable ->
+    println(context)
+  }).handle(object : Context {
+    override fun toString(): String {
+      return "foo"
+    }
+  }, NullPointerException())
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_anon_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_anon_lib/lib.kt
new file mode 100644
index 0000000..b820f4a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_anon_lib/lib.kt
@@ -0,0 +1,19 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.crossinline_anon_lib
+
+public interface Context {
+
+}
+
+public inline fun Handler(crossinline handler: (Context, Throwable) -> Unit): Handler =
+  object : Handler {
+    override fun handle(context: Context, exception: Throwable) =
+      handler.invoke(context, exception)
+  }
+
+public interface Handler {
+  fun handle(context: Context, exception: Throwable)
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_concrete_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_concrete_app/main.kt
new file mode 100644
index 0000000..4c5c3a7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_concrete_app/main.kt
@@ -0,0 +1,18 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.crossinline_concrete_app
+
+import com.android.tools.r8.kotlin.metadata.crossinline_concrete_lib.Context
+import com.android.tools.r8.kotlin.metadata.crossinline_concrete_lib.Handler
+
+fun main() {
+  Handler({ context, throwable ->
+    println(context)
+  }).handle(object : Context {
+    override fun toString(): String {
+      return "foo"
+    }
+  }, NullPointerException())
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_concrete_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_concrete_lib/lib.kt
new file mode 100644
index 0000000..cf5da42
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_concrete_lib/lib.kt
@@ -0,0 +1,25 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.crossinline_concrete_lib
+
+public interface Context {
+
+}
+
+public inline fun Handler(crossinline handler: (Context, Throwable) -> Unit): Handler =
+  ConcreteClass().getHandler(handler)
+
+class ConcreteClass {
+
+  inline fun getHandler(crossinline handler: (Context, Throwable) -> Unit): Handler =
+    object : Handler {
+      override fun handle(context: Context, exception: Throwable) =
+        handler.invoke(context, exception)
+    }
+}
+
+public interface Handler {
+  fun handle(context: Context, exception: Throwable)
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main.kt
new file mode 100644
index 0000000..7bfd637
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main.kt
@@ -0,0 +1,31 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin.metadata.delegated_property_app
+
+import com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates
+import com.android.tools.r8.kotlin.metadata.delegated_property_lib.ProvidedDelegates
+import com.android.tools.r8.kotlin.metadata.delegated_property_lib.Resource
+import com.android.tools.r8.kotlin.metadata.delegated_property_lib.User
+
+fun main() {
+
+  val delegates = Delegates()
+  delegates.customDelegate = Resource("foo");
+  println(delegates.customDelegate)
+  println(delegates.customReadOnlyDelegate)
+  println(delegates.lazyString)
+  println(delegates.localDelegatedProperties { Resource("Hello World!") })
+
+  val user = User(mapOf(
+    "name" to "Jane Doe",
+    "age"  to 42
+  ))
+
+  println(user.name)
+  println(user.age)
+
+  val providedDelegates = ProvidedDelegates()
+  println(providedDelegates.image)
+  println(providedDelegates.text)
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main_reflect.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main_reflect.kt
new file mode 100644
index 0000000..0369a86
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main_reflect.kt
@@ -0,0 +1,21 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin.metadata.delegated_property_app
+
+import com.android.tools.r8.kotlin.metadata.delegated_property_lib.CustomDelegate
+import com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates
+import com.android.tools.r8.kotlin.metadata.delegated_property_lib.Resource
+import kotlin.reflect.KMutableProperty0
+import kotlin.reflect.jvm.isAccessible
+
+fun main() {
+  val delegates = Delegates()
+  delegates.customDelegate = Resource("foo");
+  println(delegates::customDelegate.getResource())
+}
+
+inline fun KMutableProperty0<*>.getResource(): Resource {
+  isAccessible = true
+  return (getDelegate() as CustomDelegate).resource
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_lib/lib.kt
new file mode 100644
index 0000000..54c58df
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_lib/lib.kt
@@ -0,0 +1,88 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.delegated_property_lib
+
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+class Resource(private var s : String = "") {
+
+  override fun toString(): String {
+    return s;
+  }
+}
+
+class CustomDelegate(var resource: Resource = Resource()) {
+
+  operator fun getValue(thisRef: Any?, property: KProperty<*>): Resource {
+    println("$resource has been read in CustomDelegate from '" +
+            "${property.name}' in ${thisRef?.javaClass?.typeName}")
+    return resource;
+  }
+
+  operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Resource) {
+    println("$value has been assigned to '${property.name}'" +
+            " in ${thisRef?.javaClass?.typeName}")
+    this.resource = value
+  }
+}
+
+class CustomReadOnlyDelegate(private var resource : Resource = Resource("read-only"))
+  : ReadOnlyProperty<Any?, Resource> {
+  override fun getValue(thisRef: Any?, property: KProperty<*>): Resource {
+    println("$resource has been read in CustomReadOnlyDelegate" +
+            " from '${property.name}' in ${thisRef?.javaClass?.typeName}")
+    return resource;
+  }
+}
+
+class Delegates {
+
+  var customDelegate : Resource by CustomDelegate()
+  val customReadOnlyDelegate : Resource by CustomReadOnlyDelegate()
+  val lazyString : String by lazy {
+    println("Generating lazy string")
+    "42"
+  }
+
+  fun localDelegatedProperties(compute: () -> Resource) : Resource {
+    val foo by lazy(compute)
+    println(foo)
+    return foo
+  }
+}
+class User(val map : Map<String, Any?>) {
+  val name : String by map
+  val age: Int by map
+}
+
+class ResourceDelegate(val r : Resource): ReadOnlyProperty<ProvidedDelegates, Resource> {
+  override fun getValue(thisRef: ProvidedDelegates, property: KProperty<*>): Resource {
+    return r
+  }
+}
+
+class ResourceLoader(val id: Resource) {
+  operator fun provideDelegate(
+    thisRef: ProvidedDelegates,
+    prop: KProperty<*>
+  ): ReadOnlyProperty<ProvidedDelegates, Resource> {
+    checkProperty(prop.name)
+    return ResourceDelegate(id)
+  }
+
+  private fun checkProperty(name: String) {
+    println("Checking property for " + name)
+  }
+}
+
+class ProvidedDelegates {
+  fun bindResource(id: String): ResourceLoader {
+    return ResourceLoader(Resource(id))
+  }
+
+  val image by bindResource("image_id")
+  val text by bindResource("text_id")
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index cacf66b..26186aa 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -635,9 +635,10 @@
         .forEach(
             p -> {
               try {
-                CodeInspector i = new CodeInspector(AndroidApp.builder().addProgramFiles(p).build());
+                CodeInspector i =
+                    new CodeInspector(AndroidApp.builder().addProgramFiles(p).build());
                 assertFalse("Found " + clazz + " in file " + p, i.clazz(clazz).isPresent());
-              } catch (IOException | ExecutionException e) {
+              } catch (IOException e) {
                 e.printStackTrace();
               }
             });
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
index 48fc320..5562395 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
@@ -13,8 +13,10 @@
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.ForceInline;
+import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import org.junit.Test;
@@ -77,6 +79,18 @@
           assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
         });
   }
+
+  @Override
+  public void configure(R8TestBuilder<?> builder) {
+    builder.applyIf(mode == CompilationMode.RELEASE, R8TestBuilder::enableForceInliningAnnotations);
+  }
+
+  @Override
+  public void inspect(CodeInspector inspector) {
+    if (mode == CompilationMode.RELEASE) {
+      assertEquals(compat ? 2 : 1, inspector.clazz(Main.class).allMethods().size());
+    }
+  }
 }
 
 class Main {
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/LineNumberRangeTest.java b/src/test/java/com/android/tools/r8/naming/retrace/LineNumberRangeTest.java
new file mode 100644
index 0000000..02ed76d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/retrace/LineNumberRangeTest.java
@@ -0,0 +1,284 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.retrace;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.Matchers;
+import com.android.tools.r8.utils.codeinspector.Matchers.LinePosition;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class LineNumberRangeTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  private final String classDescriptor = "Lcom/android/tools/r8/naming/retrace/Main;";
+
+  private final LinePosition EXPECTED_STACK_TRACE =
+      LinePosition.stack(
+          LinePosition.create(
+              Reference.methodFromDescriptor(classDescriptor, "method3", "()V"),
+              88,
+              88,
+              "LineNumberRangeTest.java"),
+          LinePosition.create(
+              Reference.methodFromDescriptor(classDescriptor, "method2", "()V"),
+              94,
+              94,
+              "LineNumberRangeTest.java"),
+          LinePosition.create(
+              Reference.methodFromDescriptor(classDescriptor, "method1", "()V"),
+              102,
+              102,
+              "LineNumberRangeTest.java"),
+          LinePosition.create(
+              Reference.methodFromDescriptor(classDescriptor, "main", "()V"),
+              108,
+              108,
+              "LineNumberRangeTest.java"));
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public LineNumberRangeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    StackTrace expectedStackTrace =
+        testForRuntime(parameters)
+            .addProgramClassFileData(MainDump.dump())
+            .run(parameters.getRuntime(), Main.class)
+            .assertFailure()
+            .map(StackTrace::extractFromJvm);
+    assertThat(expectedStackTrace, Matchers.containsLinePositions(EXPECTED_STACK_TRACE));
+  }
+
+  @Test
+  public void testSourceFileAndLineNumberTable() throws Exception {
+    R8TestRunResult result =
+        testForR8(parameters.getBackend())
+            .addProgramClassFileData(MainDump.dump())
+            .setMode(CompilationMode.DEBUG)
+            .addKeepMainRule(Main.class)
+            .addKeepAttributes(
+                ProguardKeepAttributes.SOURCE_FILE, ProguardKeepAttributes.LINE_NUMBER_TABLE)
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(
+                options -> {
+                  options.enableInlining = false;
+                })
+            .run(parameters.getRuntime(), Main.class)
+            .assertFailure();
+    // Extract actual stack trace and retraced stack trace from failed run result.
+    StackTrace actualStackTrace;
+    if (parameters.isCfRuntime()) {
+      actualStackTrace = StackTrace.extractFromJvm(result.getStdErr());
+    } else {
+      actualStackTrace =
+          StackTrace.extractFromArt(result.getStdErr(), parameters.getRuntime().asDex().getVm());
+    }
+    StackTrace retracedStackTrace = actualStackTrace.retrace(result.proguardMap());
+    assertThat(retracedStackTrace, Matchers.containsLinePositions(EXPECTED_STACK_TRACE));
+  }
+
+  // This class is generated by taking the output of InliningRetraceTest without running the
+  // line number optimizer.
+  public static class MainDump implements Opcodes {
+
+    public static byte[] dump() {
+
+      ClassWriter classWriter = new ClassWriter(0);
+      MethodVisitor methodVisitor;
+
+      classWriter.visit(
+          V1_8,
+          ACC_SUPER,
+          "com/android/tools/r8/naming/retrace/Main",
+          null,
+          "java/lang/Object",
+          null);
+
+      classWriter.visitSource("LineNumberRangeTest.java", null);
+
+      {
+        methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(82, label0);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(1, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor =
+            classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "method3", "()V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(86, label0);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("In method3");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(87, label1);
+        methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
+        methodVisitor.visitInsn(LCONST_0);
+        methodVisitor.visitInsn(LCMP);
+        Label label2 = new Label();
+        methodVisitor.visitJumpInsn(IFLE, label2);
+        Label label3 = new Label();
+        methodVisitor.visitLabel(label3);
+        methodVisitor.visitLineNumber(88, label3);
+        methodVisitor.visitInsn(ACONST_NULL);
+        methodVisitor.visitInsn(ATHROW);
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitLineNumber(90, label2);
+        methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(4, 2);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor =
+            classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "method2", "(I)V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(92, label0);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("In method2");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(93, label1);
+        methodVisitor.visitInsn(ICONST_0);
+        methodVisitor.visitVarInsn(ISTORE, 1);
+        Label label2 = new Label();
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitVarInsn(ILOAD, 1);
+        methodVisitor.visitIntInsn(BIPUSH, 10);
+        Label label3 = new Label();
+        methodVisitor.visitJumpInsn(IF_ICMPLT, label3);
+        Label label4 = new Label();
+        methodVisitor.visitLabel(label4);
+        methodVisitor.visitLineNumber(96, label4);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitLabel(label3);
+        methodVisitor.visitLineNumber(94, label3);
+        methodVisitor.visitFrame(Opcodes.F_CHOP, 1, null, 0, null);
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC, "com/android/tools/r8/naming/retrace/Main", "method3", "()V", false);
+        Label label5 = new Label();
+        methodVisitor.visitLabel(label5);
+        methodVisitor.visitLineNumber(93, label5);
+        methodVisitor.visitInsn(ACONST_NULL);
+        methodVisitor.visitInsn(ATHROW);
+        methodVisitor.visitMaxs(2, 2);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor =
+            classWriter.visitMethod(
+                ACC_PUBLIC | ACC_STATIC, "method1", "(Ljava/lang/String;)V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(100, label0);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("In method1");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(101, label1);
+        methodVisitor.visitInsn(ICONST_0);
+        methodVisitor.visitVarInsn(ISTORE, 1);
+        Label label2 = new Label();
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[] {Opcodes.INTEGER}, 0, null);
+        methodVisitor.visitVarInsn(ILOAD, 1);
+        methodVisitor.visitIntInsn(BIPUSH, 10);
+        Label label3 = new Label();
+        methodVisitor.visitJumpInsn(IF_ICMPGE, label3);
+        Label label4 = new Label();
+        methodVisitor.visitLabel(label4);
+        methodVisitor.visitLineNumber(102, label4);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC, "java/lang/Integer", "parseInt", "(Ljava/lang/String;)I", false);
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC, "com/android/tools/r8/naming/retrace/Main", "method2", "(I)V", false);
+        Label label5 = new Label();
+        methodVisitor.visitLabel(label5);
+        methodVisitor.visitLineNumber(101, label5);
+        methodVisitor.visitIincInsn(1, 1);
+        methodVisitor.visitJumpInsn(GOTO, label2);
+        methodVisitor.visitLabel(label3);
+        methodVisitor.visitLineNumber(104, label3);
+        methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(2, 2);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor =
+            classWriter.visitMethod(
+                ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(107, label0);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("In main");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(108, label1);
+        methodVisitor.visitLdcInsn("1");
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC,
+            "com/android/tools/r8/naming/retrace/Main",
+            "method1",
+            "(Ljava/lang/String;)V",
+            false);
+        Label label2 = new Label();
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitLineNumber(109, label2);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(2, 1);
+        methodVisitor.visitEnd();
+      }
+      classWriter.visitEnd();
+
+      return classWriter.toByteArray();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
index adc5cb8..b4b90a4 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
@@ -9,6 +9,8 @@
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.List;
@@ -28,7 +30,9 @@
 
   public StackTrace expectedStackTrace;
 
-  public void configure(R8TestBuilder builder) {}
+  public void configure(R8TestBuilder<?> builder) {}
+
+  public void inspect(CodeInspector inspector) {}
 
   public Collection<Class<?>> getClasses() {
     return ImmutableList.of(getMainClass());
@@ -42,7 +46,7 @@
     expectedStackTrace =
         testForJvm()
             .addTestClasspath()
-            .run(getMainClass())
+            .run(CfRuntime.getSystemRuntime(), getMainClass())
             .assertFailure()
             .map(StackTrace::extractFromJvm);
   }
@@ -53,12 +57,13 @@
     R8TestRunResult result =
         (compat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
             .setMode(mode)
-            .enableProguardTestOptions()
             .addProgramClasses(getClasses())
             .addKeepMainRule(getMainClass())
             .addKeepRules(keepRules)
             .apply(this::configure)
             .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspect(this::inspect)
             .run(parameters.getRuntime(), getMainClass())
             .assertFailure();
 
diff --git a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderMultipleCallsTest.java b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderMultipleCallsTest.java
new file mode 100644
index 0000000..688a96f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderMultipleCallsTest.java
@@ -0,0 +1,141 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.rewrite;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNull;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DataEntryResource;
+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.origin.Origin;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ServiceLoader;
+import java.util.concurrent.ExecutionException;
+import java.util.zip.ZipFile;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ServiceLoaderMultipleCallsTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String EXPECTED_OUTPUT = StringUtils.lines("Hello World!", "Hello World!");
+
+  public interface Service {
+
+    void print();
+  }
+
+  public static class ServiceImpl implements Service {
+
+    @Override
+    public void print() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class ServiceImpl2 implements Service {
+
+    @Override
+    public void print() {
+      System.out.println("Hello World 2!");
+    }
+  }
+
+  public static class MainRunner {
+
+    public static void main(String[] args) {
+      run1();
+      run2();
+    }
+
+    @NeverInline
+    public static void run1() {
+      for (Service x : ServiceLoader.load(Service.class, Service.class.getClassLoader())) {
+        x.print();
+      }
+    }
+
+    @NeverInline
+    public static void run2() {
+      for (Service x : ServiceLoader.load(Service.class, Service.class.getClassLoader())) {
+        x.print();
+      }
+    }
+  }
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ServiceLoaderMultipleCallsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRewritings() throws IOException, CompilationFailedException, ExecutionException {
+    Path path = temp.newFile("out.zip").toPath();
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ServiceLoaderMultipleCallsTest.class)
+        .addKeepMainRule(MainRunner.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableInliningAnnotations()
+        .addDataEntryResources(
+            DataEntryResource.fromBytes(
+                StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
+                "META-INF/services/" + Service.class.getTypeName(),
+                Origin.unknown()))
+        .compile()
+        .writeToZip(path)
+        .run(parameters.getRuntime(), MainRunner.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT)
+        .inspect(
+            inspector -> {
+              // Check that we have actually rewritten the calls to ServiceLoader.load.
+              assertEquals(0, getServiceLoaderLoads(inspector, MainRunner.class));
+              // Check that the synthesize service loader class holds two methods, one for each
+              // context.
+              ClassSubject serviceLoaderMethods = inspector.clazz("$$ServiceLoaderMethods");
+              assertThat(serviceLoaderMethods, isPresent());
+              assertEquals(2, serviceLoaderMethods.allMethods().size());
+            });
+
+    // Check that we have removed the service configuration from META-INF/services.
+    ZipFile zip = new ZipFile(path.toFile());
+    assertNull(zip.getEntry("META-INF/services/" + Service.class.getTypeName()));
+  }
+
+  private static long getServiceLoaderLoads(CodeInspector inspector, Class<?> clazz) {
+    ClassSubject classSubject = inspector.clazz(clazz);
+    assertTrue(classSubject.isPresent());
+    return classSubject.allMethods().stream()
+        .mapToLong(
+            method ->
+                method
+                    .streamInstructions()
+                    .filter(ServiceLoaderMultipleCallsTest::isServiceLoaderLoad)
+                    .count())
+        .sum();
+  }
+
+  private static boolean isServiceLoaderLoad(InstructionSubject instruction) {
+    return instruction.isInvokeStatic()
+        && instruction.getMethod().qualifiedName().contains("ServiceLoader.load");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/AbstractMethodOnNonAbstractClassTest.java b/src/test/java/com/android/tools/r8/shaking/AbstractMethodOnNonAbstractClassTest.java
new file mode 100644
index 0000000..2f95543
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/AbstractMethodOnNonAbstractClassTest.java
@@ -0,0 +1,131 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.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 AbstractMethodOnNonAbstractClassTest extends TestBase {
+
+  private static final String DEX2OAT_WARNING =
+      "is abstract, but the declaring class is neither abstract nor an interface";
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public AbstractMethodOnNonAbstractClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testCompat() throws Exception {
+    R8TestCompileResult compileResult =
+        testForR8Compat(parameters.getBackend())
+            .addInnerClasses(AbstractMethodOnNonAbstractClassTest.class)
+            .addKeepMainRule(TestClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+
+    // A is not made abstract in compat mode.
+    ClassSubject classSubject = compileResult.inspector().clazz(A.class);
+    assertThat(classSubject, isPresent());
+    assertFalse(classSubject.isAbstract());
+
+    // A.m() is also not made abstract in compat mode.
+    MethodSubject methodSubject = classSubject.uniqueMethodWithName("m");
+    assertThat(methodSubject, isPresent());
+    assertFalse(methodSubject.isAbstract());
+
+    if (parameters.isDexRuntime()) {
+      compileResult
+          .runDex2Oat(parameters.getRuntime())
+          .assertStderrMatches(not(containsString(DEX2OAT_WARNING)));
+    }
+
+    compileResult
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  @Test
+  public void testFull() throws Exception {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(AbstractMethodOnNonAbstractClassTest.class)
+            .addKeepMainRule(TestClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+
+    // A is made abstract in full mode.
+    ClassSubject classSubject = compileResult.inspector().clazz(A.class);
+    assertThat(classSubject, isPresent());
+    assertTrue(classSubject.isAbstract());
+
+    // A.m() is also made abstract in full mode.
+    MethodSubject methodSubject = classSubject.uniqueMethodWithName("m");
+    assertThat(methodSubject, isPresent());
+    assertTrue(methodSubject.isAbstract());
+
+    if (parameters.isDexRuntime()) {
+      // There is no warning due to both A and A.m() being abstract.
+      compileResult
+          .runDex2Oat(parameters.getRuntime())
+          .assertStderrMatches(not(containsString(DEX2OAT_WARNING)));
+    }
+
+    compileResult
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      A b = System.currentTimeMillis() > 0 ? new B() : new C();
+      b.m();
+    }
+  }
+
+  static class A {
+
+    // Never called directly on A, thus can be made abstract.
+    void m() {}
+  }
+
+  static class B extends A {
+
+    @Override
+    void m() {
+      System.out.println("Hello world!");
+    }
+  }
+
+  static class C extends A {
+
+    @Override
+    void m() {
+      throw new RuntimeException();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
index 105bf6b..512a9ff 100644
--- a/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
@@ -103,8 +103,7 @@
         */
         "getstatic " + client.name + "/" + obj2.name + " " + itf2.getDescriptor(),
         "invokevirtual java/io/PrintStream/print(Ljava/lang/Object;)V",
-        "return"
-    );
+        "return");
 
     final String mainClassName = mainClass.name;
     String proguardConfig = StringUtils.lines(
diff --git a/src/test/java/com/android/tools/r8/shaking/array/DeadArrayLengthTest.java b/src/test/java/com/android/tools/r8/shaking/array/DeadArrayLengthTest.java
index 241abc5..14259ca 100644
--- a/src/test/java/com/android/tools/r8/shaking/array/DeadArrayLengthTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/array/DeadArrayLengthTest.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.shaking.array;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
@@ -48,13 +47,8 @@
     assertEquals(0, countArrayLength(nonNull));
 
     MethodSubject nullable = main.uniqueMethodWithName("isNullable");
-    if (isR8) {
-      // Replaced with null-throwing code at the call site.
-      assertThat(nullable, not(isPresent()));
-    } else {
-      assertThat(nullable, isPresent());
-      assertEquals(1, countArrayLength(nullable));
-    }
+    assertThat(nullable, isPresent());
+    assertEquals(isR8 ? 0 : 1, countArrayLength(nullable));
 
     MethodSubject nullCheck = main.uniqueMethodWithName("afterNullCheck");
     assertThat(nullCheck, isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForLibraryMethodTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForLibraryMethodTest.java
new file mode 100644
index 0000000..632863a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForLibraryMethodTest.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.assumenosideeffects;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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 AssumeNoSideEffectsForLibraryMethodTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public AssumeNoSideEffectsForLibraryMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-assumenosideeffects class java.lang.Object { int hashCode() return 42; }")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              MethodSubject mainMethodSubject = inspector.clazz(TestClass.class).mainMethod();
+              assertThat(mainMethodSubject, isPresent());
+              assertTrue(
+                  mainMethodSubject
+                      .streamInstructions()
+                      .noneMatch(
+                          instruction ->
+                              instruction.isInvokeVirtual()
+                                  && instruction.getMethod().name.toString().equals("hashCode")));
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("42");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(new Object().hashCode());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java
index bc842e5..9c38214 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java
@@ -116,7 +116,9 @@
 
     @NeverInline
     private static void testInvokeInterface(LoggerInterface logger, String message) {
-      logger.debug(TAG, message);
+      if (logger != null) {
+        logger.debug(TAG, message);
+      }
     }
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java
index ebf633c..71614d2 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java
@@ -41,27 +41,32 @@
         case RULE_THAT_REFERS_LIB_BASE:
           return StringUtils.lines(
               "-assumenosideeffects class " + LibraryBase.class.getTypeName() + " {",
-              "  *;",
+              "  throwing(...);",
+              "  debug(...);",
               "}");
         case RULE_THAT_REFERS_PRG_BASE:
           return StringUtils.lines(
               "-assumenosideeffects class " + ProgramBase.class.getTypeName() + " {",
-              "  *;",
+              "  throwing(...);",
+              "  debug(...);",
               "}");
         case RULE_THAT_REFERS_PRG_SUB:
           return StringUtils.lines(
               "-assumenosideeffects class " + ProgramSub.class.getTypeName() + " {",
-              "  *;",
+              "  throwing(...);",
+              "  debug(...);",
               "}");
         case RULE_WITH_EXTENDS_LIB_BASE:
           return StringUtils.lines(
               "-assumenosideeffects class * extends " + LibraryBase.class.getTypeName() + " {",
-              "  *;",
+              "  throwing(...);",
+              "  debug(...);",
               "}");
         case RULE_WITH_EXTENDS_PRG_BASE:
           return StringUtils.lines(
               "-assumenosideeffects class * extends " + ProgramBase.class.getTypeName() + " {",
-              "  *;",
+              "  throwing(...);",
+              "  debug(...);",
               "}");
       }
       throw new Unreachable();
@@ -112,7 +117,9 @@
 
   @Parameterized.Parameters(name = "{0} {1}")
   public static Collection<Object[]> data() {
-    return buildParameters(getTestParameters().withAllRuntimes().build(), TestConfig.values());
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().withAllApiLevelsAlsoForCf().build(),
+        TestConfig.values());
   }
 
   private final TestParameters parameters;
@@ -145,7 +152,7 @@
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
         .addLibraryFiles(libJarPath)
-        .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+        .addLibraryFiles(ToolHelper.getFirstSupportedAndroidJar(parameters.getApiLevel()))
         .addProgramClasses(ProgramBase.class, ProgramSub.class, MAIN)
         .addKeepMainRule(MAIN)
         .addKeepRules(config.getKeepRule())
@@ -153,7 +160,7 @@
         .enableMergeAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .addRunClasspathFiles(parameters.isDexRuntime() ? libDexPath : libJarPath)
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsWithMultipleTargetsTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsWithMultipleTargetsTest.java
index 29e4588..34b1b33 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsWithMultipleTargetsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsWithMultipleTargetsTest.java
@@ -138,7 +138,9 @@
 
     @NeverInline
     private static void testInvokeInterface(TestLogger logger, String message) {
-      logger.info(TAG, message);
+      if (logger != null) {
+        logger.info(TAG, message);
+      }
     }
 
     public static void main(String... args) {
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java
index fe72a94..560f6ea 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java
@@ -4,15 +4,28 @@
 
 package com.android.tools.r8.shaking.assumenosideeffects;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticPosition;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
 
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.TextPosition;
+import com.android.tools.r8.position.TextRange;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.google.common.collect.ImmutableList;
+import org.hamcrest.Matcher;
 import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Test;
@@ -47,13 +60,118 @@
                     }));
   }
 
+  private Matcher<Diagnostic> matchAssumeNoSideEffectsWarningMessage() {
+    return diagnosticMessage(
+        containsString(
+            "The -assumenosideeffects rule matches methods on `java.lang.Object` with"
+                + " wildcards"));
+  }
+
+  private Matcher<Diagnostic> matchWarningMessageForAllProblematicMethods() {
+    return diagnosticMessage(
+        allOf(
+            containsString("void notify()"),
+            containsString("void notifyAll()"),
+            containsString("void wait()"),
+            containsString("void wait(long)"),
+            containsString("void wait(long, int)")));
+  }
+
+  private Matcher<Diagnostic> matchWarningMessageForWaitMethods() {
+    return diagnosticMessage(
+        allOf(
+            containsString("void wait()"),
+            containsString("void wait(long)"),
+            containsString("void wait(long, int)")));
+  }
+
+  private TextRange textRangeForString(String s) {
+    return new TextRange(
+        new TextPosition(0, 1, 1), new TextPosition(s.length(), 1, s.length() + 1));
+  }
+
   @Test
-  public void testR8() throws Exception {
+  public void testR8AllMatch() throws Exception {
     testForR8(parameters.getBackend())
-        .addInnerClasses(B152492625.class)
+        .addProgramClasses(TestClass.class, B.class)
         .addKeepMainRule(TestClass.class)
         .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }")
         .setMinApi(parameters.getApiLevel())
+        .allowDiagnosticWarningMessages()
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertOnlyWarnings();
+              diagnostics.assertWarningsMatch(matchAssumeNoSideEffectsWarningMessage());
+              diagnostics.assertWarningsMatch(matchWarningMessageForAllProblematicMethods());
+            })
+        .inspect(this::noCallToWait)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world");
+  }
+
+  @Test
+  public void testR8AllMatchMultipleRules() throws Exception {
+    class MyOrigin extends Origin {
+      private final String part;
+
+      public MyOrigin(String part) {
+        super(Origin.root());
+        this.part = part;
+      }
+
+      @Override
+      public String part() {
+        return part;
+      }
+    }
+
+    Origin starRuleOrigin = new MyOrigin("star rule");
+    Origin methodsRuleOrigin = new MyOrigin("methods rule");
+
+    String starRule = "-assumenosideeffects class " + B.class.getTypeName() + " { *; }";
+    String methodsRule = "-assumenosideeffects class " + B.class.getTypeName() + " { <methods>; }";
+
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, B.class)
+        .addKeepMainRule(TestClass.class)
+        .apply(
+            b ->
+                b.getBuilder().addProguardConfiguration(ImmutableList.of(starRule), starRuleOrigin))
+        .apply(
+            b ->
+                b.getBuilder()
+                    .addProguardConfiguration(ImmutableList.of(methodsRule), methodsRuleOrigin))
+        .setMinApi(parameters.getApiLevel())
+        .allowDiagnosticWarningMessages()
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertOnlyWarnings();
+              diagnostics.assertWarningsMatch(
+                  ImmutableList.of(
+                      allOf(
+                          matchAssumeNoSideEffectsWarningMessage(),
+                          matchWarningMessageForAllProblematicMethods(),
+                          diagnosticOrigin(starRuleOrigin),
+                          diagnosticPosition(textRangeForString(starRule))),
+                      allOf(
+                          matchAssumeNoSideEffectsWarningMessage(),
+                          matchWarningMessageForAllProblematicMethods(),
+                          diagnosticOrigin(methodsRuleOrigin),
+                          diagnosticPosition(textRangeForString(methodsRule)))));
+            })
+        .inspect(this::noCallToWait)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world");
+  }
+
+  @Test
+  public void testR8AllMatchDontWarn() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, B.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }")
+        .addKeepRules("-dontwarn java.lang.Object")
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::noCallToWait)
         .run(parameters.getRuntime(), TestClass.class)
@@ -61,11 +179,85 @@
   }
 
   @Test
+  public void testR8AllMethodsMatch() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, B.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { <methods>; }")
+        .setMinApi(parameters.getApiLevel())
+        .allowDiagnosticWarningMessages()
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertOnlyWarnings();
+              diagnostics.assertWarningsMatch(matchAssumeNoSideEffectsWarningMessage());
+              diagnostics.assertWarningsMatch(matchWarningMessageForAllProblematicMethods());
+            })
+        .inspect(this::noCallToWait)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world");
+  }
+
+  @Test
+  public void testR8WaitMethodMatch() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, B.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *** w*(...); }")
+        .setMinApi(parameters.getApiLevel())
+        .allowDiagnosticWarningMessages()
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertOnlyWarnings();
+              diagnostics.assertWarningsMatch(matchAssumeNoSideEffectsWarningMessage());
+              diagnostics.assertWarningsMatch(matchWarningMessageForWaitMethods());
+            })
+        .inspect(this::noCallToWait)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world");
+  }
+
+  @Test
+  public void testR8WaitSpecificMethodMatch() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, B.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-assumenosideeffects class java.lang.Object { void wait(); }")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::noCallToWait)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world");
+  }
+
+  @Test
+  public void testR8AssumeNoSideEffectsNotConditional() throws Exception {
+    try {
+      testForR8(parameters.getBackend())
+          .addProgramClasses(TestClass.class, B.class)
+          .addKeepMainRule(TestClass.class)
+          .addKeepRules(
+              "-if class " + TestClass.class.getTypeName(),
+              " -assumenosideeffects class " + B.class.getTypeName() + " { *; }")
+          .setMinApi(parameters.getApiLevel())
+          .compileWithExpectedDiagnostics(
+              diagnostics -> {
+                diagnostics.assertOnlyErrors();
+                diagnostics.assertErrorsMatch(
+                    diagnosticMessage(
+                        containsString("Expecting '-keep' option after '-if' option")));
+              });
+      fail("Expected failed compilation");
+    } catch (CompilationFailedException e) {
+      // Expected.
+    }
+  }
+
+  @Test
   public void testProguardNotRemovingWait() throws Exception {
     Assume.assumeTrue(parameters.isCfRuntime());
 
     testForProguard()
-        .addInnerClasses(B152492625.class)
+        .addProgramClasses(TestClass.class, B.class)
         .addKeepMainRule(TestClass.class)
         .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }")
         .addKeepRules("-dontwarn " + B152492625.class.getTypeName())
@@ -80,7 +272,7 @@
     Assume.assumeTrue(parameters.isCfRuntime());
 
     testForProguard()
-        .addInnerClasses(B152492625.class)
+        .addProgramClasses(TestClass.class, B.class)
         .addKeepMainRule(TestClass.class)
         .addKeepRules("-assumenosideeffects class java.lang.Object { void wait(); }")
         .addKeepRules("-dontwarn " + B152492625.class.getTypeName())
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B157688676.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B157688676.java
new file mode 100644
index 0000000..85be324
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B157688676.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.assumenosideeffects;
+
+import com.android.tools.r8.AssumeNoSideEffects;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class B157688676 extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public B157688676(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .enableAssumeNoSideEffectsAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithEmptyOutput();
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      greet();
+    }
+
+    @AssumeNoSideEffects
+    static void greet() {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/NullableReturnAfterNonNullableReturnTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/NullableReturnAfterNonNullableReturnTest.java
new file mode 100644
index 0000000..e9b4f9f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/NullableReturnAfterNonNullableReturnTest.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.assumenosideeffects;
+
+import com.android.tools.r8.AssumeNoSideEffects;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.ReprocessMethod;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NullableReturnAfterNonNullableReturnTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public NullableReturnAfterNonNullableReturnTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .enableAssumeNoSideEffectsAnnotations()
+        .enableInliningAnnotations()
+        .enableReprocessMethodAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile();
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(test());
+    }
+
+    @NeverInline
+    @ReprocessMethod
+    static Object test() {
+      String s = System.currentTimeMillis() > 0 ? "Hello world!" : null;
+      checkNotNull(s);
+      return s;
+    }
+
+    @AssumeNoSideEffects
+    static void checkNotNull(Object o) {
+      if (o == null) {
+        throw new RuntimeException();
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesForLibraryMethodTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesForLibraryMethodTest.java
new file mode 100644
index 0000000..4d7e293
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesForLibraryMethodTest.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.assumevalues;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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 AssumeValuesForLibraryMethodTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public AssumeValuesForLibraryMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-assumevalues class java.lang.Object { int hashCode() return 42; }")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              MethodSubject mainMethodSubject = inspector.clazz(TestClass.class).mainMethod();
+              assertThat(mainMethodSubject, isPresent());
+              assertTrue(
+                  mainMethodSubject
+                      .streamInstructions()
+                      .anyMatch(
+                          instruction ->
+                              instruction.isInvokeVirtual()
+                                  && instruction.getMethod().name.toString().equals("hashCode")));
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("42");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(new Object().hashCode());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
index d4801ea..23a5616 100644
--- a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.shaking.b134858535;
 
+import static org.hamcrest.CoreMatchers.containsString;
+
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -26,7 +28,13 @@
         .addProgramClassFileData(EventPublisher$bDump.dump())
         .addKeepClassRules(Interface.class)
         .addKeepMainRule(Main.class)
+        .allowDiagnosticInfoMessages()
         .setMinApi(AndroidApiLevel.L)
-        .compile();
+        .compile()
+        // TODO(b/157537996): Handle JStyle lambdas with private methods.
+        .assertAllInfoMessagesMatch(
+            containsString(
+                "Unrecognized Kotlin lambda"
+                    + " [com.android.tools.r8.shaking.b134858535.EventPublisher$b]"));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
index 198b253..1c9a3d0 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
@@ -73,15 +73,15 @@
     Assert.assertTrue(outer.getDexProgramClass().getInnerClasses().isEmpty());
     ClassSubject inner = inspector.clazz("annotationremoval.OuterClass$InnerClass");
     Assert.assertTrue(inner.isPresent());
-    Assert.assertNull(inner.getDexProgramClass().getEnclosingMethod());
+    Assert.assertNull(inner.getDexProgramClass().getEnclosingMethodAttribute());
     Assert.assertTrue(inner.getDexProgramClass().getInnerClasses().isEmpty());
     ClassSubject anonymous = inspector.clazz("annotationremoval.OuterClass$1");
     Assert.assertTrue(anonymous.isPresent());
-    Assert.assertNull(anonymous.getDexProgramClass().getEnclosingMethod());
+    Assert.assertNull(anonymous.getDexProgramClass().getEnclosingMethodAttribute());
     Assert.assertTrue(anonymous.getDexProgramClass().getInnerClasses().isEmpty());
     ClassSubject local = inspector.clazz("annotationremoval.OuterClass$1LocalMagic");
     Assert.assertTrue(local.isPresent());
-    Assert.assertNull(local.getDexProgramClass().getEnclosingMethod());
+    Assert.assertNull(local.getDexProgramClass().getEnclosingMethodAttribute());
     Assert.assertTrue(local.getDexProgramClass().getInnerClasses().isEmpty());
   }
 
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java b/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
index 912915b..ac43338 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
-import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 
 public class SmaliBuildTest extends SmaliTestBase {
@@ -23,7 +22,7 @@
       CodeInspector inspector = new CodeInspector(application);
       ClassSubject clazz = inspector.clazz("java.lang.String");
       assertEquals(present, clazz.isPresent());
-    } catch (IOException | ExecutionException e) {
+    } catch (IOException e) {
       throw new RuntimeException(e);
     }
   }
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index efcc42f..10962c8 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -126,7 +126,7 @@
       ClassSubject clazz = inspector.clazz(className);
       assertTrue(clazz.isPresent());
       return clazz.getDexProgramClass();
-    } catch (IOException | ExecutionException e) {
+    } catch (IOException e) {
       throw new RuntimeException(e);
     }
   }
@@ -135,7 +135,7 @@
     try {
       CodeInspector inspector = new CodeInspector(appPath);
       return getMethodSubject(inspector, signature);
-    } catch (IOException | ExecutionException e) {
+    } catch (IOException e) {
       throw new RuntimeException(e);
     }
   }
diff --git a/src/test/java/com/android/tools/r8/testing/AndroidBuildVersion.java b/src/test/java/com/android/tools/r8/testing/AndroidBuildVersion.java
new file mode 100644
index 0000000..ab3ea7e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/testing/AndroidBuildVersion.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.testing;
+
+/**
+ * Stub class to simulate having a Build.VERSION property in headless tests.
+ *
+ * <p>Use test builder addAndroidBuildVersion() methods when used in tests.
+ */
+public class AndroidBuildVersion {
+  public static final String PROPERTY = "com.android.tools.r8.testing.AndroidBuildVersion.VERSION";
+  public static int VERSION = Integer.parseInt(System.getProperty(PROPERTY));
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 3f1ad69..1f1d744 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -337,6 +337,10 @@
             });
   }
 
+  public ClassFileTransformer unsetAbstract() {
+    return setAccessFlags(ClassAccessFlags::unsetAbstract);
+  }
+
   public ClassFileTransformer setAnnotation() {
     return setAccessFlags(
         accessFlags -> {
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 39f7c44..b2edae2 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
@@ -55,7 +55,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.ExecutionException;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
@@ -72,25 +71,25 @@
   public static MethodSignature MAIN =
       new MethodSignature("main", "void", new String[] {"java.lang.String[]"});
 
-  public CodeInspector(String path) throws IOException, ExecutionException {
+  public CodeInspector(String path) throws IOException {
     this(Paths.get(path));
   }
 
-  public CodeInspector(Path file, String mappingFile) throws IOException, ExecutionException {
+  public CodeInspector(Path file, String mappingFile) throws IOException {
     this(Collections.singletonList(file), mappingFile, null);
   }
 
-  public CodeInspector(Path file) throws IOException, ExecutionException {
+  public CodeInspector(Path file) throws IOException {
     this(Collections.singletonList(file), null, null);
   }
 
-  public CodeInspector(List<Path> files) throws IOException, ExecutionException {
+  public CodeInspector(List<Path> files) throws IOException {
     this(files, null, null);
   }
 
   public CodeInspector(
       List<Path> files, String mappingFile, Consumer<InternalOptions> optionsConsumer)
-      throws IOException, ExecutionException {
+      throws IOException {
     Path mappingPath = mappingFile != null ? Paths.get(mappingFile) : null;
     if (mappingPath != null && Files.exists(mappingPath)) {
       mapping = ClassNameMapper.mapperFromFile(mappingPath);
@@ -109,14 +108,14 @@
     application = new ApplicationReader(input, options, timing).read();
   }
 
-  public CodeInspector(AndroidApp app) throws IOException, ExecutionException {
+  public CodeInspector(AndroidApp app) throws IOException {
     this(
         new ApplicationReader(app, runOptionsConsumer(null), Timing.empty())
             .read(app.getProguardMapOutputData()));
   }
 
   public CodeInspector(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
-      throws IOException, ExecutionException {
+      throws IOException {
     this(
         new ApplicationReader(app, runOptionsConsumer(optionsConsumer), Timing.empty())
             .read(app.getProguardMapOutputData()));
@@ -130,15 +129,13 @@
     return internalOptions;
   }
 
-  public CodeInspector(AndroidApp app, Path proguardMapFile)
-      throws IOException, ExecutionException {
+  public CodeInspector(AndroidApp app, Path proguardMapFile) throws IOException {
     this(
         new ApplicationReader(app, runOptionsConsumer(null), Timing.empty())
             .read(StringResource.fromFile(proguardMapFile)));
   }
 
-  public CodeInspector(AndroidApp app, String proguardMapContent)
-      throws IOException, ExecutionException {
+  public CodeInspector(AndroidApp app, String proguardMapContent) throws IOException {
     this(
         new ApplicationReader(app, runOptionsConsumer(null), Timing.empty())
             .read(StringResource.fromString(proguardMapContent, Origin.unknown())));
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 c5ca6c9..bfcda02 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
@@ -50,6 +50,10 @@
 
   boolean isConstNull();
 
+  default boolean isConstString() {
+    return isConstString(JumboStringMode.ALLOW);
+  }
+
   boolean isConstString(JumboStringMode jumboStringMode);
 
   boolean isConstString(String value, JumboStringMode jumboStringMode);
diff --git a/src/test/java/com/android/tools/r8/workaround/InputWithAbstractMethodOnNonAbstractClassTest.java b/src/test/java/com/android/tools/r8/workaround/InputWithAbstractMethodOnNonAbstractClassTest.java
new file mode 100644
index 0000000..aca08d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/workaround/InputWithAbstractMethodOnNonAbstractClassTest.java
@@ -0,0 +1,99 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.workaround;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbstract;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+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 InputWithAbstractMethodOnNonAbstractClassTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InputWithAbstractMethodOnNonAbstractClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addProgramClasses(TestClass.class)
+        .addProgramClassFileData(transformer(Greeter.class).unsetAbstract().transform())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addProgramClassFileData(transformer(Greeter.class).unsetAbstract().transform())
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassAndMembersRules(Greeter.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  @Test
+  public void testJVM() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(TestClass.class)
+        .addProgramClassFileData(transformer(Greeter.class).unsetAbstract().transform())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    MethodSubject methodOfInterest = inspector.clazz(Greeter.class).uniqueMethodWithName("dead");
+    assertThat(methodOfInterest, isPresent());
+    if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.L)) {
+      assertThat(methodOfInterest, not(isAbstract()));
+    } else {
+      assertThat(methodOfInterest, isAbstract());
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Greeter.greet();
+    }
+  }
+
+  /*not-*/ abstract static class Greeter {
+
+    static void greet() {
+      System.out.println("Hello world!");
+    }
+
+    abstract void dead();
+  }
+}
diff --git a/third_party/chrome/monochrome_public_minimal_apks/chrome_200520.tar.gz.sha1 b/third_party/chrome/monochrome_public_minimal_apks/chrome_200520.tar.gz.sha1
new file mode 100644
index 0000000..a4d4e8f
--- /dev/null
+++ b/third_party/chrome/monochrome_public_minimal_apks/chrome_200520.tar.gz.sha1
@@ -0,0 +1 @@
+4a9de4ba961c2f0c953c0a88675f29959e8602a7
\ No newline at end of file
diff --git a/tools/chrome_data.py b/tools/chrome_data.py
index 81f2fc7..20ddb21 100644
--- a/tools/chrome_data.py
+++ b/tools/chrome_data.py
@@ -10,6 +10,8 @@
 
 V180917_BASE = os.path.join(BASE, 'chrome_180917_ffbaa8')
 V200430_BASE = os.path.join(BASE, 'chrome_200430')
+V200520_MINIMAL_BASE = os.path.join(
+    BASE, 'monochrome_public_minimal_apks', 'chrome_200520')
 
 INPUT_JARS = [
     'out/Release/gen/chrome/android/monochrome_public_apk/monochrome_public_apk.jar',
@@ -256,4 +258,22 @@
         'min-api': ANDROID_N_API
     },
   },
+  '200520-monochrome_public_minimal_apks': {
+    'deploy' : {
+        'inputs': [os.path.join(V200520_MINIMAL_BASE, 'program.jar')],
+        'features': [
+            { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-1.jar')] },
+            { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-2.jar')] },
+            { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-3.jar')] },
+            { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-4.jar')] },
+            { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-5.jar')] },
+            { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-6.jar')] },
+            { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-7.jar')] },
+            { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-8.jar')] }
+        ],
+        'pgconf': [os.path.join(V200520_MINIMAL_BASE, 'proguard.config')],
+        'libraries': [os.path.join(V200520_MINIMAL_BASE, 'library.jar')],
+        'min-api': ANDROID_N_API
+    },
+  },
 }
diff --git a/tools/iosched_data.py b/tools/iosched_data.py
index 10edd2b..5aac7b0 100644
--- a/tools/iosched_data.py
+++ b/tools/iosched_data.py
@@ -5,6 +5,7 @@
 import os
 import utils
 
+ANDROID_L_API = '21'
 BASE = os.path.join(utils.THIRD_PARTY, 'iosched_2019')
 
 INPUT_JARS = [
@@ -168,9 +169,10 @@
 VERSIONS = {
   '2019': {
     'deploy' : {
-        'inputs': [os.path.join(BASE, path) for path in INPUT_JARS],
-        'pgconf': [os.path.join(BASE, 'proguard-rules.pro')],
-        'libraries': [utils.get_android_jar(28)],
+      'inputs': [os.path.join(BASE, path) for path in INPUT_JARS],
+      'pgconf': [os.path.join(BASE, 'proguard-rules.pro')],
+      'libraries': [utils.get_android_jar(28)],
+      'min-api' : ANDROID_L_API,
     },
   },
 }
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 2f3c4fc..7b1ba0b 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -17,7 +17,7 @@
 import archive_desugar_jdk_libs
 import utils
 
-R8_DEV_BRANCH = '2.1'
+R8_DEV_BRANCH = '2.2'
 R8_VERSION_FILE = os.path.join(
     'src', 'main', 'java', 'com', 'android', 'tools', 'r8', 'Version.java')
 THIS_FILE_RELATIVE = os.path.join('tools', 'r8_release.py')
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 809a1fe..1bd62e3 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -182,6 +182,10 @@
   result.add_option('--cpu-list',
                     help='Run under \'taskset\' with these CPUs. See '
                          'the \'taskset\' -c option for the format')
+  result.add_option('--quiet',
+                    help='Disable compiler logging',
+                    default=False,
+                    action='store_true')
 
   return result.parse_args(argv)
 
@@ -370,7 +374,7 @@
     return find_min_xmx(options, args)
   if options.track_time_in_memory:
     return track_time_in_memory(options, args)
-  exit_code = run_with_options(options, args)
+  exit_code = run_with_options(options, args, quiet=options.quiet)
   if options.expect_oom:
     exit_code = 0 if exit_code == OOM_EXIT_CODE else 1
   return exit_code
@@ -565,6 +569,13 @@
   if options.r8_flags:
     args.extend(options.r8_flags.split(' '))
 
+  # Feature jars.
+  features = values['features'] if 'features' in values else []
+  for i, feature in enumerate(features, start=1):
+    feature_out = os.path.join(outdir, 'feature-%d.zip' % i)
+    for feature_jar in feature['inputs']:
+      args.extend(['--feature', feature_jar, feature_out])
+
   args.extend(inputs)
 
   t0 = time.time()
diff --git a/tools/tachiyomi_data.py b/tools/tachiyomi_data.py
index 5518cea..10fafe2 100644
--- a/tools/tachiyomi_data.py
+++ b/tools/tachiyomi_data.py
@@ -5,14 +5,16 @@
 import os
 import utils
 
+ANDROID_J_API = '16'
 BASE = os.path.join(utils.THIRD_PARTY, 'tachiyomi')
 
 VERSIONS = {
   'b15d2fe16864645055af6a745a62cc5566629798': {
     'deploy' : {
-        'inputs': [os.path.join(BASE, 'program.jar')],
-        'pgconf': [os.path.join(BASE, 'proguard.config')],
-        'libraries': [os.path.join(BASE, 'library.jar')],
+      'inputs': [os.path.join(BASE, 'program.jar')],
+      'pgconf': [os.path.join(BASE, 'proguard.config')],
+      'libraries': [os.path.join(BASE, 'library.jar')],
+      'min-api' : ANDROID_J_API,
     },
   },
 }