Merge commit 'ee7803543ca0f13b223ff7bc02543c56cabbeacf' into dev-release
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 75f862e..4c4d2d5 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -370,6 +370,8 @@
 
         classesToRetainInnerClassAttributeFor =
             AnnotationRemover.computeClassesToRetainInnerClassAttributeFor(appView.withLiveness());
+        // TODO(b/149729626): Annotations should not be removed until after the second round of tree
+        //  shaking, since they are needed for interpretation of keep rules.
         new AnnotationRemover(appView.withLiveness(), classesToRetainInnerClassAttributeFor)
             .ensureValid()
             .run();
@@ -419,6 +421,8 @@
 
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       appView.setGraphLense(new MemberRebindingAnalysis(appViewWithLiveness).run());
+      appView.appInfo().withLiveness().getFieldAccessInfoCollection().restrictToProgram(appView);
+
       if (options.shouldDesugarNests()) {
         timing.begin("NestBasedAccessDesugaring");
         R8NestBasedAccessDesugaring analyzer = new R8NestBasedAccessDesugaring(appViewWithLiveness);
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
index 7ab2a56..0878119 100644
--- a/src/main/java/com/android/tools/r8/dex/Marker.java
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -23,6 +23,7 @@
   public static final String COMPILATION_MODE = "compilation-mode";
   public static final String HAS_CHECKSUMS = "has-checksums";
   public static final String PG_MAP_ID = "pg-map-id";
+  public static final String R8_MODE = "r8-mode";
 
   public enum Tool {
     D8,
@@ -128,6 +129,16 @@
     return this;
   }
 
+  public String getR8Mode() {
+    return jsonObject.get(R8_MODE).getAsString();
+  }
+
+  public Marker setR8Mode(String r8Mode) {
+    assert !jsonObject.has(R8_MODE);
+    jsonObject.addProperty(R8_MODE, r8Mode);
+    return this;
+  }
+
   @Override
   public String toString() {
     // In order to make printing of markers deterministic we sort the entries by key.
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 141f742..eb972ca 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -17,6 +17,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMap.Builder;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashMap;
@@ -402,16 +403,32 @@
    * 5.4.3.3 of the JVM Spec</a>. As this is the same for interfaces and classes, we share one
    * implementation.
    */
-  ResolutionResult resolveMethodStep3(DexClass clazz, DexMethod method) {
-    MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder(clazz);
-    resolveMethodStep3Helper(clazz, method, builder);
-    return builder.resolve();
+  private ResolutionResult resolveMethodStep3(DexClass clazz, DexMethod method) {
+    MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
+    resolveMethodStep3Helper(method, clazz, builder);
+    return builder.resolve(clazz);
+  }
+
+  // Non-private lookup (ie, not resolution) to find interface targets.
+  DexClassAndMethod lookupMaximallySpecificTarget(DexClass clazz, DexMethod method) {
+    MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
+    resolveMethodStep3Helper(method, clazz, builder);
+    return builder.lookup();
   }
 
   /** Helper method that builds the set of maximally specific methods. */
   private void resolveMethodStep3Helper(
-      DexClass clazz, DexMethod method, MaximallySpecificMethodsBuilder builder) {
-    for (DexType iface : clazz.interfaces.values) {
+      DexMethod method, DexClass clazz, MaximallySpecificMethodsBuilder builder) {
+    resolveMethodStep3Helper(
+        method, clazz.superType, Arrays.asList(clazz.interfaces.values), builder);
+  }
+
+  private void resolveMethodStep3Helper(
+      DexMethod method,
+      DexType superType,
+      List<DexType> interfaces,
+      MaximallySpecificMethodsBuilder builder) {
+    for (DexType iface : interfaces) {
       DexClass definiton = definitionFor(iface);
       if (definiton == null) {
         // Ignore missing interface definitions.
@@ -424,14 +441,14 @@
         builder.addCandidate(definiton, result, this);
       } else {
         // Look at the super-interfaces of this class and keep searching.
-        resolveMethodStep3Helper(definiton, method, builder);
+        resolveMethodStep3Helper(method, definiton, builder);
       }
     }
     // Now look at indirect super interfaces.
-    if (clazz.superType != null) {
-      DexClass superClass = definitionFor(clazz.superType);
+    if (superType != null) {
+      DexClass superClass = definitionFor(superType);
       if (superClass != null) {
-        resolveMethodStep3Helper(superClass, method, builder);
+        resolveMethodStep3Helper(method, superClass, builder);
       }
     }
   }
@@ -579,8 +596,6 @@
 
   private static class MaximallySpecificMethodsBuilder {
 
-    private final DexClass initialResolutionHolder;
-
     // The set of actual maximally specific methods.
     // This set is linked map so that in the case where a number of methods remain a deterministic
     // choice can be made. The map is from definition classes to their maximally specific method, or
@@ -589,10 +604,6 @@
     // prior to writing.
     LinkedHashMap<DexClass, DexEncodedMethod> maximallySpecificMethods = new LinkedHashMap<>();
 
-    public MaximallySpecificMethodsBuilder(DexClass initialResolutionHolder) {
-      this.initialResolutionHolder = initialResolutionHolder;
-    }
-
     void addCandidate(DexClass holder, DexEncodedMethod method, AppInfo appInfo) {
       // If this candidate is already a candidate or it is shadowed, then no need to continue.
       if (maximallySpecificMethods.containsKey(holder)) {
@@ -629,16 +640,26 @@
       }
     }
 
-    ResolutionResult resolve() {
+    DexClassAndMethod lookup() {
+      SingleResolutionResult result = internalResolve(null).asSingleResolution();
+      return result != null
+          ? DexClassAndMethod.create(result.getResolvedHolder(), result.getResolvedMethod())
+          : null;
+    }
+
+    ResolutionResult resolve(DexClass initialResolutionHolder) {
+      assert initialResolutionHolder != null;
+      return internalResolve(initialResolutionHolder);
+    }
+
+    private ResolutionResult internalResolve(DexClass initialResolutionHolder) {
       if (maximallySpecificMethods.isEmpty()) {
         return NoSuchMethodResult.INSTANCE;
       }
       // Fast path in the common case of a single method.
       if (maximallySpecificMethods.size() == 1) {
-        Entry<DexClass, DexEncodedMethod> first =
-            maximallySpecificMethods.entrySet().iterator().next();
-        return new SingleResolutionResult(
-            initialResolutionHolder, first.getKey(), first.getValue());
+        return singleResultHelper(
+            initialResolutionHolder, maximallySpecificMethods.entrySet().iterator().next());
       }
       Entry<DexClass, DexEncodedMethod> firstMaximallySpecificMethod = null;
       List<Entry<DexClass, DexEncodedMethod>> nonAbstractMethods =
@@ -659,21 +680,24 @@
       // If there are no non-abstract methods, then any candidate will suffice as a target.
       // For deterministic resolution, we return the first mapped method (of the linked map).
       if (nonAbstractMethods.isEmpty()) {
-        return new SingleResolutionResult(
-            initialResolutionHolder,
-            firstMaximallySpecificMethod.getKey(),
-            firstMaximallySpecificMethod.getValue());
+        return singleResultHelper(initialResolutionHolder, firstMaximallySpecificMethod);
       }
       // If there is exactly one non-abstract method (a default method) it is the resolution target.
       if (nonAbstractMethods.size() == 1) {
-        Entry<DexClass, DexEncodedMethod> entry = nonAbstractMethods.get(0);
-        return new SingleResolutionResult(
-            initialResolutionHolder, entry.getKey(), entry.getValue());
+        return singleResultHelper(initialResolutionHolder, nonAbstractMethods.get(0));
       }
       return IncompatibleClassResult.create(ListUtils.map(nonAbstractMethods, Entry::getValue));
     }
   }
 
+  private static SingleResolutionResult singleResultHelper(
+      DexClass initialResolutionResult, Entry<DexClass, DexEncodedMethod> entry) {
+    return new SingleResolutionResult(
+        initialResolutionResult != null ? initialResolutionResult : entry.getKey(),
+        entry.getKey(),
+        entry.getValue());
+  }
+
   // TODO(b/149190785): Remove once fixed.
   public void enableDefinitionForAssert() {}
 
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index cefb55a..e0e8269 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.utils.TraversalContinuation.BREAK;
 import static com.android.tools.r8.utils.TraversalContinuation.CONTINUE;
 
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
@@ -207,16 +206,8 @@
    * Helper method used for emulated interface resolution (not in JVM specifications). The result
    * may be abstract.
    */
-  public ResolutionResult resolveMaximallySpecificMethods(DexClass clazz, DexMethod method) {
-    assert !clazz.type.isArrayType();
-    if (clazz.isInterface()) {
-      // Look for exact method on interface.
-      DexEncodedMethod result = clazz.lookupMethod(method);
-      if (result != null) {
-        return new SingleResolutionResult(clazz, clazz, result);
-      }
-    }
-    return resolveMethodStep3(clazz, method);
+  public DexClassAndMethod lookupMaximallySpecificMethod(DexClass clazz, DexMethod method) {
+    return lookupMaximallySpecificTarget(clazz, method);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 813ea84..9a07dd2 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.WorkList;
-import com.android.tools.r8.utils.WorkList.EqualityTest;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -42,7 +41,7 @@
       DexType type,
       Consumer<DexProgramClass> subTypeConsumer,
       Consumer<DexCallSite> callSiteConsumer) {
-    WorkList<DexType> workList = new WorkList<>(EqualityTest.IDENTITY);
+    WorkList<DexType> workList = WorkList.newIdentityWorkList();
     workList.addIfNotSeen(type);
     workList.addIfNotSeen(allImmediateSubtypes(type));
     while (workList.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 5c8de46..4f8fa23 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -210,7 +211,7 @@
     for (CfInstruction instruction : instructions) {
       if (instruction instanceof CfFrame
           && (classFileVersion <= 49
-              || (classFileVersion == 50 && !options.shouldKeepStackMapTable()))) {
+          || (classFileVersion == 50 && !options.shouldKeepStackMapTable()))) {
         continue;
       }
       instruction.write(visitor, namingLens);
@@ -287,7 +288,7 @@
   @Override
   public IRCode buildIR(DexEncodedMethod encodedMethod, AppView<?> appView, Origin origin) {
     return internalBuildPossiblyWithLocals(
-        encodedMethod, encodedMethod, appView, null, null, origin);
+        encodedMethod, encodedMethod, appView, null, null, origin, null);
   }
 
   @Override
@@ -297,11 +298,18 @@
       AppView<?> appView,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin) {
+      Origin origin,
+      MethodProcessor methodProcessor) {
     assert valueNumberGenerator != null;
     assert callerPosition != null;
     return internalBuildPossiblyWithLocals(
-        context, encodedMethod, appView, valueNumberGenerator, callerPosition, origin);
+        context,
+        encodedMethod,
+        appView,
+        valueNumberGenerator,
+        callerPosition,
+        origin,
+        methodProcessor);
   }
 
   // First build entry. Will either strip locals or build with locals.
@@ -311,7 +319,8 @@
       AppView<?> appView,
       ValueNumberGenerator generator,
       Position callerPosition,
-      Origin origin) {
+      Origin origin,
+      MethodProcessor methodProcessor) {
     if (!encodedMethod.keepLocals(appView.options())) {
       return internalBuild(
           Collections.emptyList(),
@@ -320,10 +329,11 @@
           appView,
           generator,
           callerPosition,
-          origin);
+          origin,
+          methodProcessor);
     } else {
       return internalBuildWithLocals(
-          context, encodedMethod, appView, generator, callerPosition, origin);
+          context, encodedMethod, appView, generator, callerPosition, origin, methodProcessor);
     }
   }
 
@@ -334,7 +344,8 @@
       AppView<?> appView,
       ValueNumberGenerator generator,
       Position callerPosition,
-      Origin origin) {
+      Origin origin,
+      MethodProcessor methodProcessor) {
     try {
       return internalBuild(
           Collections.unmodifiableList(localVariables),
@@ -343,7 +354,8 @@
           appView,
           generator,
           callerPosition,
-          origin);
+          origin,
+          methodProcessor);
     } catch (InvalidDebugInfoException e) {
       appView.options().warningInvalidDebugInfo(encodedMethod, origin, e);
       return internalBuild(
@@ -353,19 +365,21 @@
           appView,
           generator,
           callerPosition,
-          origin);
+          origin,
+          methodProcessor);
     }
   }
 
   // Inner-most subroutine for building. Must only be called by the two internalBuildXYZ above.
   private IRCode internalBuild(
-      List<CfCode.LocalVariableInfo> localVariables,
+      List<LocalVariableInfo> localVariables,
       DexEncodedMethod context,
       DexEncodedMethod encodedMethod,
       AppView<?> appView,
       ValueNumberGenerator generator,
       Position callerPosition,
-      Origin origin) {
+      Origin origin,
+      MethodProcessor methodProcessor) {
     CfSourceCode source =
         new CfSourceCode(
             this,
@@ -375,7 +389,11 @@
             callerPosition,
             origin,
             appView);
-    return new IRBuilder(encodedMethod, appView, source, origin, generator).build(context);
+    IRBuilder builder = methodProcessor == null ?
+        IRBuilder.create(encodedMethod, appView, source, origin) :
+        IRBuilder
+            .createForInlining(encodedMethod, appView, source, origin, methodProcessor, generator);
+    return builder.build(context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 0fbae38..8e81c8e 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.Outliner.OutlineCode;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
@@ -24,7 +25,8 @@
       AppView<?> appView,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin) {
+      Origin origin,
+      MethodProcessor methodProcessor) {
     throw new Unreachable("Unexpected attempt to build IR graph for inlining from: "
         + getClass().getCanonicalName());
   }
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 3601754..c474718 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -138,9 +138,7 @@
   }
 
   public Iterable<DexEncodedMethod> methods(Predicate<? super DexEncodedMethod> predicate) {
-    return Iterables.concat(
-        Iterables.filter(Arrays.asList(directMethods), predicate::test),
-        Iterables.filter(Arrays.asList(virtualMethods), predicate::test));
+    return Iterables.concat(directMethods(predicate), virtualMethods(predicate));
   }
 
   @Override
@@ -156,6 +154,10 @@
     return Arrays.asList(directMethods);
   }
 
+  public Iterable<DexEncodedMethod> directMethods(Predicate<? super DexEncodedMethod> predicate) {
+    return Iterables.filter(Arrays.asList(directMethods), predicate::test);
+  }
+
   public void appendDirectMethod(DexEncodedMethod method) {
     cachedClassInitializer = null;
     DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length + 1];
@@ -223,6 +225,10 @@
     return Arrays.asList(virtualMethods);
   }
 
+  public Iterable<DexEncodedMethod> virtualMethods(Predicate<? super DexEncodedMethod> predicate) {
+    return Iterables.filter(Arrays.asList(virtualMethods), predicate::test);
+  }
+
   public void appendVirtualMethod(DexEncodedMethod method) {
     DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + 1];
     System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length);
@@ -815,6 +821,15 @@
     return !clinit.getOptimizationInfo().classInitializerMayBePostponed();
   }
 
+  public void forEachImmediateSupertype(Consumer<DexType> fn) {
+    if (superType != null) {
+      fn.accept(superType);
+    }
+    for (DexType iface : interfaces.values) {
+      fn.accept(iface);
+    }
+  }
+
   public Iterable<DexType> allImmediateSupertypes() {
     Iterator<DexType> iterator =
         superType != null
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
index 281b58a..ded00bd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -11,7 +11,7 @@
   private final DexClass holder;
   private final DexEncodedMethod method;
 
-  protected DexClassAndMethod(DexClass holder, DexEncodedMethod method) {
+  DexClassAndMethod(DexClass holder, DexEncodedMethod method) {
     assert holder.type == method.method.holder;
     this.holder = holder;
     this.method = method;
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index d32abdf..9c02dec 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.DexSourceCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.StringUtils;
@@ -220,9 +221,7 @@
             encodedMethod,
             appView.graphLense().getOriginalMethodSignature(encodedMethod.method),
             null);
-    IRBuilder builder =
-        new IRBuilder(encodedMethod, appView, source, origin, new ValueNumberGenerator());
-    return builder.build(encodedMethod);
+    return IRBuilder.create(encodedMethod,appView,source,origin).build(encodedMethod);
   }
 
   @Override
@@ -232,15 +231,16 @@
       AppView<?> appView,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin) {
+      Origin origin,
+      MethodProcessor methodProcessor) {
     DexSourceCode source =
         new DexSourceCode(
             this,
             encodedMethod,
             appView.graphLense().getOriginalMethodSignature(encodedMethod.method),
             callerPosition);
-    IRBuilder builder = new IRBuilder(encodedMethod, appView, source, origin, valueNumberGenerator);
-    return builder.build(context);
+    return IRBuilder.createForInlining(
+        encodedMethod, appView, source, origin, methodProcessor, valueNumberGenerator).build(context);
   }
 
   @Override
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 b616649..8b35b54 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
@@ -16,5 +16,15 @@
 
   DexProgramClass definitionForProgramType(DexType type);
 
+  default <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+      DexClass definitionForHolder(DexEncodedMember<D, R> member) {
+    return definitionForHolder(member.toReference());
+  }
+
+  default <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+      DexClass definitionForHolder(DexMember<D, R> member) {
+    return definitionFor(member.holder);
+  }
+
   DexItemFactory dexItemFactory();
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
index 0e436d6..638a9be 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -10,6 +10,10 @@
     super(annotations);
   }
 
+  public DexType holder() {
+    return toReference().holder;
+  }
+
   @Override
   public abstract R toReference();
 
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 b755c49..1fdd415 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -40,6 +40,7 @@
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring.DexFieldWithAccess;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -550,10 +551,11 @@
       AppView<?> appView,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin) {
+      Origin origin,
+      MethodProcessor methodProcessor) {
     checkIfObsolete();
     return code.buildInliningIR(
-        context, this, appView, valueNumberGenerator, callerPosition, origin);
+        context, this, appView, valueNumberGenerator, callerPosition, origin, methodProcessor);
   }
 
   public void setCode(Code newCode, AppView<?> appView) {
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
index b9e1b5c..2bd8acf 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
@@ -40,5 +40,7 @@
 
   boolean isWrittenInMethodSatisfying(Predicate<DexEncodedMethod> predicate);
 
+  boolean isWrittenOnlyInMethodSatisfying(Predicate<DexEncodedMethod> predicate);
+
   boolean isWrittenOutside(DexEncodedMethod method);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
index 5b0ebc2..ddab792 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
@@ -17,4 +17,6 @@
   void forEach(Consumer<T> consumer);
 
   void removeIf(BiPredicate<DexField, FieldAccessInfoImpl> predicate);
+
+  void restrictToProgram(DexDefinitionSupplier definitions);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
index 47a9c30..7ef10ac 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
@@ -42,6 +42,11 @@
     infos.entrySet().removeIf(entry -> predicate.test(entry.getKey(), entry.getValue()));
   }
 
+  @Override
+  public void restrictToProgram(DexDefinitionSupplier definitions) {
+    removeIf((field, info) -> !definitions.definitionForHolder(field).isProgramClass());
+  }
+
   public FieldAccessInfoCollectionImpl rewrittenWithLens(
       DexDefinitionSupplier definitions, GraphLense lens) {
     FieldAccessInfoCollectionImpl collection = new FieldAccessInfoCollectionImpl();
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index 2b813ea..4715169 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -230,6 +230,24 @@
   }
 
   /**
+   * Returns true if this field is only written by methods for which {@param predicate} returns
+   * true.
+   */
+  @Override
+  public boolean isWrittenOnlyInMethodSatisfying(Predicate<DexEncodedMethod> predicate) {
+    if (writesWithContexts != null) {
+      for (Set<DexEncodedMethod> encodedWriteContexts : writesWithContexts.values()) {
+        for (DexEncodedMethod encodedWriteContext : encodedWriteContexts) {
+          if (!predicate.test(encodedWriteContext)) {
+            return false;
+          }
+        }
+      }
+    }
+    return true;
+  }
+
+  /**
    * Returns true if this field is written by a method in the program other than {@param method}.
    */
   @Override
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 edec3a9..41499ea 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -56,8 +56,10 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.InternalOptions;
@@ -149,7 +151,8 @@
   public void parseCode(ReparseContext context, boolean useJsrInliner) {
     int parsingOptions = getParsingOptions(application, reachabilitySensitive);
     ClassCodeVisitor classVisitor =
-        new ClassCodeVisitor(context.owner, createCodeLocator(context), application, useJsrInliner);
+        new ClassCodeVisitor(
+            context.owner, createCodeLocator(context), application, useJsrInliner, origin);
     new ClassReader(context.classCache).accept(classVisitor, parsingOptions);
   }
 
@@ -198,10 +201,17 @@
       AppView<?> appView,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin) {
+      Origin origin,
+      MethodProcessor methodProcessor) {
     return asCfCode()
         .buildInliningIR(
-            context, encodedMethod, appView, valueNumberGenerator, callerPosition, origin);
+            context,
+            encodedMethod,
+            appView,
+            valueNumberGenerator,
+            callerPosition,
+            origin,
+            methodProcessor);
   }
 
   @Override
@@ -250,17 +260,20 @@
     private final BiFunction<String, String, LazyCfCode> codeLocator;
     private final JarApplicationReader application;
     private boolean usrJsrInliner;
+    private final Origin origin;
 
     ClassCodeVisitor(
         DexClass clazz,
         BiFunction<String, String, LazyCfCode> codeLocator,
         JarApplicationReader application,
-        boolean useJsrInliner) {
+        boolean useJsrInliner,
+        Origin origin) {
       super(InternalOptions.ASM_VERSION);
       this.clazz = clazz;
       this.codeLocator = codeLocator;
       this.application = application;
       this.usrJsrInliner = useJsrInliner;
+      this.origin = origin;
     }
 
     @Override
@@ -271,7 +284,8 @@
         LazyCfCode code = codeLocator.apply(name, desc);
         if (code != null) {
           DexMethod method = application.getMethod(clazz.type, name, desc);
-          MethodCodeVisitor methodVisitor = new MethodCodeVisitor(application, method, code);
+          MethodCodeVisitor methodVisitor =
+              new MethodCodeVisitor(application, method, code, origin);
           if (!usrJsrInliner) {
             return methodVisitor;
           }
@@ -294,14 +308,17 @@
     private Map<Label, CfLabel> labelMap;
     private final LazyCfCode code;
     private final DexMethod method;
+    private final Origin origin;
 
-    MethodCodeVisitor(JarApplicationReader application, DexMethod method, LazyCfCode code) {
+    MethodCodeVisitor(
+        JarApplicationReader application, DexMethod method, LazyCfCode code, Origin origin) {
       super(InternalOptions.ASM_VERSION);
       assert code != null;
       this.application = application;
       this.factory = application.getFactory();
       this.code = code;
       this.method = method;
+      this.origin = origin;
     }
 
     @Override
@@ -316,6 +333,15 @@
 
     @Override
     public void visitEnd() {
+      if (instructions == null) {
+        // Everything that is initialized at `visitCode` should be null too.
+        assert tryCatchRanges == null && localVariables == null && labelMap == null;
+        // This code visitor is used only if the method is neither abstract nor native, hence it
+        // should have exactly one Code attribute:
+        // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.3
+        throw new CompilationError("Absent Code attribute in method that is not native or abstract")
+            .withAdditionalOriginAndPositionInfo(origin, new MethodPosition(method));
+      }
       code.setCode(
           new CfCode(
               method.holder, maxStack, maxLocals, instructions, tryCatchRanges, localVariables));
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index feffac7..4c5e239 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -155,21 +155,11 @@
     public DexEncodedMethod lookupInvokeSpecialTarget(
         DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
       // If the resolution is non-accessible then no target exists.
-      if (!isAccessibleFrom(context, appInfo)) {
-        return null;
+      if (isAccessibleFrom(context, appInfo)) {
+        return internalInvokeSpecialOrSuper(
+            context, appInfo, (sup, sub) -> isSuperclass(sup, sub, appInfo));
       }
-      DexEncodedMethod target =
-          internalInvokeSpecialOrSuper(
-              context, appInfo, (sup, sub) -> isSuperclass(sup, sub, appInfo));
-      if (target == null) {
-        return null;
-      }
-      // Should we check access control again?
-      DexClass holder = appInfo.definitionFor(target.method.holder);
-      if (!AccessControl.isMethodAccessible(target, holder, context, appInfo)) {
-        return null;
-      }
-      return target;
+      return null;
     }
 
     /**
@@ -191,7 +181,7 @@
     public DexEncodedMethod lookupInvokeSuperTarget(
         DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
       // TODO(b/147848950): Investigate and remove the Compilation error. It could compile to
-      // throw IAE.
+      //  throw IAE.
       if (resolvedMethod.isInstanceInitializer()
           || (appInfo.hasSubtyping()
               && initialResolutionHolder != context
@@ -199,19 +189,10 @@
         throw new CompilationError(
             "Illegal invoke-super to " + resolvedMethod.toSourceString(), context.getOrigin());
       }
-      if (!isAccessibleFrom(context, appInfo)) {
-        return null;
+      if (isAccessibleFrom(context, appInfo)) {
+        return internalInvokeSpecialOrSuper(context, appInfo, (sup, sub) -> true);
       }
-      DexEncodedMethod target = internalInvokeSpecialOrSuper(context, appInfo, (sup, sub) -> true);
-      if (target == null) {
-        return null;
-      }
-      // Should we check access control again?
-      DexClass holder = appInfo.definitionFor(target.method.holder);
-      if (!AccessControl.isMethodAccessible(target, holder, context, appInfo)) {
-        return null;
-      }
-      return target;
+      return null;
     }
 
     /**
@@ -305,7 +286,10 @@
       }
       // 4. Otherwise, it is the single maximally specific method:
       if (target == null) {
-        target = appInfo.resolveMaximallySpecificMethods(initialType, method).getSingleTarget();
+        DexClassAndMethod result = appInfo.lookupMaximallySpecificMethod(initialType, method);
+        if (result != null) {
+          target = result.getMethod();
+        }
       }
       if (target == null) {
         return null;
@@ -341,6 +325,8 @@
         AppView<? extends AppInfoWithClassHierarchy> appView,
         InstantiatedSubTypeInfo instantiatedInfo) {
       // Check that the initial resolution holder is accessible from the context.
+      assert appView.isSubtype(initialResolutionHolder.type, resolvedHolder.type).isTrue()
+          : initialResolutionHolder.type + " is not a subtype of " + resolvedHolder.type;
       if (context != null && !isAccessibleFrom(context, appView.appInfo())) {
         return LookupResult.createFailedResult();
       }
@@ -356,7 +342,8 @@
       instantiatedInfo.forEachInstantiatedSubType(
           resolvedHolder.type,
           subClass -> {
-            DexClassAndMethod dexClassAndMethod = lookupVirtualDispatchTarget(subClass, appView);
+            DexClassAndMethod dexClassAndMethod =
+                lookupVirtualDispatchTarget(subClass, appView, resolvedHolder.type);
             if (dexClassAndMethod != null) {
               addVirtualDispatchTarget(
                   dexClassAndMethod.getMethod(), resolvedHolder.isInterface(), result);
@@ -418,6 +405,15 @@
     @Override
     public DexClassAndMethod lookupVirtualDispatchTarget(
         DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView) {
+      return lookupVirtualDispatchTarget(dynamicInstance, appView, initialResolutionHolder.type);
+    }
+
+    private DexClassAndMethod lookupVirtualDispatchTarget(
+        DexProgramClass dynamicInstance,
+        AppView<? extends AppInfoWithClassHierarchy> appView,
+        DexType resolutionHolder) {
+      assert appView.isSubtype(dynamicInstance.type, resolutionHolder).isTrue()
+          : dynamicInstance.type + " is not a subtype of " + resolutionHolder;
       // TODO(b/148591377): Enable this assertion.
       // The dynamic type cannot be an interface.
       // assert !dynamicInstance.isInterface();
@@ -441,24 +437,19 @@
         }
         return DexClassAndMethod.create(current, candidate);
       }
-      // TODO(b/149557233): Enable assertion.
-      // assert resolvedHolder.isInterface();
-      DexEncodedMethod maximalSpecific =
-          lookupMaximallySpecificDispatchTarget(dynamicInstance, appView);
-      return maximalSpecific == null
-          ? null
-          : DexClassAndMethod.create(
-              appView.definitionFor(maximalSpecific.method.holder), maximalSpecific);
+      // If we have not found a candidate and the holder is not an interface it must be because the
+      // class is missing.
+      if (!resolvedHolder.isInterface()) {
+        return null;
+      }
+      return lookupMaximallySpecificDispatchTarget(dynamicInstance, appView);
     }
 
-    private DexEncodedMethod lookupMaximallySpecificDispatchTarget(
+    private DexClassAndMethod lookupMaximallySpecificDispatchTarget(
         DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView) {
-      ResolutionResult maximallySpecificResult =
-          appView.appInfo().resolveMaximallySpecificMethods(dynamicInstance, resolvedMethod.method);
-      if (maximallySpecificResult.isSingleResolution()) {
-        return maximallySpecificResult.asSingleResolution().resolvedMethod;
-      }
-      return null;
+      return appView
+          .appInfo()
+          .lookupMaximallySpecificMethod(dynamicInstance, resolvedMethod.method);
     }
 
     /**
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
index 1049fb8..80c81e9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.analysis;
 
+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.DexDefinition;
@@ -13,6 +14,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
 import com.android.tools.r8.ir.code.DominatorTree;
@@ -34,11 +36,13 @@
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.OptionalBool;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
 import java.util.ArrayDeque;
+import java.util.BitSet;
 import java.util.Deque;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -192,33 +196,10 @@
     }
 
     for (CatchHandler<BasicBlock> catchHandler : dominator.getCatchHandlers()) {
-      if (!catchHandler.target.isMarked(markingColor)) {
-        // There is no path from this catch handler to the instruction of interest, so we can
-        // ignore it.
-        continue;
-      }
-
-      DexType guard = catchHandler.guard;
-      if (exceptionalExit.isInstanceGet()
-          || exceptionalExit.isInstancePut()
-          || exceptionalExit.isInvokeMethodWithReceiver()) {
-        // If an instance-get, instance-put, or instance-invoke instruction does not fail with a
-        // NullPointerException, then the receiver class must have been initialized.
-        OptionalBool isCatchingNPE = appView.isSubtype(dexItemFactory.npeType, guard);
-        if (isCatchingNPE.isPossiblyTrue()) {
-          return AnalysisAssumption.NONE;
-        }
-      }
-      if (exceptionalExit.isStaticGet()
-          || exceptionalExit.isStaticPut()
-          || exceptionalExit.isInvokeStatic()) {
-        // If a static-get, static-put, or invoke-static does not fail with an ExceptionIn-
-        // InitializerError, then the holder class must have been initialized.
-        OptionalBool isCatchingInitError =
-            appView.isSubtype(dexItemFactory.exceptionInInitializerErrorType, guard);
-        if (isCatchingInitError.isPossiblyTrue()) {
-          return AnalysisAssumption.NONE;
-        }
+      if (catchHandler.target.isMarked(markingColor)) {
+        // There is a path from this catch handler to the instruction of interest, so we can't make
+        // any assumptions.
+        return AnalysisAssumption.NONE;
       }
     }
 
@@ -288,7 +269,7 @@
         }
       }
       DexEncodedField field = appView.appInfo().resolveField(instruction.getField());
-      return field != null && isTypeInitializedBy(type, field, appView, mode);
+      return field != null && isTypeInitializedBy(instruction, type, field, appView, mode);
     }
 
     public static boolean forInvokeDirect(
@@ -304,7 +285,7 @@
         }
       }
       DexEncodedMethod method = appView.definitionFor(instruction.getInvokedMethod());
-      return method != null && isTypeInitializedBy(type, method, appView, mode);
+      return method != null && isTypeInitializedBy(instruction, type, method, appView, mode);
     }
 
     public static boolean forInvokeInterface(
@@ -330,7 +311,7 @@
         DexEncodedMethod singleTarget =
             instruction.lookupSingleTarget(appView.withLiveness(), context);
         if (singleTarget != null) {
-          return isTypeInitializedBy(type, singleTarget, appView, mode);
+          return isTypeInitializedBy(instruction, type, singleTarget, appView, mode);
         }
       }
       DexMethod method = instruction.getInvokedMethod();
@@ -346,6 +327,7 @@
     public static boolean forInvokeStatic(
         InvokeStatic instruction,
         DexType type,
+        DexType context,
         AppView<?> appView,
         Query mode,
         AnalysisAssumption assumption) {
@@ -353,8 +335,8 @@
         // Class initialization may fail with ExceptionInInitializerError.
         return false;
       }
-      DexEncodedMethod method = appView.definitionFor(instruction.getInvokedMethod());
-      return method != null && isTypeInitializedBy(type, method, appView, mode);
+      DexEncodedMethod method = instruction.lookupSingleTarget(appView, context);
+      return method != null && isTypeInitializedBy(instruction, type, method, appView, mode);
     }
 
     public static boolean forInvokeSuper(
@@ -380,7 +362,7 @@
         DexEncodedMethod singleTarget =
             instruction.lookupSingleTarget(appView.withLiveness(), context);
         if (singleTarget != null) {
-          return isTypeInitializedBy(type, singleTarget, appView, mode);
+          return isTypeInitializedBy(instruction, type, singleTarget, appView, mode);
         }
       }
       DexMethod method = instruction.getInvokedMethod();
@@ -424,7 +406,7 @@
         DexEncodedMethod singleTarget =
             instruction.lookupSingleTarget(appView.withLiveness(), context);
         if (singleTarget != null) {
-          return isTypeInitializedBy(type, singleTarget, appView, mode);
+          return isTypeInitializedBy(instruction, type, singleTarget, appView, mode);
         }
       }
       DexMethod method = instruction.getInvokedMethod();
@@ -448,7 +430,7 @@
         return false;
       }
       DexClass clazz = appView.definitionFor(instruction.clazz);
-      return clazz != null && isTypeInitializedBy(type, clazz, appView, mode);
+      return clazz != null && isTypeInitializedBy(instruction, type, clazz, appView, mode);
     }
 
     public static boolean forStaticGet(
@@ -481,23 +463,23 @@
         return false;
       }
       DexEncodedField field = appView.appInfo().resolveField(instruction.getField());
-      return field != null && isTypeInitializedBy(type, field, appView, mode);
+      return field != null && isTypeInitializedBy(instruction, type, field, appView, mode);
     }
 
     private static boolean isTypeInitializedBy(
-        DexType typeToBeInitialized, DexDefinition definition, AppView<?> appView, Query mode) {
+        Instruction instruction,
+        DexType typeToBeInitialized,
+        DexDefinition definition,
+        AppView<?> appView,
+        Query mode) {
       if (mode == Query.DIRECTLY) {
         if (definition.isDexClass()) {
           return definition.asDexClass().type == typeToBeInitialized;
         }
-        if (definition.isDexEncodedField()) {
-          return definition.asDexEncodedField().field.holder == typeToBeInitialized;
+        if (definition.isDexEncodedMember()) {
+          return definition.asDexEncodedMember().toReference().holder == typeToBeInitialized;
         }
-        if (definition.isDexEncodedMethod()) {
-          return definition.asDexEncodedMethod().method.holder == typeToBeInitialized;
-        }
-        assert false;
-        return false;
+        throw new Unreachable();
       }
 
       Set<DexType> visited = Sets.newIdentityHashSet();
@@ -510,9 +492,10 @@
         DexEncodedField field = definition.asDexEncodedField();
         enqueue(field.field.holder, visited, worklist);
       } else if (definition.isDexEncodedMethod()) {
+        assert instruction.isInvokeMethod();
         DexEncodedMethod method = definition.asDexEncodedMethod();
         enqueue(method.method.holder, visited, worklist);
-        enqueueInitializedClassesOnNormalExit(method, visited, worklist);
+        enqueueInitializedClassesOnNormalExit(method, instruction.inValues(), visited, worklist);
       } else {
         assert false;
       }
@@ -529,7 +512,8 @@
         if (clazz != null) {
           DexEncodedMethod classInitializer = clazz.getClassInitializer();
           if (classInitializer != null) {
-            enqueueInitializedClassesOnNormalExit(classInitializer, visited, worklist);
+            enqueueInitializedClassesOnNormalExit(
+                classInitializer, ImmutableList.of(), visited, worklist);
           }
         }
       }
@@ -544,10 +528,36 @@
     }
 
     private static void enqueueInitializedClassesOnNormalExit(
-        DexEncodedMethod method, Set<DexType> visited, Deque<DexType> worklist) {
+        DexEncodedMethod method,
+        List<Value> arguments,
+        Set<DexType> visited,
+        Deque<DexType> worklist) {
       for (DexType type : method.getOptimizationInfo().getInitializedClassesOnNormalExit()) {
         enqueue(type, visited, worklist);
       }
+      // If an invoke to an instance method succeeds, then the receiver must be non-null, which
+      // implies that the type of the receiver must be initialized.
+      if (!method.isStatic()) {
+        assert arguments.size() > 0;
+        TypeLatticeElement type = arguments.get(0).getTypeLattice();
+        if (type.isClassType()) {
+          enqueue(type.asClassTypeLatticeElement().getClassType(), visited, worklist);
+        }
+      }
+      // If an invoke to a method succeeds, and the method would have thrown and exception if the
+      // i'th argument was null, then the i'th argument must be non-null, which implies that the
+      // type of the i'th argument must be initialized.
+      BitSet nonNullParamOrThrowFacts = method.getOptimizationInfo().getNonNullParamOrThrow();
+      if (nonNullParamOrThrowFacts != null) {
+        for (int i = 0; i < arguments.size(); i++) {
+          if (nonNullParamOrThrowFacts.get(i)) {
+            TypeLatticeElement type = arguments.get(i).getTypeLattice();
+            if (type.isClassType()) {
+              enqueue(type.asClassTypeLatticeElement().getClassType(), visited, worklist);
+            }
+          }
+        }
+      }
     }
   }
 }
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 0bef6b1..4064d79 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
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.analysis.fieldaccess;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -12,6 +14,7 @@
 import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.BottomValue;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
@@ -164,12 +167,22 @@
               initializationInfo.asArgumentInitializationInfo();
           Value argument = invoke.arguments().get(argumentInitializationInfo.getArgumentIndex());
           AbstractValue abstractValue =
-              argument.getAbstractValue(appView, context.method.holder).join(entry.getValue());
+              entry.getValue().join(argument.getAbstractValue(appView, context.method.holder));
           assert !abstractValue.isBottom();
           if (!abstractValue.isUnknown()) {
             entry.setValue(abstractValue);
             continue;
           }
+        } else if (initializationInfo.isSingleValue()) {
+          SingleValue singleValueInitializationInfo = initializationInfo.asSingleValue();
+          AbstractValue abstractValue = entry.getValue().join(singleValueInitializationInfo);
+          assert !abstractValue.isBottom();
+          if (!abstractValue.isUnknown()) {
+            entry.setValue(abstractValue);
+            continue;
+          }
+        } else if (initializationInfo.isTypeInitializationInfo()) {
+          // TODO(b/149732532): Not handled, for now.
         } else {
           assert initializationInfo.isUnknown();
         }
@@ -182,10 +195,56 @@
 
   private void recordAllFieldPutsProcessed(
       DexEncodedField field, OptimizationFeedbackDelayed feedback) {
+    DexProgramClass clazz = asProgramClassOrNull(appView.definitionForHolder(field));
+    if (clazz == null) {
+      assert false;
+      return;
+    }
+
     if (isAlwaysZero(field)) {
       feedback.recordFieldHasAbstractValue(
           field, appView, appView.abstractValueFactory().createSingleNumberValue(0));
     }
+
+    if (!field.isStatic()) {
+      recordAllInstanceFieldPutsProcessed(clazz, field, feedback);
+    }
+  }
+
+  private void recordAllInstanceFieldPutsProcessed(
+      DexProgramClass clazz, DexEncodedField field, OptimizationFeedbackDelayed feedback) {
+    if (appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)) {
+      AbstractValue abstractValue = BottomValue.getInstance();
+      for (DexEncodedMethod method : clazz.directMethods(DexEncodedMethod::isInstanceInitializer)) {
+        InstanceFieldInitializationInfo fieldInitializationInfo =
+            method
+                .getOptimizationInfo()
+                .getInstanceInitializerInfo()
+                .fieldInitializationInfos()
+                .get(field);
+        if (fieldInitializationInfo.isSingleValue()) {
+          abstractValue = abstractValue.join(fieldInitializationInfo.asSingleValue());
+          if (abstractValue.isUnknown()) {
+            break;
+          }
+        } else if (fieldInitializationInfo.isTypeInitializationInfo()) {
+          // TODO(b/149732532): Not handled, for now.
+          abstractValue = UnknownValue.getInstance();
+          break;
+        } else {
+          assert fieldInitializationInfo.isArgumentInitializationInfo()
+              || fieldInitializationInfo.isUnknown();
+          abstractValue = UnknownValue.getInstance();
+          break;
+        }
+      }
+
+      assert !abstractValue.isBottom();
+
+      if (!abstractValue.isUnknown()) {
+        feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
+      }
+    }
   }
 
   private void recordAllAllocationsSitesProcessed(
@@ -211,11 +270,16 @@
   }
 
   public void waveDone(Collection<DexEncodedMethod> wave, OptimizationFeedbackDelayed feedback) {
+    // This relies on the instance initializer info in the method optimization feedback. It is
+    // therefore important that the optimization info has been flushed in advance.
+    assert feedback.noUpdatesLeft();
     for (DexEncodedMethod method : wave) {
       fieldAccessGraph.markProcessed(method, field -> recordAllFieldPutsProcessed(field, feedback));
       objectAllocationGraph.markProcessed(
           method, clazz -> recordAllAllocationsSitesProcessed(clazz, feedback));
     }
+    feedback.refineAppInfoWithLiveness(appView.appInfo().withLiveness());
+    feedback.updateVisibleOptimizationInfo();
   }
 
   private boolean verifyValueIsConsistentWithFieldOptimizationInfo(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
index 2289a28..a9c7cac 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
@@ -4,24 +4,12 @@
 
 package com.android.tools.r8.ir.analysis.fieldvalueanalysis;
 
-import static com.android.tools.r8.ir.code.Opcodes.ARRAY_PUT;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
-import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
-
 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.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.SingleEnumValue;
-import com.android.tools.r8.ir.analysis.value.UnknownValue;
-import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.DominatorTree;
 import com.android.tools.r8.ir.code.DominatorTree.Assumption;
@@ -29,8 +17,6 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
-import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
@@ -45,11 +31,11 @@
 
 public abstract class FieldValueAnalysis {
 
-  private final AppView<AppInfoWithLiveness> appView;
-  private final DexProgramClass clazz;
+  final AppView<AppInfoWithLiveness> appView;
+  final DexProgramClass clazz;
   private final IRCode code;
-  private final OptimizationFeedback feedback;
-  private final DexEncodedMethod method;
+  final OptimizationFeedback feedback;
+  final DexEncodedMethod method;
 
   private Map<BasicBlock, AbstractFieldSet> fieldsMaybeReadBeforeBlockInclusiveCache;
 
@@ -75,13 +61,13 @@
     return fieldsMaybeReadBeforeBlockInclusiveCache;
   }
 
+  abstract boolean isSubjectToOptimization(DexEncodedField field);
+
   /** This method analyzes initializers with the purpose of computing field optimization info. */
   void computeFieldOptimizationInfo(ClassInitializerDefaultsResult classInitializerDefaultsResult) {
     AppInfoWithLiveness appInfo = appView.appInfo();
     DominatorTree dominatorTree = null;
 
-    DexType context = method.method.holder;
-
     // Find all the static-put instructions that assign a field in the enclosing class which is
     // guaranteed to be assigned only in the current initializer.
     boolean isStraightLineCode = true;
@@ -95,10 +81,7 @@
           FieldInstruction fieldPut = instruction.asFieldInstruction();
           DexField field = fieldPut.getField();
           DexEncodedField encodedField = appInfo.resolveField(field);
-          if (encodedField != null
-              && encodedField.field.holder == context
-              && encodedField.isStatic() == method.isStatic()
-              && appInfo.isFieldOnlyWrittenInMethod(encodedField, method)) {
+          if (encodedField != null && isSubjectToOptimization(encodedField)) {
             putsPerField.computeIfAbsent(encodedField, ignore -> new LinkedList<>()).add(fieldPut);
           }
         }
@@ -126,7 +109,7 @@
       if (!priorReadsWillReadSameValue && fieldMaybeReadBeforeInstruction(encodedField, fieldPut)) {
         continue;
       }
-      updateFieldOptimizationInfo(encodedField, fieldPut.value());
+      updateFieldOptimizationInfo(encodedField, fieldPut, fieldPut.value());
     }
   }
 
@@ -275,141 +258,6 @@
     return true;
   }
 
-  void updateFieldOptimizationInfo(DexEncodedField field, Value value) {
-    // Abstract value.
-    Value root = value.getAliasedValue();
-    AbstractValue abstractValue = computeAbstractValue(root);
-    if (abstractValue.isUnknown()) {
-      if (field.isStatic()) {
-        feedback.recordFieldHasAbstractValue(
-            field, appView, appView.abstractValueFactory().createSingleFieldValue(field.field));
-      }
-    } else {
-      feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
-    }
-
-    // Dynamic upper bound type.
-    TypeLatticeElement fieldType =
-        TypeLatticeElement.fromDexType(field.field.type, Nullability.maybeNull(), appView);
-    TypeLatticeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
-    if (dynamicUpperBoundType.strictlyLessThan(fieldType, appView)) {
-      feedback.markFieldHasDynamicUpperBoundType(field, dynamicUpperBoundType);
-    }
-
-    // Dynamic lower bound type.
-    ClassTypeLatticeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
-    if (dynamicLowerBoundType != null) {
-      assert dynamicLowerBoundType.lessThanOrEqual(dynamicUpperBoundType, appView);
-      feedback.markFieldHasDynamicLowerBoundType(field, dynamicLowerBoundType);
-    }
-  }
-
-  private AbstractValue computeAbstractValue(Value value) {
-    assert !value.hasAliasedValue();
-    if (clazz.isEnum()) {
-      SingleEnumValue singleEnumValue = getSingleEnumValue(value);
-      if (singleEnumValue != null) {
-        return singleEnumValue;
-      }
-    }
-    if (!value.isPhi()) {
-      return value.definition.getAbstractValue(appView, clazz.type);
-    }
-    return UnknownValue.getInstance();
-  }
-
-  /**
-   * If {@param value} is defined by a new-instance instruction that instantiates the enclosing enum
-   * class, and the value is assigned into exactly one static enum field on the enclosing enum
-   * class, then returns a {@link SingleEnumValue} instance. Otherwise, returns {@code null}.
-   *
-   * <p>Note that enum constructors also store the newly instantiated enums in the {@code $VALUES}
-   * array field on the enum. Therefore, this code also allows {@param value} to be stored into an
-   * array as long as the array is identified as being the {@code $VALUES} array.
-   */
-  private SingleEnumValue getSingleEnumValue(Value value) {
-    assert clazz.isEnum();
-    assert !value.hasAliasedValue();
-    if (value.isPhi() || !value.definition.isNewInstance()) {
-      return null;
-    }
-
-    NewInstance newInstance = value.definition.asNewInstance();
-    if (newInstance.clazz != clazz.type) {
-      return null;
-    }
-
-    if (value.hasDebugUsers() || value.hasPhiUsers()) {
-      return null;
-    }
-
-    DexEncodedField enumField = null;
-    for (Instruction user : value.uniqueUsers()) {
-      switch (user.opcode()) {
-        case ARRAY_PUT:
-          // Check that this is assigning the enum into the enum values array.
-          ArrayPut arrayPut = user.asArrayPut();
-          if (arrayPut.value().getAliasedValue() != value || !isEnumValuesArray(arrayPut.array())) {
-            return null;
-          }
-          break;
-
-        case INVOKE_DIRECT:
-          // Check that this is the corresponding constructor call.
-          InvokeDirect invoke = user.asInvokeDirect();
-          if (!appView.dexItemFactory().isConstructor(invoke.getInvokedMethod())
-              || invoke.getReceiver() != value) {
-            return null;
-          }
-          break;
-
-        case STATIC_PUT:
-          DexEncodedField field = clazz.lookupStaticField(user.asStaticPut().getField());
-          if (field != null && field.accessFlags.isEnum()) {
-            if (enumField != null) {
-              return null;
-            }
-            enumField = field;
-          }
-          break;
-
-        default:
-          return null;
-      }
-    }
-
-    if (enumField == null) {
-      return null;
-    }
-
-    return appView.abstractValueFactory().createSingleEnumValue(enumField.field);
-  }
-
-  private boolean isEnumValuesArray(Value value) {
-    assert clazz.isEnum();
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    DexField valuesField =
-        dexItemFactory.createField(
-            clazz.type,
-            clazz.type.toArrayType(1, dexItemFactory),
-            dexItemFactory.enumValuesFieldName);
-
-    Value root = value.getAliasedValue();
-    if (root.isPhi()) {
-      return false;
-    }
-
-    Instruction definition = root.definition;
-    if (definition.isNewArrayEmpty()) {
-      for (Instruction user : root.aliasedUsers()) {
-        if (user.isStaticPut() && user.asStaticPut().getField() == valuesField) {
-          return true;
-        }
-      }
-    } else if (definition.isStaticGet()) {
-      return definition.asStaticGet().getField() == valuesField;
-    }
-
-    return false;
-  }
+  abstract void updateFieldOptimizationInfo(
+      DexEncodedField field, FieldInstruction fieldPut, Value value);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
index ecc93b7..447834f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
@@ -4,11 +4,18 @@
 
 package com.android.tools.r8.ir.analysis.fieldvalueanalysis;
 
+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.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.Argument;
+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.Value;
@@ -55,13 +62,6 @@
     if (!appView.options().enableValuePropagationForInstanceFields) {
       return EmptyInstanceFieldInitializationInfoCollection.getInstance();
     }
-    DexEncodedMethod otherInstanceInitializer =
-        clazz.lookupDirectMethod(other -> other.isInstanceInitializer() && other != method);
-    if (otherInstanceInitializer != null) {
-      // Conservatively bail out.
-      // TODO(b/125282093): Handle multiple instance initializers on the same class.
-      return EmptyInstanceFieldInitializationInfoCollection.getInstance();
-    }
     InstanceFieldValueAnalysis analysis =
         new InstanceFieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method);
     analysis.computeFieldOptimizationInfo(classInitializerDefaultsResult);
@@ -69,16 +69,53 @@
   }
 
   @Override
-  void updateFieldOptimizationInfo(DexEncodedField field, Value value) {
-    super.updateFieldOptimizationInfo(field, value);
+  boolean isSubjectToOptimization(DexEncodedField field) {
+    return !field.isStatic() && field.holder() == clazz.type;
+  }
 
-    // If this instance field is initialized with an argument, then record this in the instance
-    // field initialization info.
+  @Override
+  void updateFieldOptimizationInfo(DexEncodedField field, FieldInstruction fieldPut, Value value) {
+    if (fieldMaybeWrittenBetweenInstructionAndMethodExit(field, fieldPut)) {
+      return;
+    }
+
+    // If this instance field is initialized with an argument or a constant, then record this in the
+    // instance field initialization info.
     Value root = value.getAliasedValue();
     if (root.isDefinedByInstructionSatisfying(Instruction::isArgument)) {
       Argument argument = root.definition.asArgument();
       builder.recordInitializationInfo(
           field, factory.createArgumentInitializationInfo(argument.getIndex()));
+      return;
     }
+
+    AbstractValue abstractValue = value.getAbstractValue(appView, clazz.type);
+    if (abstractValue.isSingleValue()) {
+      builder.recordInitializationInfo(field, abstractValue.asSingleValue());
+      return;
+    }
+
+    DexType fieldType = field.field.type;
+    if (fieldType.isClassType()) {
+      ClassTypeLatticeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
+      TypeLatticeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
+      TypeLatticeElement staticFieldType =
+          TypeLatticeElement.fromDexType(fieldType, maybeNull(), appView);
+      if (dynamicLowerBoundType != null || !dynamicUpperBoundType.equals(staticFieldType)) {
+        builder.recordInitializationInfo(
+            field,
+            factory.createTypeInitializationInfo(dynamicLowerBoundType, dynamicUpperBoundType));
+      }
+    }
+  }
+
+  private boolean fieldMaybeWrittenBetweenInstructionAndMethodExit(
+      DexEncodedField field, FieldInstruction fieldPut) {
+    if (field.isFinal()
+        || appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)) {
+      return false;
+    }
+    // Otherwise, conservatively return true.
+    return true;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 0c91855..f25c566 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -4,10 +4,27 @@
 
 package com.android.tools.r8.ir.analysis.fieldvalueanalysis;
 
+import static com.android.tools.r8.ir.code.Opcodes.*;
+
 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.DexProgramClass;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleEnumValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
+import com.android.tools.r8.ir.code.ArrayPut;
+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.InvokeDirect;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -36,4 +53,148 @@
     new StaticFieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method)
         .computeFieldOptimizationInfo(classInitializerDefaultsResult);
   }
+
+  @Override
+  boolean isSubjectToOptimization(DexEncodedField field) {
+    return field.isStatic()
+        && field.holder() == clazz.type
+        && appView.appInfo().isFieldOnlyWrittenInMethod(field, method);
+  }
+
+  @Override
+  void updateFieldOptimizationInfo(DexEncodedField field, FieldInstruction fieldPut, Value value) {
+    // Abstract value.
+    Value root = value.getAliasedValue();
+    AbstractValue abstractValue = computeAbstractValue(root);
+    if (abstractValue.isUnknown()) {
+      feedback.recordFieldHasAbstractValue(
+          field, appView, appView.abstractValueFactory().createSingleFieldValue(field.field));
+    } else {
+      feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
+    }
+
+    // Dynamic upper bound type.
+    TypeLatticeElement fieldType =
+        TypeLatticeElement.fromDexType(field.field.type, Nullability.maybeNull(), appView);
+    TypeLatticeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
+    if (dynamicUpperBoundType.strictlyLessThan(fieldType, appView)) {
+      feedback.markFieldHasDynamicUpperBoundType(field, dynamicUpperBoundType);
+    }
+
+    // Dynamic lower bound type.
+    ClassTypeLatticeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
+    if (dynamicLowerBoundType != null) {
+      assert dynamicLowerBoundType.lessThanOrEqual(dynamicUpperBoundType, appView);
+      feedback.markFieldHasDynamicLowerBoundType(field, dynamicLowerBoundType);
+    }
+  }
+
+  private AbstractValue computeAbstractValue(Value value) {
+    assert !value.hasAliasedValue();
+    if (clazz.isEnum()) {
+      SingleEnumValue singleEnumValue = getSingleEnumValue(value);
+      if (singleEnumValue != null) {
+        return singleEnumValue;
+      }
+    }
+    if (!value.isPhi()) {
+      return value.definition.getAbstractValue(appView, clazz.type);
+    }
+    return UnknownValue.getInstance();
+  }
+
+  /**
+   * If {@param value} is defined by a new-instance instruction that instantiates the enclosing enum
+   * class, and the value is assigned into exactly one static enum field on the enclosing enum
+   * class, then returns a {@link SingleEnumValue} instance. Otherwise, returns {@code null}.
+   *
+   * <p>Note that enum constructors also store the newly instantiated enums in the {@code $VALUES}
+   * array field on the enum. Therefore, this code also allows {@param value} to be stored into an
+   * array as long as the array is identified as being the {@code $VALUES} array.
+   */
+  private SingleEnumValue getSingleEnumValue(Value value) {
+    assert clazz.isEnum();
+    assert !value.hasAliasedValue();
+    if (value.isPhi() || !value.definition.isNewInstance()) {
+      return null;
+    }
+
+    NewInstance newInstance = value.definition.asNewInstance();
+    if (newInstance.clazz != clazz.type) {
+      return null;
+    }
+
+    if (value.hasDebugUsers() || value.hasPhiUsers()) {
+      return null;
+    }
+
+    DexEncodedField enumField = null;
+    for (Instruction user : value.uniqueUsers()) {
+      switch (user.opcode()) {
+        case ARRAY_PUT:
+          // Check that this is assigning the enum into the enum values array.
+          ArrayPut arrayPut = user.asArrayPut();
+          if (arrayPut.value().getAliasedValue() != value || !isEnumValuesArray(arrayPut.array())) {
+            return null;
+          }
+          break;
+
+        case INVOKE_DIRECT:
+          // Check that this is the corresponding constructor call.
+          InvokeDirect invoke = user.asInvokeDirect();
+          if (!appView.dexItemFactory().isConstructor(invoke.getInvokedMethod())
+              || invoke.getReceiver() != value) {
+            return null;
+          }
+          break;
+
+        case STATIC_PUT:
+          DexEncodedField field = clazz.lookupStaticField(user.asStaticPut().getField());
+          if (field != null && field.accessFlags.isEnum()) {
+            if (enumField != null) {
+              return null;
+            }
+            enumField = field;
+          }
+          break;
+
+        default:
+          return null;
+      }
+    }
+
+    if (enumField == null) {
+      return null;
+    }
+
+    return appView.abstractValueFactory().createSingleEnumValue(enumField.field);
+  }
+
+  private boolean isEnumValuesArray(Value value) {
+    assert clazz.isEnum();
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    DexField valuesField =
+        dexItemFactory.createField(
+            clazz.type,
+            clazz.type.toArrayType(1, dexItemFactory),
+            dexItemFactory.enumValuesFieldName);
+
+    Value root = value.getAliasedValue();
+    if (root.isPhi()) {
+      return false;
+    }
+
+    Instruction definition = root.definition;
+    if (definition.isNewArrayEmpty()) {
+      for (Instruction user : root.aliasedUsers()) {
+        if (user.isStaticPut() && user.asStaticPut().getField() == valuesField) {
+          return true;
+        }
+      }
+    } else if (definition.isStaticGet()) {
+      return definition.asStaticGet().getField() == valuesField;
+    }
+
+    return false;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
index 44aa581..c7b249f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
@@ -10,8 +10,9 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
 
-public abstract class SingleValue extends AbstractValue {
+public abstract class SingleValue extends AbstractValue implements InstanceFieldInitializationInfo {
 
   @Override
   public boolean isNonTrivial() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index c8d6abc..69c6281 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -27,6 +27,15 @@
 
 public abstract class FieldInstruction extends Instruction {
 
+  public enum Assumption {
+    NONE,
+    RECEIVER_NOT_NULL;
+
+    boolean canAssumeReceiverIsNotNull() {
+      return this == RECEIVER_NOT_NULL;
+    }
+  }
+
   private final DexField field;
 
   protected FieldInstruction(DexField field, Value dest, Value value) {
@@ -61,6 +70,11 @@
 
   @Override
   public AbstractError instructionInstanceCanThrow(AppView<?> appView, DexType context) {
+    return instructionInstanceCanThrow(appView, context, Assumption.NONE);
+  }
+
+  public AbstractError instructionInstanceCanThrow(
+      AppView<?> appView, DexType context, Assumption assumption) {
     DexEncodedField resolvedField;
     if (appView.enableWholeProgramOptimizations()) {
       // TODO(b/123857022): Should be possible to use definitionFor().
@@ -106,9 +120,11 @@
     // TODO(b/137168535): Without non-null tracking, only locally created receiver is allowed in D8.
     // * NullPointerException (null receiver).
     if (isInstanceGet() || isInstancePut()) {
-      Value receiver = inValues.get(0);
-      if (receiver.isAlwaysNull(appView) || receiver.typeLattice.isNullable()) {
-        return AbstractError.specific(appView.dexItemFactory().npeType);
+      if (!assumption.canAssumeReceiverIsNotNull()) {
+        Value receiver = inValues.get(0);
+        if (receiver.isAlwaysNull(appView) || receiver.typeLattice.isNullable()) {
+          return AbstractError.specific(appView.dexItemFactory().npeType);
+        }
       }
     }
     // For D8, reaching here means the field is in the same context, hence the class is guaranteed
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 3155fa1..2ca1b2a 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
@@ -116,7 +116,12 @@
 
   @Override
   public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
-    return instructionInstanceCanThrow(appView, context).isThrowing();
+    return instructionMayHaveSideEffects(appView, context, Assumption.NONE);
+  }
+
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, Assumption assumption) {
+    return instructionInstanceCanThrow(appView, context, assumption).isThrowing();
   }
 
   @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 87b93a4..a42988e 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
@@ -82,7 +82,8 @@
         DexType refinedReceiverType =
             TypeAnalysis.getRefinedReceiverType(appViewWithLiveness, this);
         assert receiverLowerBoundType.getClassType() == refinedReceiverType
-            || receiverLowerBoundType.isBasedOnMissingClass(appViewWithLiveness);
+                || receiverLowerBoundType.isBasedOnMissingClass(appViewWithLiveness)
+            : "The receiver lower bound does not match the receiver type";
       }
     }
 
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 3a7969a..da1c8fd 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
@@ -152,7 +152,7 @@
       Query mode,
       AnalysisAssumption assumption) {
     return ClassInitializationAnalysis.InstructionUtils.forInvokeStatic(
-        this, clazz, appView, mode, assumption);
+        this, clazz, context, appView, mode, assumption);
   }
 
   @Override
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 78b1405..0e24fe9 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
@@ -33,7 +33,6 @@
 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.GraphLense;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection;
@@ -424,50 +423,66 @@
   // then the IR does not necessarily contain a const-string instruction).
   private final IRMetadata metadata = new IRMetadata();
 
-  public IRBuilder(DexEncodedMethod method, AppView<?> appView, SourceCode source, Origin origin) {
-    this(method, appView, source, origin, new ValueNumberGenerator());
+  public static IRBuilder create(DexEncodedMethod method,
+      AppView<?> appView,
+      SourceCode source,
+      Origin origin) {
+    return new IRBuilder(method,
+        appView,
+        source,
+        origin,
+        lookupPrototypeChanges(appView, method.method),
+        new ValueNumberGenerator());
   }
 
-  public IRBuilder(
+  public static IRBuilder createForInlining(DexEncodedMethod method,
+      AppView<?> appView,
+      SourceCode source,
+      Origin origin,
+      MethodProcessor processor,
+      ValueNumberGenerator valueNumberGenerator) {
+    RewrittenPrototypeDescription protoChanges = processor.shouldApplyCodeRewritings(method) ?
+        lookupPrototypeChanges(appView, method.method) :
+        RewrittenPrototypeDescription.none();
+    return new IRBuilder(method,
+        appView,
+        source,
+        origin,
+        protoChanges,
+        valueNumberGenerator);
+  }
+
+  private static RewrittenPrototypeDescription lookupPrototypeChanges(AppView<?> appView,
+      DexMethod method) {
+    RewrittenPrototypeDescription prototypeChanges = appView.graphLense()
+        .lookupPrototypeChanges(method);
+    if (Log.ENABLED
+        && prototypeChanges.getRemovedArgumentInfoCollection().hasRemovedArguments()) {
+      Log.info(
+          IRBuilder.class,
+          "Removed "
+              + prototypeChanges.getRemovedArgumentInfoCollection().numberOfRemovedArguments()
+              + " arguments from "
+              + method.toSourceString());
+    }
+    return prototypeChanges;
+  }
+
+  private IRBuilder(
       DexEncodedMethod method,
       AppView<?> appView,
       SourceCode source,
       Origin origin,
+      RewrittenPrototypeDescription prototypeChanges,
       ValueNumberGenerator valueNumberGenerator) {
     assert source != null;
+    assert valueNumberGenerator != null;
     this.method = method;
     this.appView = appView;
     this.source = source;
-    this.valueNumberGenerator =
-        valueNumberGenerator != null ? valueNumberGenerator : new ValueNumberGenerator();
     this.origin = origin;
-
-    if (method.isProcessed()) {
-      // NOTE: This is currently assuming that we never remove additional arguments from methods
-      // after they have already been processed once.
-      assert verifyMethodSignature(method, appView.graphLense());
-      this.prototypeChanges = RewrittenPrototypeDescription.none();
-    } else {
-      this.prototypeChanges = appView.graphLense().lookupPrototypeChanges(method.method);
-
-      if (Log.ENABLED
-          && prototypeChanges.getRemovedArgumentInfoCollection().hasRemovedArguments()) {
-        Log.info(
-            getClass(),
-            "Removed "
-                + prototypeChanges.getRemovedArgumentInfoCollection().numberOfRemovedArguments()
-                + " arguments from "
-                + method.toSourceString());
-      }
-    }
-  }
-
-  private static boolean verifyMethodSignature(DexEncodedMethod method, GraphLense graphLense) {
-    RewrittenPrototypeDescription prototypeChanges =
-        graphLense.lookupPrototypeChanges(method.method);
-    assert !prototypeChanges.hasBeenChangedToReturnVoid()
-        || method.method.proto.returnType.isVoidType();
-    return true;
+    this.prototypeChanges = prototypeChanges;
+    this.valueNumberGenerator = valueNumberGenerator;
   }
 
   public DexEncodedMethod getMethod() {
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 ce65222..94e5cf4 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
@@ -716,12 +716,11 @@
       // TODO(b/127694949): Adapt to PostOptimization.
       staticizeClasses(feedback, executorService);
       feedback.updateVisibleOptimizationInfo();
+      // The class staticizer lens shall not be applied through lens code rewriting or it breaks
+      // the lambda merger.
+      appView.clearCodeRewritings();
     }
 
-    // The class staticizer lens shall not be applied through lens code rewriting or it breaks
-    // the lambda merger.
-    appView.clearCodeRewritings();
-
     // Build a new application with jumbo string info.
     Builder<?> builder = application.builder();
     builder.setHighestSortingString(highestSortingString);
@@ -831,11 +830,12 @@
   }
 
   private void waveDone(Collection<DexEncodedMethod> wave) {
+    delayedOptimizationFeedback.refineAppInfoWithLiveness(appView.appInfo().withLiveness());
+    delayedOptimizationFeedback.updateVisibleOptimizationInfo();
     if (options.enableFieldAssignmentTracker) {
       fieldAccessAnalysis.fieldAssignmentTracker().waveDone(wave, delayedOptimizationFeedback);
     }
-    delayedOptimizationFeedback.refineAppInfoWithLiveness(appView.appInfo().withLiveness());
-    delayedOptimizationFeedback.updateVisibleOptimizationInfo();
+    assert delayedOptimizationFeedback.noUpdatesLeft();
     onWaveDoneActions.forEach(com.android.tools.r8.utils.Action::execute);
     onWaveDoneActions = null;
   }
@@ -1147,7 +1147,7 @@
 
     if (lambdaMerger != null) {
       timing.begin("Merge lambdas");
-      lambdaMerger.rewriteCode(method, code, inliner);
+      lambdaMerger.rewriteCode(method, code, inliner, methodProcessor);
       timing.end();
       assert code.isConsistentSSA();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index 34609c9..0c2eb60 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -15,6 +15,8 @@
 
   Phase getPhase();
 
+  boolean shouldApplyCodeRewritings(DexEncodedMethod method);
+
   default boolean isPrimary() {
     return getPhase() == Phase.PRIMARY;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
index b55f54f..939395f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
@@ -31,6 +31,11 @@
   }
 
   @Override
+  public boolean shouldApplyCodeRewritings(DexEncodedMethod method) {
+    return true;
+  }
+
+  @Override
   public Phase getPhase() {
     return Phase.ONE_TIME;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index 96c93a9..529bdc7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Deque;
@@ -31,8 +32,9 @@
 
   private final AppView<AppInfoWithLiveness> appView;
   private final Map<DexEncodedMethod, Collection<CodeOptimization>> methodsMap;
-  private final Deque<Collection<DexEncodedMethod>> waves;
-  private Collection<DexEncodedMethod> wave;
+  private final Deque<Set<DexEncodedMethod>> waves;
+  private Set<DexEncodedMethod> wave;
+  private final Set<DexEncodedMethod> processed = Sets.newIdentityHashSet();
 
   private PostMethodProcessor(
       AppView<AppInfoWithLiveness> appView,
@@ -48,6 +50,12 @@
     return Phase.POST;
   }
 
+  @Override
+  public boolean shouldApplyCodeRewritings(DexEncodedMethod method) {
+    assert !wave.contains(method);
+    return !processed.contains(method);
+  }
+
   static class Builder {
     private final Collection<CodeOptimization> defaultCodeOptimizations;
     private final Map<DexEncodedMethod, Collection<CodeOptimization>> methodsMap =
@@ -107,9 +115,9 @@
     }
   }
 
-  private Deque<Collection<DexEncodedMethod>> createWaves(AppView<?> appView, CallGraph callGraph) {
+  private Deque<Set<DexEncodedMethod>> createWaves(AppView<?> appView, CallGraph callGraph) {
     IROrdering shuffle = appView.options().testing.irOrdering;
-    Deque<Collection<DexEncodedMethod>> waves = new ArrayDeque<>();
+    Deque<Set<DexEncodedMethod>> waves = new ArrayDeque<>();
 
     int waveCount = 1;
     while (!callGraph.isEmpty()) {
@@ -141,6 +149,7 @@
             forEachMethod(method, codeOptimizations, feedback);
           },
           executorService);
+      processed.addAll(wave);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index 8fb6afb..5b26255 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -65,6 +65,12 @@
   }
 
   @Override
+  public boolean shouldApplyCodeRewritings(DexEncodedMethod method) {
+    assert !wave.contains(method);
+    return !method.isProcessed();
+  }
+
+  @Override
   public CallSiteInformation getCallSiteInformation() {
     return callSiteInformation;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 2f54714..76e97df 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexLibraryClass;
@@ -319,14 +320,10 @@
     // If target is a non-interface library class it may be an emulated interface.
     if (!libraryHolder.isInterface()) {
       // Here we use step-3 of resolution to find a maximally specific default interface method.
-      target =
-          appView
-              .appInfo()
-              .resolveMaximallySpecificMethods(libraryHolder, method)
-              .getSingleTarget();
-      if (target != null && rewriter.isEmulatedInterface(target.method.holder)) {
-        targetHolder = appView.definitionFor(target.method.holder);
-        addForward.accept(targetHolder, target);
+      DexClassAndMethod result =
+          appView.appInfo().lookupMaximallySpecificMethod(libraryHolder, method);
+      if (result != null && rewriter.isEmulatedInterface(result.getHolder().type)) {
+        addForward.accept(result.getHolder(), result.getMethod());
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 0e8cbae..f6693eb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -399,11 +400,18 @@
     if (appView.rewritePrefix.hasRewrittenType(dexClass.type, appView)) {
       return null;
     }
-    DexEncodedMethod singleTarget =
-        appView
-            .appInfo()
-            .resolveMaximallySpecificMethods(dexClass, invokedMethod)
-            .getSingleTarget();
+    DexEncodedMethod singleTarget = null;
+    if (dexClass.isInterface()) {
+      // Look for exact method on the interface.
+      singleTarget = dexClass.lookupMethod(invokedMethod);
+    }
+    if (singleTarget == null) {
+      DexClassAndMethod result =
+          appView.appInfo().lookupMaximallySpecificMethod(dexClass, invokedMethod);
+      if (result != null) {
+        singleTarget = result.getMethod();
+      }
+    }
     if (singleTarget == null) {
       // At this point we are in a library class. Failures can happen with NoSuchMethod if a
       // library class implement a method with same signature but not related to emulated
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 0546e9d..023ef1c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -1,9 +1,9 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// 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.
 
 // ***********************************************************************************
-// GENERATED FILE. DO NOT EDIT! Changes should be made to GenerateBackportMethods.java
+// GENERATED FILE. DO NOT EDIT! See GenerateBackportMethods.java.
 // ***********************************************************************************
 
 package com.android.tools.r8.ir.desugar.backports;
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 7618cb6..ee62dc3 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
@@ -718,11 +718,12 @@
         }
       }
 
-      if (!target.isProcessed()) {
+      if (inliningIRProvider.shouldApplyCodeRewritings(code.method)) {
+        assert lensCodeRewriter != null;
         lensCodeRewriter.rewrite(code, target);
       }
       if (lambdaMerger != null) {
-        lambdaMerger.rewriteCodeForInlining(target, code, context);
+        lambdaMerger.rewriteCodeForInlining(target, code, context, inliningIRProvider);
       }
       assert code.isConsistentSSA();
       return new InlineeWithReason(code, reason);
@@ -817,14 +818,6 @@
   public void performForcedInlining(
       DexEncodedMethod method,
       IRCode code,
-      Map<? extends InvokeMethod, InliningInfo> invokesToInline) {
-    performForcedInlining(
-        method, code, invokesToInline, new InliningIRProvider(appView, method, code));
-  }
-
-  public void performForcedInlining(
-      DexEncodedMethod method,
-      IRCode code,
       Map<? extends InvokeMethod, InliningInfo> invokesToInline,
       InliningIRProvider inliningIRProvider) {
     ForcedInliningOracle oracle = new ForcedInliningOracle(appView, method, invokesToInline);
@@ -859,7 +852,8 @@
             options.inliningInstructionLimit,
             options.inliningInstructionAllowance - numberOfInstructions(code),
             inliningReasonStrategy);
-    InliningIRProvider inliningIRProvider = new InliningIRProvider(appView, method, code);
+    InliningIRProvider inliningIRProvider =
+        new InliningIRProvider(appView, method, code, methodProcessor);
     assert inliningIRProvider.verifyIRCacheIsEmpty();
     performInliningImpl(oracle, oracle, method, code, feedback, inliningIRProvider);
   }
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 722625f..a28181f 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
@@ -20,9 +20,12 @@
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.IRMetadata;
+import com.android.tools.r8.ir.code.InstanceGet;
 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.InvokeStatic;
+import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
@@ -31,6 +34,7 @@
 import com.android.tools.r8.shaking.ProguardMemberRuleReturnValue;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.ListIterator;
 import java.util.Set;
@@ -336,12 +340,24 @@
         target.valueAsConstInstruction(code, current.outValue().getLocalInfo(), appView);
     if (replacement != null) {
       affectedValues.addAll(current.outValue().affectedValues());
-      if (current.instructionMayHaveSideEffects(appView, code.method.method.holder)) {
-        // To preserve class initialization/NPE side effects, original field-get remains as-is,
-        // but its value is replaced with constant.
-        replacement.setPosition(current.getPosition());
+      DexType context = code.method.method.holder;
+      if (current.instructionMayHaveSideEffects(appView, context)) {
+        // All usages are replaced by the replacement value.
         current.outValue().replaceUsers(replacement.outValue());
-        if (current.getBlock().hasCatchHandlers()) {
+
+        // To preserve side effects, original field-get is replaced by an explicit null-check, if
+        // the field-get instruction may only fail with an NPE, or the field-get remains as-is.
+        Instruction currentOrNullCheck;
+        if (current.isInstanceGet()) {
+          currentOrNullCheck =
+              replaceInstanceGetByNullCheckIfPossible(current.asInstanceGet(), iterator, context);
+        } else {
+          currentOrNullCheck = current;
+        }
+
+        // Insert the definition of the replacement.
+        replacement.setPosition(currentOrNullCheck.getPosition());
+        if (currentOrNullCheck.getBlock().hasCatchHandlers()) {
           iterator.split(code, blocks).listIterator(code).add(replacement);
         } else {
           iterator.add(replacement);
@@ -353,6 +369,26 @@
     }
   }
 
+  private Instruction replaceInstanceGetByNullCheckIfPossible(
+      InstanceGet instruction, InstructionListIterator iterator, DexType context) {
+    assert !instruction.outValue().hasAnyUsers();
+    if (instruction.instructionMayHaveSideEffects(
+        appView, context, FieldInstruction.Assumption.RECEIVER_NOT_NULL)) {
+      return instruction;
+    }
+    Value receiver = instruction.object();
+    InvokeMethod replacement;
+    if (appView.options().canUseRequireNonNull()) {
+      DexMethod requireNonNullMethod = appView.dexItemFactory().objectsMethods.requireNonNull;
+      replacement = new InvokeStatic(requireNonNullMethod, null, ImmutableList.of(receiver));
+    } else {
+      DexMethod getClassMethod = appView.dexItemFactory().objectMethods.getClass;
+      replacement = new InvokeVirtual(getClassMethod, null, ImmutableList.of(receiver));
+    }
+    iterator.replaceCurrentInstruction(replacement);
+    return replacement;
+  }
+
   /**
    * Replace invoke targets and field accesses with constant values where possible.
    *
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index ea419cc..002abc9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -1595,7 +1595,8 @@
     @Override
     public IRCode buildIR(DexEncodedMethod encodedMethod, AppView<?> appView, Origin origin) {
       OutlineSourceCode source = new OutlineSourceCode(outline, encodedMethod.method);
-      return new IRBuilder(encodedMethod, appView, source, origin).build(encodedMethod);
+      return IRBuilder.create(encodedMethod, appView, source, origin)
+          .build(encodedMethod);
     }
 
     @Override
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 856baa9..e8799da 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,6 +35,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * ServiceLoaderRewriter will attempt to rewrite calls on the form of: ServiceLoader.load(X.class,
@@ -72,6 +73,8 @@
   private ConcurrentHashMap<DexType, DexEncodedMethod> synthesizedServiceLoaders =
       new ConcurrentHashMap<>();
 
+  private AtomicInteger atomicInteger = new AtomicInteger(0);
+
   private final AppView<? extends AppInfoWithLiveness> appView;
 
   public ServiceLoaderRewriter(AppView<? extends AppInfoWithLiveness> appView) {
@@ -216,7 +219,7 @@
             .createMethod(
                 serviceLoaderType,
                 proto,
-                SERVICE_LOADER_METHOD_PREFIX_NAME + synthesizedServiceLoaders.size());
+                SERVICE_LOADER_METHOD_PREFIX_NAME + atomicInteger.incrementAndGet());
     MethodAccessFlags methodAccess =
         MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_STATIC, false);
     DexEncodedMethod encodedMethod =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java
index 8799529..0aabe6e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java
@@ -135,13 +135,12 @@
       }
     }
 
-    assert targetBlockIndexOffset[theSwitch.getFallthroughBlockIndex()] == 0;
-
     iterator.replaceCurrentInstruction(
         new IntSwitch(
             theSwitch.value(),
             newKeys,
             newTargetBlockIndices,
-            theSwitch.getFallthroughBlockIndex()));
+            theSwitch.getFallthroughBlockIndex()
+                - targetBlockIndexOffset[theSwitch.getFallthroughBlockIndex()]));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index b20eba1..291f4d5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -227,7 +227,8 @@
         assert processor.getReceivers().verifyReceiverSetsAreDisjoint();
 
         // Is inlining allowed.
-        InliningIRProvider inliningIRProvider = new InliningIRProvider(appView, method, code);
+        InliningIRProvider inliningIRProvider =
+            new InliningIRProvider(appView, method, code, methodProcessor);
         ClassInlinerCostAnalysis costAnalysis =
             new ClassInlinerCostAnalysis(
                 appView, inliningIRProvider, processor.getReceivers().getDefiniteReceiverAliases());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index f076b41..5612322 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -248,6 +248,10 @@
         }
 
         if (user.isInstancePut()) {
+          if (root.isStaticGet()) {
+            // We can't remove instructions that mutate the singleton instance.
+            return user; // Not eligible.
+          }
           if (!receivers.addIllegalReceiverAlias(user.asInstancePut().value())) {
             return user; // Not eligible.
           }
@@ -747,6 +751,9 @@
       if (encodedParent == null) {
         return null;
       }
+      if (methodProcessor.isProcessedConcurrently(encodedParent)) {
+        return null;
+      }
       if (!encodedParent.isInliningCandidate(
           method,
           Reason.SIMPLE,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
new file mode 100644
index 0000000..0bf0aed
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
@@ -0,0 +1,195 @@
+// 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.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See GenerateEnumUnboxingMethods.java.
+// ***********************************************************************************
+
+package com.android.tools.r8.ir.optimize.enums;
+
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
+import com.android.tools.r8.cf.code.CfArrayLength;
+import com.android.tools.r8.cf.code.CfArrayStore;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfIfCmp;
+import com.android.tools.r8.cf.code.CfIinc;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+
+public final class EnumUnboxingCfMethods {
+
+  public static CfCode EnumUnboxingMethods_compareTo(InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        2,
+        2,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.INT, 0),
+            new CfIf(If.Type.EQ, ValueType.INT, label1),
+            new CfLoad(ValueType.INT, 1),
+            new CfIf(If.Type.NE, ValueType.INT, label2),
+            label1,
+            new CfNew(
+                options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfInvoke(
+                183,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;"),
+                    options.itemFactory.createProto(options.itemFactory.voidType),
+                    options.itemFactory.createString("<init>")),
+                false),
+            new CfThrow(),
+            label2,
+            new CfLoad(ValueType.INT, 0),
+            new CfLoad(ValueType.INT, 1),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
+            new CfReturn(ValueType.INT),
+            label3),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode EnumUnboxingMethods_equals(InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        2,
+        2,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.INT, 0),
+            new CfIf(If.Type.NE, ValueType.INT, label2),
+            label1,
+            new CfNew(
+                options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfInvoke(
+                183,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;"),
+                    options.itemFactory.createProto(options.itemFactory.voidType),
+                    options.itemFactory.createString("<init>")),
+                false),
+            new CfThrow(),
+            label2,
+            new CfLoad(ValueType.INT, 0),
+            new CfLoad(ValueType.INT, 1),
+            new CfIfCmp(If.Type.NE, ValueType.INT, label3),
+            new CfConstNumber(1, ValueType.INT),
+            new CfGoto(label4),
+            label3,
+            new CfConstNumber(0, ValueType.INT),
+            label4,
+            new CfReturn(ValueType.INT),
+            label5),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode EnumUnboxingMethods_ordinal(InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        2,
+        1,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.INT, 0),
+            new CfIf(If.Type.NE, ValueType.INT, label2),
+            label1,
+            new CfNew(
+                options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfInvoke(
+                183,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;"),
+                    options.itemFactory.createProto(options.itemFactory.voidType),
+                    options.itemFactory.createString("<init>")),
+                false),
+            new CfThrow(),
+            label2,
+            new CfLoad(ValueType.INT, 0),
+            new CfConstNumber(1, ValueType.INT),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
+            new CfReturn(ValueType.INT),
+            label3),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode EnumUnboxingMethods_values(InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    CfLabel label6 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        4,
+        3,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.INT, 0),
+            new CfNewArray(options.itemFactory.intArrayType),
+            new CfStore(ValueType.OBJECT, 1),
+            label1,
+            new CfConstNumber(0, ValueType.INT),
+            new CfStore(ValueType.INT, 2),
+            label2,
+            new CfLoad(ValueType.INT, 2),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfArrayLength(),
+            new CfIfCmp(If.Type.GE, ValueType.INT, label5),
+            label3,
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.INT, 2),
+            new CfLoad(ValueType.INT, 2),
+            new CfConstNumber(1, ValueType.INT),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT),
+            new CfArrayStore(MemberType.INT),
+            label4,
+            new CfIinc(2, 1),
+            new CfGoto(label2),
+            label5,
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfReturn(ValueType.OBJECT),
+            label6),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
index c56c562..b9e7652 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
@@ -8,7 +8,7 @@
  * Used to represent that a constructor initializes an instance field on the newly created instance
  * with argument number {@link #argumentIndex} from the constructor's argument list.
  */
-public class InstanceFieldArgumentInitializationInfo extends InstanceFieldInitializationInfo {
+public class InstanceFieldArgumentInitializationInfo implements InstanceFieldInitializationInfo {
 
   private final int argumentIndex;
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java
index 312d7a0..2e9285a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.info.field;
 
+import com.android.tools.r8.ir.analysis.value.SingleValue;
+
 /**
  * Information about the way a constructor initializes an instance field on the newly created
  * instance.
@@ -11,17 +13,33 @@
  * <p>For example, this can be used to represent that a constructor always initializes a particular
  * instance field with a constant, or with an argument from the constructor's argument list.
  */
-public abstract class InstanceFieldInitializationInfo {
+public interface InstanceFieldInitializationInfo {
 
-  public boolean isArgumentInitializationInfo() {
+  default boolean isArgumentInitializationInfo() {
     return false;
   }
 
-  public InstanceFieldArgumentInitializationInfo asArgumentInitializationInfo() {
+  default InstanceFieldArgumentInitializationInfo asArgumentInitializationInfo() {
     return null;
   }
 
-  public boolean isUnknown() {
+  default boolean isTypeInitializationInfo() {
+    return false;
+  }
+
+  default InstanceFieldTypeInitializationInfo asTypeInitializationInfo() {
+    return null;
+  }
+
+  default boolean isSingleValue() {
+    return false;
+  }
+
+  default SingleValue asSingleValue() {
+    return null;
+  }
+
+  default boolean isUnknown() {
     return false;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoFactory.java
index 42209b4..eb7cdda 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoFactory.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.info.field;
 
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import java.util.concurrent.ConcurrentHashMap;
 
 public class InstanceFieldInitializationInfoFactory {
@@ -16,4 +18,9 @@
     return argumentInitializationInfos.computeIfAbsent(
         argumentIndex, InstanceFieldArgumentInitializationInfo::new);
   }
+
+  public InstanceFieldTypeInitializationInfo createTypeInitializationInfo(
+      ClassTypeLatticeElement dynamicLowerBoundType, TypeLatticeElement dynamicUpperBoundType) {
+    return new InstanceFieldTypeInitializationInfo(dynamicLowerBoundType, dynamicUpperBoundType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
new file mode 100644
index 0000000..e6f84ff
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
@@ -0,0 +1,62 @@
+// 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.info.field;
+
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import java.util.Objects;
+
+/**
+ * Used to represent that a constructor initializes an instance field on the newly created instance
+ * with a known dynamic lower- and upper-bound type.
+ */
+public class InstanceFieldTypeInitializationInfo implements InstanceFieldInitializationInfo {
+
+  private final ClassTypeLatticeElement dynamicLowerBoundType;
+  private final TypeLatticeElement dynamicUpperBoundType;
+
+  /** Intentionally package private, use {@link InstanceFieldInitializationInfoFactory} instead. */
+  InstanceFieldTypeInitializationInfo(
+      ClassTypeLatticeElement dynamicLowerBoundType, TypeLatticeElement dynamicUpperBoundType) {
+    this.dynamicLowerBoundType = dynamicLowerBoundType;
+    this.dynamicUpperBoundType = dynamicUpperBoundType;
+  }
+
+  public ClassTypeLatticeElement getDynamicLowerBoundType() {
+    return dynamicLowerBoundType;
+  }
+
+  public TypeLatticeElement getDynamicUpperBoundType() {
+    return dynamicUpperBoundType;
+  }
+
+  @Override
+  public boolean isTypeInitializationInfo() {
+    return true;
+  }
+
+  @Override
+  public InstanceFieldTypeInitializationInfo asTypeInitializationInfo() {
+    return this;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(dynamicLowerBoundType, dynamicUpperBoundType);
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (this == other) {
+      return true;
+    }
+    if (getClass() != other.getClass()) {
+      return false;
+    }
+    InstanceFieldTypeInitializationInfo info = (InstanceFieldTypeInitializationInfo) other;
+    return Objects.equals(dynamicLowerBoundType, info.dynamicLowerBoundType)
+        && Objects.equals(dynamicUpperBoundType, info.dynamicUpperBoundType);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
index 66e882e..a10f045 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
@@ -8,7 +8,7 @@
  * Represents that no information is known about the way a particular constructor initializes an
  * instance field of the newly created instance.
  */
-public class UnknownInstanceFieldInitializationInfo extends InstanceFieldInitializationInfo {
+public class UnknownInstanceFieldInitializationInfo implements InstanceFieldInitializationInfo {
 
   private static final UnknownInstanceFieldInitializationInfo INSTANCE =
       new UnknownInstanceFieldInitializationInfo();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
index b1408dd..3a98637 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.origin.Origin;
 import java.util.IdentityHashMap;
 import java.util.Map;
@@ -19,13 +20,16 @@
   private final AppView<?> appView;
   private final DexEncodedMethod context;
   private final ValueNumberGenerator valueNumberGenerator;
+  private final MethodProcessor methodProcessor;
 
   private final Map<InvokeMethod, IRCode> cache = new IdentityHashMap<>();
 
-  public InliningIRProvider(AppView<?> appView, DexEncodedMethod context, IRCode code) {
+  public InliningIRProvider(
+      AppView<?> appView, DexEncodedMethod context, IRCode code, MethodProcessor methodProcessor) {
     this.appView = appView;
     this.context = context;
     this.valueNumberGenerator = code.valueNumberGenerator;
+    this.methodProcessor = methodProcessor;
   }
 
   public IRCode getInliningIR(InvokeMethod invoke, DexEncodedMethod method) {
@@ -35,7 +39,8 @@
     }
     Position position = Position.getPositionForInlining(appView, invoke, context);
     Origin origin = appView.appInfo().originFor(method.method.holder);
-    return method.buildInliningIR(context, appView, valueNumberGenerator, position, origin);
+    return method.buildInliningIR(
+        context, appView, valueNumberGenerator, position, origin, methodProcessor);
   }
 
   public IRCode getAndCacheInliningIR(InvokeMethod invoke, DexEncodedMethod method) {
@@ -53,4 +58,8 @@
     assert cache.isEmpty();
     return true;
   }
+
+  public boolean shouldApplyCodeRewritings(DexEncodedMethod method) {
+    return methodProcessor.shouldApplyCodeRewritings(method);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index 6512985..ab02906 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
@@ -36,6 +37,7 @@
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
+import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
 import com.android.tools.r8.ir.optimize.lambda.CodeProcessor.Strategy;
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaStructureError;
 import com.android.tools.r8.ir.optimize.lambda.kotlin.KotlinLambdaGroupIdFactory;
@@ -92,7 +94,11 @@
   private abstract static class Mode {
 
     void rewriteCode(
-        DexEncodedMethod method, IRCode code, Inliner inliner, DexEncodedMethod context) {}
+        DexEncodedMethod method,
+        IRCode code,
+        Inliner inliner,
+        DexEncodedMethod context,
+        InliningIRProvider provider) {}
 
     void analyzeCode(DexEncodedMethod method, IRCode code) {}
   }
@@ -119,7 +125,11 @@
 
     @Override
     void rewriteCode(
-        DexEncodedMethod method, IRCode code, Inliner inliner, DexEncodedMethod context) {
+        DexEncodedMethod method,
+        IRCode code,
+        Inliner inliner,
+        DexEncodedMethod context,
+        InliningIRProvider provider) {
       DexProgramClass clazz = appView.definitionFor(method.method.holder).asProgramClass();
       assert clazz != null;
 
@@ -150,7 +160,7 @@
 
       assert invokesToInline.size() > 1;
 
-      inliner.performForcedInlining(method, code, invokesToInline);
+      inliner.performForcedInlining(method, code, invokesToInline, provider);
     }
   }
 
@@ -291,20 +301,27 @@
    *       no more invalid lambda class references.
    * </ol>
    */
-  public final void rewriteCode(DexEncodedMethod method, IRCode code, Inliner inliner) {
+  public final void rewriteCode(
+      DexEncodedMethod method, IRCode code, Inliner inliner, MethodProcessor methodProcessor) {
     if (mode != null) {
-      mode.rewriteCode(method, code, inliner, null);
+      mode.rewriteCode(
+          method,
+          code,
+          inliner,
+          null,
+          new InliningIRProvider(appView, method, code, methodProcessor));
     }
   }
 
   /**
-   * Similar to {@link #rewriteCode(DexEncodedMethod, IRCode, Inliner)}, but for rewriting code for
-   * inlining. The {@param context} is the caller that {@param method} is being inlined into.
+   * Similar to {@link #rewriteCode(DexEncodedMethod, IRCode, Inliner, MethodProcessor)}, but for
+   * rewriting code for inlining. The {@param context} is the caller that {@param method} is being
+   * inlined into.
    */
   public final void rewriteCodeForInlining(
-      DexEncodedMethod method, IRCode code, DexEncodedMethod context) {
+      DexEncodedMethod method, IRCode code, DexEncodedMethod context, InliningIRProvider provider) {
     if (mode != null) {
-      mode.rewriteCode(method, code, null, context);
+      mode.rewriteCode(method, code, null, context, provider);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
index f9da7c6..331f14d 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.SourceCode;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
@@ -35,14 +36,11 @@
 
   @Override
   public final IRCode buildIR(DexEncodedMethod encodedMethod, AppView<?> appView, Origin origin) {
-    IRBuilder builder =
-        new IRBuilder(
-            encodedMethod,
-            appView,
-            getSourceCodeProvider().get(null),
-            origin,
-            new ValueNumberGenerator());
-    return builder.build(encodedMethod);
+    return IRBuilder.create(
+        encodedMethod,
+        appView,
+        getSourceCodeProvider().get(null),
+        origin).build(encodedMethod);
   }
 
   @Override
@@ -52,15 +50,15 @@
       AppView<?> appView,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin) {
-    IRBuilder builder =
-        new IRBuilder(
-            encodedMethod,
-            appView,
-            getSourceCodeProvider().get(callerPosition),
-            origin,
-            valueNumberGenerator);
-    return builder.build(context);
+      Origin origin,
+      MethodProcessor methodProcessor) {
+    return IRBuilder.createForInlining(
+        encodedMethod,
+        appView,
+        getSourceCodeProvider().get(callerPosition),
+        origin,
+        methodProcessor,
+        valueNumberGenerator).build(context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 96cd284..be788ab 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -69,9 +69,7 @@
     this.packageObfuscationMode = options.getProguardConfiguration().getPackageObfuscationMode();
     this.isAccessModificationAllowed =
         options.getProguardConfiguration().isAccessModificationAllowed();
-    this.keepInnerClassStructure =
-        options.getProguardConfiguration().getKeepAttributes().signature
-            || options.getProguardConfiguration().getKeepAttributes().innerClasses;
+    this.keepInnerClassStructure = options.keepInnerClassStructure();
 
     // Initialize top-level naming state.
     topLevelState = new Namespace(
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index b95f778..734c0b0 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -84,7 +84,8 @@
     }
     int index = innerTypeMapped.lastIndexOf(separator);
     if (index < 0) {
-      assert options.getProguardConfiguration().hasApplyMappingFile()
+      assert !options.keepInnerClassStructure()
+              || options.getProguardConfiguration().hasApplyMappingFile()
           : innerType + " -> " + innerTypeMapped;
       String descriptor = lookupDescriptor(innerType).toString();
       return options.itemFactory.createString(
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index b4ddb8e..67d2fa9 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -266,7 +266,7 @@
         int innerClassPos = enclosingRenamedBinaryName.length() + 1;
         if (innerClassPos < fullRenamedBinaryName.length()) {
           renamedSignature.append(fullRenamedBinaryName.substring(innerClassPos));
-        } else {
+        } else if (appView.options().keepInnerClassStructure()) {
           reporter.warning(
               new StringDiagnostic(
                   "Should have retained InnerClasses attribute of " + type + ".",
@@ -275,7 +275,7 @@
         }
       } else {
         // Did not find the class - keep the inner class name as is.
-        // TODO(110085899): Warn about missing classes in signatures?
+        // TODO(b/110085899): Warn about missing classes in signatures?
         renamedSignature.append(name);
       }
       return type;
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 509a832..fd8742d 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -834,6 +834,21 @@
     return false;
   }
 
+  public boolean isInstanceFieldWrittenOnlyInInstanceInitializers(DexEncodedField field) {
+    assert checkIfObsolete();
+    assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
+    if (isPinned(field.field)) {
+      return false;
+    }
+    FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.field);
+    if (fieldAccessInfo == null || !fieldAccessInfo.isWritten()) {
+      return false;
+    }
+    DexType holder = field.field.holder;
+    return fieldAccessInfo.isWrittenOnlyInMethodSatisfying(
+        method -> method.isInstanceInitializer() && method.method.holder == holder);
+  }
+
   public boolean isStaticFieldWrittenOnlyInEnclosingStaticInitializer(DexEncodedField field) {
     assert checkIfObsolete();
     assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
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 a7bb64b..4dd45b3 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -54,6 +55,7 @@
 import com.android.tools.r8.graph.FieldAccessInfoImpl;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.LookupResult;
+import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
 import com.android.tools.r8.graph.PresortedComparable;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -95,6 +97,7 @@
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.WorkList;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
@@ -290,8 +293,11 @@
   private final Set<DexEncodedMethod> pendingReflectiveUses = Sets.newLinkedHashSet();
 
   /** A cache for DexMethod that have been marked reachable. */
+  private final Map<DexProgramClass, Set<DexEncodedMethod>> reachableVirtualResolutions =
+      new IdentityHashMap<>();
+
   private final Map<DexMethod, MarkedResolutionTarget> virtualTargetsMarkedAsReachable =
-      Maps.newIdentityHashMap();
+      new IdentityHashMap<>();
 
   /**
    * A set of references we have reported missing to dedupe warnings.
@@ -415,22 +421,24 @@
     return getProgramClassOrNull(type) != null;
   }
 
-  private DexProgramClass getProgramClassOrNull(DexType type) {
+  private DexClass definitionFor(DexType type) {
     DexClass clazz = appView.definitionFor(type);
-    if (clazz != null) {
-      if (clazz.isProgramClass()) {
-        return clazz.asProgramClass();
-      }
-      if (liveNonProgramTypes.add(clazz) && clazz.isLibraryClass()) {
-        // TODO(b/149201735): This likely needs to apply to classpath too.
-        ensureMethodsContinueToWidenAccess(clazz);
-        // TODO(b/149201158): This should apply to classpath too (likely even hard fail).
-        warnIfLibraryTypeInheritsFromProgramType(clazz.asLibraryClass());
-      }
-    } else {
+    if (clazz == null) {
       reportMissingClass(type);
+      return null;
     }
-    return null;
+    if (liveNonProgramTypes.add(clazz) && clazz.isLibraryClass()) {
+      // TODO(b/149201735): This likely needs to apply to classpath too.
+      ensureMethodsContinueToWidenAccess(clazz);
+      // Only libraries must not derive program. Classpath classes can, assuming correct keep rules.
+      warnIfLibraryTypeInheritsFromProgramType(clazz.asLibraryClass());
+    }
+    return clazz;
+  }
+
+  private DexProgramClass getProgramClassOrNull(DexType type) {
+    DexClass clazz = definitionFor(type);
+    return clazz != null && clazz.isProgramClass() ? clazz.asProgramClass() : null;
   }
 
   private void warnIfLibraryTypeInheritsFromProgramType(DexLibraryClass clazz) {
@@ -725,6 +733,7 @@
     // we have to look at the interface chain and mark default methods as reachable, not taking
     // the shadowing of other interface chains into account.
     // See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3
+    // TODO(b/148271337): Support lookupVirtualDispatchTarget(CallSite) and replace this.
     ScopedDexMethodSet seen = new ScopedDexMethodSet();
     for (DexType iface : descriptor.interfaces) {
       DexProgramClass ifaceClazz = getProgramClassOrNull(iface);
@@ -1333,6 +1342,8 @@
       }
     }
 
+    // TODO(b/149729626): Consider marking types with a dependent instance constructor as being
+    //  instantiated.
     rootSet.forEachDependentStaticMember(holder, appView, this::enqueueDependentItem);
     compatEnqueueHolderIfDependentNonStaticMember(
         holder, rootSet.getDependentKeepClassCompatRule(holder.getType()));
@@ -1659,7 +1670,7 @@
     markDirectAndIndirectClassInitializersAsLive(clazz);
     // For all methods of the class, if we have seen a call, mark the method live.
     // We only do this for virtual calls, as the other ones will be done directly.
-    transitionMethodsForInstantiatedClass(clazz);
+    transitionMethodsForInstantiatedClass(clazz, keepReason);
     // For all instance fields visible from the class, mark them live if we have seen a read.
     transitionFieldsForInstantiatedClass(clazz);
     // Add all dependent instance members to the workqueue.
@@ -1685,95 +1696,119 @@
   }
 
   /**
-   * Marks all methods live that can be reached by calls previously seen.
+   * Marks all methods live that are overrides of reachable methods for a given class.
    *
-   * <p>This should only be invoked if the given type newly becomes instantiated. In essence, this
-   * method replays all the invokes we have seen so far that could apply to this type and marks the
-   * corresponding methods live.
-   *
-   * <p>Only methods that are visible in this type are considered. That is, only those methods that
-   * are either defined directly on this type or that are defined on a supertype but are not
-   * shadowed by another inherited method. Furthermore, default methods from implemented interfaces
-   * that are not otherwise shadowed are considered, too.
-   *
-   * <p>Finally all methods on library types that resolve starting at the instantiated type are
-   * marked live.
+   * <p>Only reachable methods in the hierarchy of the given class and above are considered, and
+   * only the lowest such reachable target (ie, mirroring resolution). All library and classpath
+   * methods are considered reachable.
    */
-  private void transitionMethodsForInstantiatedClass(DexProgramClass instantiatedClass) {
+  private void transitionMethodsForInstantiatedClass(
+      DexProgramClass instantiatedClass, KeepReason instantiationReason) {
+    assert !instantiatedClass.isAnnotation();
+    assert !instantiatedClass.isInterface();
     ScopedDexMethodSet seen = new ScopedDexMethodSet();
-    Set<DexType> interfaces = Sets.newIdentityHashSet();
-    DexProgramClass current = instantiatedClass;
-    do {
-      // We only have to look at virtual methods here, as only those can actually be executed at
-      // runtime. Illegal dispatch situations and the corresponding exceptions are already handled
-      // by the reachability logic.
-      transitionReachableVirtualMethods(current, seen);
-      Collections.addAll(interfaces, current.interfaces.values);
-      current = getProgramClassOrNull(current.superType);
-    } while (current != null && !objectAllocationInfoCollection.isInstantiatedDirectly(current));
-
-    // The set now contains all virtual methods on the type and its supertype that are reachable.
-    // In a second step, we now look at interfaces. We have to do this in this order due to JVM
-    // semantics for default methods. A default method is only reachable if it is not overridden in
-    // any superclass. Also, it is not defined which default method is chosen if multiple
-    // interfaces define the same default method. Hence, for every interface (direct or indirect),
-    // we have to look at the interface chain and mark default methods as reachable, not taking
-    // the shadowing of other interface chains into account.
-    // See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3
-    for (DexType iface : interfaces) {
-      DexClass clazz = appView.definitionFor(iface);
-      if (clazz == null) {
-        reportMissingClass(iface);
-        // TODO(herhut): In essence, our subtyping chain is broken here. Handle that case better.
-        break;
-      }
-      transitionDefaultMethodsForInstantiatedClass(iface, seen);
-    }
-
-    // When tracing the main-dex content, library roots must be specified, thus there are no
-    // implicit edges from library methods.
-    if (getMode().isTracingMainDex()) {
-      return;
-    }
-
-    // When a type becomes live, all library methods on that type become live too.
-    // This is done by searching the library supertypes and then resolving each method defined by
-    // such a library type from the point of the instantiated type. If the resolved targets are in
-    // the program, i.e., the instantiated type has a method overidding a library method, then the
-    // program method is live.
-    Deque<DexClass> librarySearchItems = new ArrayDeque<>();
-    librarySearchItems.add(instantiatedClass);
-    while (!librarySearchItems.isEmpty()) {
-      DexClass clazz = librarySearchItems.pop();
-      if (clazz.isNotProgramClass()) {
-        markLibraryAndClasspathMethodOverridesAsLive(clazz, instantiatedClass);
-      }
-      if (clazz.superType != null) {
-        DexClass superClass = appView.definitionFor(clazz.superType);
-        if (superClass != null) {
-          librarySearchItems.add(superClass);
+    WorkList<DexType> worklist = WorkList.newIdentityWorkList();
+    // First we lookup and mark all targets on the instantiated class for each reachable method in
+    // the super chain (inclusive).
+    {
+      DexClass clazz = instantiatedClass;
+      while (clazz != null) {
+        if (clazz.isProgramClass()) {
+          markProgramMethodOverridesAsLive(
+              instantiatedClass, clazz.asProgramClass(), seen, instantiationReason);
+        } else {
+          markLibraryAndClasspathMethodOverridesAsLive(instantiatedClass, clazz);
         }
+        worklist.addIfNotSeen(Arrays.asList(clazz.interfaces.values));
+        clazz = clazz.superType != null ? definitionFor(clazz.superType) : null;
       }
-      for (DexType iface : clazz.interfaces.values) {
-        DexClass ifaceClass = appView.definitionFor(iface);
-        if (ifaceClass != null) {
-          librarySearchItems.add(ifaceClass);
-        }
+    }
+    // The targets for methods on the type and its supertype that are reachable are now marked.
+    // In a second step, we look at interfaces. We order the search this way such that a
+    // method reachable on a class takes precedence when reporting edges. That order mirrors JVM
+    // resolution/dispatch.
+    while (worklist.hasNext()) {
+      DexType type = worklist.next();
+      DexClass iface = definitionFor(type);
+      if (iface == null) {
+        continue;
+      }
+      assert iface.superType == appInfo.dexItemFactory().objectType;
+      if (iface.isNotProgramClass()) {
+        markLibraryAndClasspathMethodOverridesAsLive(instantiatedClass, iface);
+      } else {
+        markProgramMethodOverridesAsLive(
+            instantiatedClass, iface.asProgramClass(), seen, instantiationReason);
+      }
+      worklist.addIfNotSeen(Arrays.asList(iface.interfaces.values));
+    }
+  }
+
+  private Set<DexEncodedMethod> getReachableVirtualResolutions(DexProgramClass clazz) {
+    return reachableVirtualResolutions.getOrDefault(clazz, Collections.emptySet());
+  }
+
+  private void markProgramMethodOverridesAsLive(
+      DexProgramClass instantiatedClass,
+      DexProgramClass superClass,
+      ScopedDexMethodSet seenMethods,
+      KeepReason instantiationReason) {
+    for (DexEncodedMethod resolution : getReachableVirtualResolutions(superClass)) {
+      if (seenMethods.addMethod(resolution)) {
+        markLiveOverrides(instantiatedClass, superClass, resolution, instantiationReason);
       }
     }
   }
 
+  private void markLiveOverrides(
+      DexProgramClass instantiatedClass,
+      DexProgramClass reachableHolder,
+      DexEncodedMethod reachableMethod,
+      KeepReason instantiationReason) {
+    assert reachableHolder.type == reachableMethod.method.holder;
+    // The validity of the reachable method is checked at the point it becomes "reachable" and is
+    // resolved. If the method is private, then the dispatch is not "virtual" and the method is
+    // simply marked live on its holder.
+    if (reachableMethod.isPrivateMethod()) {
+      markVirtualMethodAsLive(
+          reachableHolder,
+          reachableMethod,
+          graphReporter.reportReachableMethodAsLive(
+              reachableMethod.method, new ProgramMethod(reachableHolder, reachableMethod)));
+      return;
+    }
+    // Otherwise, we set the initial holder type to be the holder of the reachable method, which
+    // ensures that access will be generally valid.
+    SingleResolutionResult result =
+        new SingleResolutionResult(reachableHolder, reachableHolder, reachableMethod);
+    DexClassAndMethod lookup = result.lookupVirtualDispatchTarget(instantiatedClass, appView);
+    if (lookup == null || !lookup.isProgramMethod() || lookup.getMethod().isAbstract()) {
+      return;
+    }
+    ProgramMethod method = lookup.asProgramMethod();
+    markTypeAsLive(method.getHolder().type, instantiationReason);
+    markVirtualMethodAsLive(
+        method.getHolder(),
+        method.getMethod(),
+        graphReporter.reportReachableMethodAsLive(reachableMethod.method, method));
+  }
+
   private void markLibraryAndClasspathMethodOverridesAsLive(
-      DexClass libraryClass, DexProgramClass instantiatedClass) {
+      DexProgramClass instantiatedClass, DexClass libraryClass) {
     assert libraryClass.isNotProgramClass();
     assert !instantiatedClass.isInterface() || instantiatedClass.isAnnotation();
+    if (mode.isTracingMainDex()) {
+      // Library roots must be specified for tracing of library methods. For classpath the expected
+      // use case is that the classes will be classloaded, thus they should have no bearing on the
+      // content of the main dex file.
+      return;
+    }
     for (DexEncodedMethod method : libraryClass.virtualMethods()) {
-      // Note: it may be worthwhile to add a resolution cache here. If so, it must still ensure
-      // that all library override edges are reported to the kept-graph consumer.
-      ResolutionResult firstResolution =
-          appView.appInfo().resolveMethod(instantiatedClass, method.method);
-      markResolutionAsLive(libraryClass, firstResolution);
-      markOverridesAsLibraryMethodOverrides(method.method, instantiatedClass);
+      assert !method.isPrivateMethod();
+      // Note: It would be reasonable to not process methods already seen during the marking of
+      // program usages, but that would cause the methods to not be marked as library overrides.
+      markLibraryOrClasspathOverrideLive(
+          instantiatedClass, libraryClass, appInfo.resolveMethod(libraryClass, method.method));
 
       // Due to API conversion, some overrides can be hidden since they will be rewritten. See
       // class comment of DesugaredLibraryAPIConverter and vivifiedType logic.
@@ -1785,47 +1820,49 @@
             DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
                 method.method, method.method.holder, appView);
         assert methodToResolve != method.method;
-        ResolutionResult secondResolution =
-            appView.appInfo().resolveMethod(instantiatedClass, methodToResolve);
-        markResolutionAsLive(libraryClass, secondResolution);
-        markOverridesAsLibraryMethodOverrides(methodToResolve, instantiatedClass);
+        markLibraryOrClasspathOverrideLive(
+            instantiatedClass,
+            libraryClass,
+            appInfo.resolveMethod(instantiatedClass, methodToResolve));
       }
-
     }
   }
 
-  private void markResolutionAsLive(DexClass libraryClass, ResolutionResult resolution) {
-    if (resolution.isVirtualTarget()) {
-      DexEncodedMethod target = resolution.getSingleTarget();
-      DexProgramClass targetHolder = getProgramClassOrNull(target.method.holder);
-      if (targetHolder != null
-          && shouldMarkLibraryMethodOverrideAsReachable(targetHolder, target)) {
-        markVirtualMethodAsLive(
-            targetHolder, target, KeepReason.isLibraryMethod(targetHolder, libraryClass.type));
-      }
+  private void markLibraryOrClasspathOverrideLive(
+      DexProgramClass instantiatedClass,
+      DexClass libraryOrClasspathClass,
+      ResolutionResult resolution) {
+    DexClassAndMethod lookup = resolution.lookupVirtualDispatchTarget(instantiatedClass, appView);
+    if (lookup == null || !lookup.isProgramMethod() || lookup.getMethod().isAbstract()) {
+      return;
     }
+    DexProgramClass clazz = lookup.asProgramMethod().getHolder();
+    DexEncodedMethod target = lookup.getMethod();
+    if (shouldMarkLibraryMethodOverrideAsReachable(clazz, target)) {
+      markVirtualMethodAsLive(
+          clazz, target, KeepReason.isLibraryMethod(clazz, libraryOrClasspathClass.type));
+    }
+    markOverridesAsLibraryMethodOverrides(instantiatedClass, target.method);
   }
 
   private void markOverridesAsLibraryMethodOverrides(
-      DexMethod libraryMethod, DexProgramClass instantiatedClass) {
-    Set<DexProgramClass> visited = SetUtils.newIdentityHashSet(instantiatedClass);
-    Deque<DexProgramClass> worklist = DequeUtils.newArrayDeque(instantiatedClass);
-    while (!worklist.isEmpty()) {
-      DexProgramClass clazz = worklist.removeFirst();
-      assert visited.contains(clazz);
-      DexEncodedMethod libraryMethodOverride = clazz.lookupVirtualMethod(libraryMethod);
-      if (libraryMethodOverride != null) {
-        if (libraryMethodOverride.isLibraryMethodOverride().isTrue()) {
+      DexProgramClass instantiatedClass, DexMethod libraryMethodOverride) {
+    WorkList<DexType> worklist = WorkList.newIdentityWorkList();
+    worklist.addIfNotSeen(instantiatedClass.type);
+    while (worklist.hasNext()) {
+      DexType type = worklist.next();
+      DexProgramClass clazz = getProgramClassOrNull(type);
+      if (clazz == null) {
+        continue;
+      }
+      DexEncodedMethod override = clazz.lookupVirtualMethod(libraryMethodOverride);
+      if (override != null) {
+        if (override.isLibraryMethodOverride().isTrue()) {
           continue;
         }
-        libraryMethodOverride.setLibraryMethodOverride(OptionalBool.TRUE);
+        override.setLibraryMethodOverride(OptionalBool.TRUE);
       }
-      for (DexType superType : clazz.allImmediateSupertypes()) {
-        DexProgramClass superClass = getProgramClassOrNull(superType);
-        if (superClass != null && visited.add(superClass)) {
-          worklist.add(superClass);
-        }
-      }
+      clazz.forEachImmediateSupertype(worklist::addIfNotSeen);
     }
   }
 
@@ -2171,6 +2208,10 @@
     // each possible target edge below.
     assert resolution.holder.isProgramClass();
 
+    reachableVirtualResolutions
+        .computeIfAbsent(resolution.holder.asProgramClass(), k -> Sets.newIdentityHashSet())
+        .add(resolution.method);
+
     assert interfaceInvoke == holder.isInterface();
     DexProgramClass context = contextOrNull == null ? null : contextOrNull.getHolder();
     LookupResult lookupResult =
@@ -2180,11 +2221,12 @@
     if (!lookupResult.isLookupResultSuccess()) {
       return;
     }
-    for (DexEncodedMethod encodedPossibleTarget :
-        lookupResult.asLookupResultSuccess().getMethodTargets()) {
+    LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
+    for (DexEncodedMethod encodedPossibleTarget : lookupResultSuccess.getMethodTargets()) {
       if (encodedPossibleTarget.isAbstract()) {
         continue;
       }
+      // TODO(b/139464956): Replace this downwards search once targets are found for live types.
       markPossibleTargetsAsReachable(resolution, encodedPossibleTarget);
     }
   }
@@ -2196,20 +2238,15 @@
     assert !encodedPossibleTarget.isAbstract();
     DexMethod possibleTarget = encodedPossibleTarget.method;
     DexProgramClass clazz = getProgramClassOrNull(possibleTarget.holder);
-    if (clazz == null) {
-      return;
-    }
+    // Add the method to the reachable set to ensure targets to lambdas are still identified.
     ReachableVirtualMethodsSet reachable =
         reachableVirtualMethods.computeIfAbsent(clazz, ignore -> new ReachableVirtualMethodsSet());
     if (!reachable.add(encodedPossibleTarget, reason)) {
       return;
     }
 
-    // If the holder type is instantiated, the method is live. Otherwise check whether we find
-    // a subtype that does not shadow this methods but is instantiated.
-    // Note that library classes are always considered instantiated, as we do not know where
-    // they are instantiated.
-    if (!isInstantiatedOrHasInstantiatedSubtype(clazz)) {
+    // If the holder type is uninstantiated (directly or indirectly) the method is not live yet.
+    if (clazz == null || !isInstantiatedOrHasInstantiatedSubtype(clazz)) {
       return;
     }
 
@@ -2218,7 +2255,8 @@
       markVirtualMethodAsLive(
           clazz,
           encodedPossibleTarget,
-          graphReporter.reportReachableMethodAsLive(encodedPossibleTarget, reason));
+          graphReporter.reportReachableMethodAsLive(
+              reason.method.method, new ProgramMethod(clazz, encodedPossibleTarget)));
     } else {
       Deque<DexType> worklist =
           new ArrayDeque<>(appInfo.allImmediateSubtypes(possibleTarget.holder));
@@ -2400,7 +2438,11 @@
     finalizeLibraryMethodOverrideInformation();
     analyses.forEach(EnqueuerAnalysis::done);
     assert verifyKeptGraph();
-    return createAppInfo(appInfo);
+    AppInfoWithLiveness appInfoWithLiveness = createAppInfo(appInfo);
+    if (options.testing.enqueuerInspector != null) {
+      options.testing.enqueuerInspector.accept(appInfoWithLiveness, mode);
+    }
+    return appInfoWithLiveness;
   }
 
   private void finalizeLibraryMethodOverrideInformation() {
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
index c172b52..37be4b3 100644
--- a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -26,6 +26,7 @@
 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.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
@@ -248,6 +249,17 @@
   }
 
   public KeepReasonWitness reportReachableMethodAsLive(
+      DexMethod overriddenMethod, ProgramMethod derivedMethod) {
+    if (keptGraphConsumer != null && overriddenMethod != derivedMethod.getMethod().method) {
+      return reportEdge(
+          getMethodGraphNode(overriddenMethod),
+          getMethodGraphNode(derivedMethod.getMethod().method),
+          EdgeKind.OverridingMethod);
+    }
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  public KeepReasonWitness reportReachableMethodAsLive(
       DexEncodedMethod encodedMethod, MarkedResolutionTarget reason) {
     if (keptGraphConsumer != null) {
       return reportEdge(
diff --git a/src/main/java/com/android/tools/r8/utils/IROrdering.java b/src/main/java/com/android/tools/r8/utils/IROrdering.java
index 4b4ded3..db97cfa 100644
--- a/src/main/java/com/android/tools/r8/utils/IROrdering.java
+++ b/src/main/java/com/android/tools/r8/utils/IROrdering.java
@@ -8,7 +8,9 @@
 import com.google.common.collect.Lists;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Set;
 
 public interface IROrdering {
 
@@ -16,6 +18,8 @@
 
   Collection<DexEncodedMethod> order(Collection<DexEncodedMethod> methods);
 
+  Set<DexEncodedMethod> order(Set<DexEncodedMethod> methods);
+
   class IdentityIROrdering implements IROrdering {
 
     private static final IdentityIROrdering INSTANCE = new IdentityIROrdering();
@@ -35,6 +39,11 @@
     public Collection<DexEncodedMethod> order(Collection<DexEncodedMethod> methods) {
       return methods;
     }
+
+    @Override
+    public Set<DexEncodedMethod> order(Set<DexEncodedMethod> methods) {
+      return methods;
+    }
   }
 
   class NondeterministicIROrdering implements IROrdering {
@@ -58,5 +67,10 @@
     public List<DexEncodedMethod> order(Collection<DexEncodedMethod> methods) {
       return order((Iterable<DexEncodedMethod>) methods);
     }
+
+    @Override
+    public Set<DexEncodedMethod> order(Set<DexEncodedMethod> methods) {
+      return new LinkedHashSet<>(order((Iterable<DexEncodedMethod>) methods));
+    }
   }
 }
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 c6330b1..232b54a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -37,6 +37,8 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.utils.IROrdering.IdentityIROrdering;
@@ -61,6 +63,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.function.BiConsumer;
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 import org.objectweb.asm.Opcodes;
@@ -311,6 +314,9 @@
     if (Version.isDevelopmentVersion()) {
       marker.setSha1(VersionProperties.INSTANCE.getSha());
     }
+    if (tool == Tool.R8) {
+      marker.setR8Mode(forceProguardCompatibility ? "compatibility" : "full");
+    }
     return marker;
   }
 
@@ -450,6 +456,11 @@
     return enableMinification;
   }
 
+  public boolean keepInnerClassStructure() {
+    return getProguardConfiguration().getKeepAttributes().signature
+        || getProguardConfiguration().getKeepAttributes().innerClasses;
+  }
+
   public boolean printCfg = false;
   public String printCfgFile;
   public boolean ignoreMissingClasses = false;
@@ -989,6 +1000,8 @@
             ? NondeterministicIROrdering.getInstance()
             : IdentityIROrdering.getInstance();
 
+    public BiConsumer<AppInfoWithLiveness, Enqueuer.Mode> enqueuerInspector = null;
+
     public Consumer<Deque<Collection<DexEncodedMethod>>> waveModifier = waves -> {};
 
     /**
@@ -1094,6 +1107,15 @@
     enablePropagationOfConstantsAtCallSites = true;
   }
 
+  public boolean isCallSiteOptimizationEnabled() {
+    return enablePropagationOfConstantsAtCallSites || enablePropagationOfDynamicTypesAtCallSites;
+  }
+
+  public void disableCallSiteOptimization() {
+    enablePropagationOfConstantsAtCallSites = false;
+    enablePropagationOfDynamicTypesAtCallSites = false;
+  }
+
   private boolean hasMinApi(AndroidApiLevel level) {
     assert isGeneratingDex();
     return minApiLevel >= level.getLevel();
@@ -1139,15 +1161,6 @@
     return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.N);
   }
 
-  public boolean isCallSiteOptimizationEnabled() {
-    return enablePropagationOfConstantsAtCallSites || enablePropagationOfDynamicTypesAtCallSites;
-  }
-
-  public void disableCallSiteOptimization() {
-    enablePropagationOfConstantsAtCallSites = false;
-    enablePropagationOfDynamicTypesAtCallSites = false;
-  }
-
   public boolean canUseDexPcAsDebugInformation() {
     // TODO(b/37830524): Enable for min-api 26 (OREO) and above.
     return enablePcDebugInfoOutput;
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 68f1749..6bce9c1 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexDebugEvent;
 import com.android.tools.r8.graph.DexDebugEvent.AdvancePC;
@@ -35,7 +36,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser;
 import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.Result;
@@ -309,7 +309,7 @@
           // they may be bridges for interface methods with covariant return types.
           sortMethods(methods);
           // TODO(b/149360203): Reenable assert.
-          // assert verifyMethodsAreKeptDirectlyOrIndirectly(appView, methods);
+          assert true || verifyMethodsAreKeptDirectlyOrIndirectly(appView, methods);
         }
 
         boolean identityMapping =
@@ -457,15 +457,14 @@
       }
       // We use the same name for interface names even if it has different types.
       DexProgramClass clazz = appView.definitionForProgramType(method.method.holder);
-      ResolutionResult resolutionResult =
-          appView.appInfo().resolveMaximallySpecificMethods(clazz, method.method);
-      if (resolutionResult.isFailedResolution()) {
+      DexClassAndMethod lookupResult =
+          appView.appInfo().lookupMaximallySpecificMethod(clazz, method.method);
+      if (lookupResult == null) {
         // We cannot rename methods we cannot look up.
         continue;
       }
       String errorString = method.method.qualifiedName() + " is not kept but is overloaded";
-      assert resolutionResult.isSingleResolution() : errorString;
-      assert resolutionResult.asSingleResolution().getResolvedHolder().isInterface() : errorString;
+      assert lookupResult.getHolder().isInterface() : errorString;
       assert originalName == null || originalName.equals(method.method.name) : errorString;
       originalName = method.method.name;
     }
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
index b631262..835c528 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -15,7 +15,15 @@
   private final Deque<T> workingList = new ArrayDeque<>();
   private final Set<T> seen;
 
-  public WorkList(EqualityTest equalityTest) {
+  public static <T> WorkList<T> newIdentityWorkList() {
+    return new WorkList<T>(EqualityTest.IDENTITY);
+  }
+
+  public WorkList() {
+    this(EqualityTest.HASH);
+  }
+
+  private WorkList(EqualityTest equalityTest) {
     if (equalityTest == EqualityTest.HASH) {
       seen = new HashSet<>();
     } else {
diff --git a/src/test/java/com/android/tools/r8/NeverInline.java b/src/test/java/com/android/tools/r8/NeverInline.java
index 082d446..ca0c2a4 100644
--- a/src/test/java/com/android/tools/r8/NeverInline.java
+++ b/src/test/java/com/android/tools/r8/NeverInline.java
@@ -6,5 +6,5 @@
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Target;
 
-@Target({ElementType.METHOD})
+@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
 public @interface NeverInline {}
diff --git a/src/test/java/com/android/tools/r8/R8ModeMarkerTest.java b/src/test/java/com/android/tools/r8/R8ModeMarkerTest.java
new file mode 100644
index 0000000..c830c8a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8ModeMarkerTest.java
@@ -0,0 +1,126 @@
+// 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 static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.dex.Marker;
+import java.util.Collection;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class R8ModeMarkerTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public R8ModeMarkerTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  interface GetMarker {
+    Marker getMarker();
+  }
+
+  static class ExtractDexMarkerConsumer extends DexIndexedConsumer.ForwardingConsumer
+      implements GetMarker {
+    private Marker marker;
+
+    ExtractDexMarkerConsumer() {
+      super(null);
+    }
+
+    @Override
+    public void accept(
+        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+      try {
+        Collection<Marker> markers =
+            ExtractMarker.extractMarkerFromDexProgramData(data.copyByteData());
+        assertEquals(1, markers.size());
+        marker = markers.iterator().next();
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    @Override
+    public Marker getMarker() {
+      return marker;
+    }
+  }
+
+  static class ExtractClassFileMarkerConsumer extends ClassFileConsumer.ForwardingConsumer
+      implements GetMarker {
+    private Marker marker;
+
+    ExtractClassFileMarkerConsumer() {
+      super(null);
+    }
+
+    @Override
+    public void accept(ByteDataView data, String descriptors, DiagnosticsHandler handler) {
+      try {
+        Collection<Marker> markers =
+            ExtractMarker.extractMarkerFromClassProgramData(data.copyByteData());
+        assertEquals(1, markers.size());
+        marker = markers.iterator().next();
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    @Override
+    public Marker getMarker() {
+      return marker;
+    }
+  }
+
+  @Test
+  public void testFullMode() throws Exception {
+    ProgramConsumer consumer =
+        parameters.getBackend() == Backend.DEX
+            ? new ExtractDexMarkerConsumer()
+            : new ExtractClassFileMarkerConsumer();
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .setProgramConsumer(consumer)
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world");
+    assertEquals("full", ((GetMarker) consumer).getMarker().getR8Mode());
+  }
+
+  @Test
+  public void testCompatMode() throws Exception {
+    ProgramConsumer consumer =
+        parameters.getBackend() == Backend.DEX
+            ? new ExtractDexMarkerConsumer()
+            : new ExtractClassFileMarkerConsumer();
+    testForR8Compat(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .setProgramConsumer(consumer)
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world");
+    assertEquals("compatibility", ((GetMarker) consumer).getMarker().getR8Mode());
+  }
+
+  static class TestClass {
+    public static void main(String[] args) {
+      System.out.println("Hello, world");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index d3bc8da..5cc8194 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -447,7 +447,7 @@
       PreloadedClassFileProvider.Builder libraryBuilder = PreloadedClassFileProvider.builder();
       for (Class<?> clazz : libraryClasses) {
         Path file = ToolHelper.getClassFileForTestClass(clazz);
-        libraryBuilder.addResource(DescriptorUtils.javaTypeToDescriptor(clazz.getCanonicalName()),
+        libraryBuilder.addResource(DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName()),
             Files.readAllBytes(file));
       }
       builder.addLibraryResourceProvider(libraryBuilder.build());
@@ -1432,4 +1432,16 @@
       throws IOException, CompilationFailedException {
     return buildOnDexRuntime(parameters, Arrays.asList(paths));
   }
+
+  public static String binaryName(Class<?> clazz) {
+    return DescriptorUtils.getBinaryNameFromJavaType(typeName(clazz));
+  }
+
+  public static String descriptor(Class<?> clazz) {
+    return DescriptorUtils.javaTypeToDescriptor(typeName(clazz));
+  }
+
+  public static String typeName(Class<?> clazz) {
+    return clazz.getTypeName();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 13afb51..fab7d11 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -206,7 +206,11 @@
   }
 
   public T addKeepAttributeLineNumberTable() {
-    return addKeepRules("-keepattributes LineNumberTable");
+    return addKeepAttributes("LineNumberTable");
+  }
+
+  public T addKeepRuntimeVisibleAnnotations() {
+    return addKeepAttributes("RuntimeVisibleAnnotations");
   }
 
   public T addKeepAllAttributes() {
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
new file mode 100644
index 0000000..862bb79
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
@@ -0,0 +1,150 @@
+// 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.cfmethodgeneration;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.cf.CfCodePrinter;
+import com.android.tools.r8.graph.ClassKind;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.JarApplicationReader;
+import com.android.tools.r8.graph.JarClassFileReader;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Calendar;
+import java.util.List;
+
+public abstract class MethodGenerationBase extends TestBase {
+
+  private static final Path GOOGLE_FORMAT_DIR =
+      Paths.get(ToolHelper.THIRD_PARTY_DIR, "google-java-format");
+  private static final Path GOOGLE_FORMAT_JAR =
+      GOOGLE_FORMAT_DIR.resolve("google-java-format-1.7-all-deps.jar");
+
+  protected final DexItemFactory factory = new DexItemFactory();
+
+  protected static String getJavaExecutable() {
+    return ToolHelper.getSystemJavaExecutable();
+  }
+
+  protected abstract DexType getGeneratedType();
+
+  protected abstract List<Class<?>> getMethodTemplateClasses();
+
+  private String getHeaderString(Class<? extends MethodGenerationBase> generationClass) {
+    int year = Calendar.getInstance().get(Calendar.YEAR);
+    String simpleName = generationClass.getSimpleName();
+    return StringUtils.lines(
+        "// Copyright (c) " + year + ", 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.",
+        "",
+        "// ***********************************************************************************",
+        "// GENERATED FILE. DO NOT EDIT! See " + simpleName + ".java.",
+        "// ***********************************************************************************",
+        "",
+        "package " + getGeneratedClassPackageName() + ";");
+  }
+
+  protected Path getGeneratedFile() {
+    return Paths.get(ToolHelper.SOURCE_DIR, getGeneratedType().getInternalName() + ".java");
+  }
+
+  private String getGeneratedClassName() {
+    return getGeneratedType().getName();
+  }
+
+  private String getGeneratedClassPackageName() {
+    return getGeneratedType().getPackageName();
+  }
+
+  // Running this method will regenerate / overwrite the content of the generated class.
+  protected void generateMethodsAndWriteThemToFile() throws IOException {
+    FileUtils.writeToFile(getGeneratedFile(), null, generateMethods().getBytes());
+  }
+
+  // Running this method generate the content of the generated class but does not overwrite it.
+  protected String generateMethods() throws IOException {
+    CfCodePrinter codePrinter = new CfCodePrinter();
+
+    File tempFile = File.createTempFile("output-", ".java");
+
+    readMethodTemplatesInto(codePrinter);
+    generateRawOutput(codePrinter, tempFile.toPath());
+    String result = formatRawOutput(tempFile.toPath());
+
+    tempFile.deleteOnExit();
+    return result;
+  }
+
+  private void readMethodTemplatesInto(CfCodePrinter codePrinter) throws IOException {
+    InternalOptions options = new InternalOptions();
+    JarClassFileReader reader =
+        new JarClassFileReader(
+            new JarApplicationReader(options),
+            clazz -> {
+              for (DexEncodedMethod method : clazz.allMethodsSorted()) {
+                if (method.isInitializer()) {
+                  continue;
+                }
+                String methodName =
+                    method.method.holder.getName() + "_" + method.method.name.toString();
+                codePrinter.visitMethod(methodName, method.getCode().asCfCode());
+              }
+            });
+    for (Class<?> clazz : getMethodTemplateClasses()) {
+      try (InputStream stream = Files.newInputStream(ToolHelper.getClassFileForTestClass(clazz))) {
+        reader.read(Origin.unknown(), ClassKind.PROGRAM, stream);
+      }
+    }
+  }
+
+  private void generateRawOutput(CfCodePrinter codePrinter, Path tempFile) throws IOException {
+    try (PrintStream printer = new PrintStream(Files.newOutputStream(tempFile))) {
+      printer.print(getHeaderString(this.getClass()));
+      codePrinter.getImports().forEach(i -> printer.println("import " + i + ";"));
+      printer.println("public final class " + getGeneratedClassName() + " {\n");
+      codePrinter.getMethods().forEach(printer::println);
+      printer.println("}");
+    }
+  }
+
+  private String formatRawOutput(Path tempFile) throws IOException {
+    // Apply google format.
+    ProcessBuilder builder =
+        new ProcessBuilder(
+            ImmutableList.of(
+                getJavaExecutable(),
+                "-jar",
+                GOOGLE_FORMAT_JAR.toString(),
+                tempFile.toAbsolutePath().toString()));
+    String commandString = String.join(" ", builder.command());
+    System.out.println(commandString);
+    Process process = builder.start();
+    ProcessResult result = ToolHelper.drainProcessOutputStreams(process, commandString);
+    if (result.exitCode != 0) {
+      throw new IllegalStateException(result.toString());
+    }
+    // Fix line separators.
+    String content = result.stdout;
+    if (!StringUtils.LINE_SEPARATOR.equals("\n")) {
+      return content.replace(StringUtils.LINE_SEPARATOR, "\n");
+    }
+    return content;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java
index 8dcad81..dafc7db 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java
@@ -4,10 +4,15 @@
 
 package com.android.tools.r8.classmerging;
 
+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.objectweb.asm.Opcodes.INVOKESPECIAL;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -50,11 +55,16 @@
         .addProgramClasses(Base.class, B.class, Main.class)
         .addProgramClassFileData(getAWithRewrittenInvokeSpecialToBase())
         .addKeepMainRule(Main.class)
-        .addKeepClassRules(Base.class, B.class)
         .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableMergeAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(EXPECTED);
+        .assertSuccessWithOutputLines(EXPECTED)
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(A.class), not(isPresent()));
+            });
   }
 
   private byte[] getAWithRewrittenInvokeSpecialToBase() throws IOException {
@@ -72,6 +82,7 @@
         .transform();
   }
 
+  @NeverMerge
   public static class Base {
 
     public void collect() {
@@ -93,8 +104,10 @@
     }
   }
 
+  @NeverClassInline
   public static class B extends A {
 
+    @NeverInline
     public void bar() {
       collect();
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/LambdaWithDefaultMethodsTest.java b/src/test/java/com/android/tools/r8/desugar/LambdaWithDefaultMethodsTest.java
new file mode 100644
index 0000000..ce7a552
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/LambdaWithDefaultMethodsTest.java
@@ -0,0 +1,89 @@
+// 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.desugar;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LambdaWithDefaultMethodsTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("A::bar", "I::bar");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public LambdaWithDefaultMethodsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(I.class, A.class, TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class, A.class, TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  interface I {
+    void foo();
+
+    default void bar() {
+      System.out.println("I::bar");
+    }
+  }
+
+  static class A implements I {
+
+    @Override
+    public void foo() {
+      System.out.println("A::foo");
+    }
+
+    @Override
+    public void bar() {
+      System.out.println("A::bar");
+    }
+  }
+
+  static class TestClass {
+
+    public static void runDefault(I i) {
+      i.bar();
+    }
+
+    public static I createLambda() {
+      return () -> System.out.println("lambda::foo");
+    }
+
+    public static void main(String[] args) {
+      // Target the default method, causing it to be marked reachable.
+      // This is done directly in main to ensure that it is the first thing hit in tracing.
+      I i = new A();
+      i.bar();
+      // Create a call-site instance that will need to identify the default method as live.
+      // The creation is outlined to ensure that it is not hit before the method is reachable.
+      runDefault(createLambda());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
index 66cdcbc..687a673 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
@@ -8,7 +8,6 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
-import com.android.tools.r8.desugar.desugaredlibrary.conversiontests.MoreFunctionConversionTest.CustomLibClass;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingTest.java
similarity index 79%
rename from src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java
rename to src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingTest.java
index a15dc08..b13c250 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingTest.java
@@ -3,9 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.enumunboxing;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.R8TestRunResult;
@@ -17,7 +17,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class ComparisonEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+public class ComparisonEnumUnboxingTest extends EnumUnboxingTestBase {
 
   private static final Class<?>[] INPUTS = new Class<?>[] {NullCheck.class, EnumComparison.class};
 
@@ -30,7 +30,7 @@
     return enumUnboxingTestParameters();
   }
 
-  public ComparisonEnumUnboxingAnalysisTest(
+  public ComparisonEnumUnboxingTest(
       TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
@@ -41,9 +41,10 @@
   public void testEnumUnboxing() throws Exception {
     R8TestCompileResult compile =
         testForR8(parameters.getBackend())
-            .addInnerClasses(ComparisonEnumUnboxingAnalysisTest.class)
+            .addInnerClasses(ComparisonEnumUnboxingTest.class)
             .addKeepMainRules(INPUTS)
             .enableInliningAnnotations()
+            .enableNeverClassInliningAnnotations()
             .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
@@ -51,12 +52,8 @@
             .compile()
             .inspect(
                 inspector -> {
-                  assertThat(
-                      inspector.clazz(NullCheck.class).uniqueMethodWithName("nullCheck"),
-                      isPresent());
-                  assertThat(
-                      inspector.clazz(EnumComparison.class).uniqueMethodWithName("check"),
-                      isPresent());
+                  assertEquals(3, inspector.clazz(NullCheck.class).allMethods().size());
+                  assertEquals(2, inspector.clazz(EnumComparison.class).allMethods().size());
                 });
     for (Class<?> input : INPUTS) {
       R8TestRunResult run =
@@ -72,6 +69,7 @@
   @SuppressWarnings("ConstantConditions")
   static class NullCheck {
 
+    @NeverClassInline
     enum MyEnum {
       A,
       B
@@ -84,6 +82,14 @@
       System.out.println(false);
       System.out.println(nullCheck(null));
       System.out.println(true);
+      System.out.println(onlyNull());
+      System.out.println(true);
+    }
+
+    // This method has no outValue of type MyEnum but still needs to be reprocessed.
+    @NeverInline
+    static boolean onlyNull() {
+      return nullCheck(null);
     }
 
     // Do not resolve the == with constants after inlining.
@@ -95,6 +101,7 @@
 
   static class EnumComparison {
 
+    @NeverClassInline
     enum MyEnum {
       A,
       B
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingMethods.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingMethods.java
new file mode 100644
index 0000000..e1d51cc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingMethods.java
@@ -0,0 +1,54 @@
+// 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;
+
+// This class implements support methods for enum unboxing. The enum unboxing optimization may
+// rewrite any call to an enum method into one of the following methods.
+// The methods Enum#name, Enum#toString and MyEnum#valueOf cannot be implemented here since they
+// are different on each Enum implementation.
+public class EnumUnboxingMethods {
+
+  // An enum is unboxed to ordinal + 1.
+  // For example, enum E {A,B}, is unboxed to null -> 0, A -> 1, B-> 2.
+  // Computing the ordinal of an unboxed enum throws a null pointer exception on 0,
+  // else answers the value - 1.
+  public static int ordinal(int unboxedEnum) {
+    if (unboxedEnum == 0) {
+      throw new NullPointerException();
+    }
+    return unboxedEnum - 1;
+  }
+
+  // The values methods normally reads the $VALUES field, then clones and returns it.
+  // In our case we just create a new instance each time.
+  // Note: This can replace a MyEnum#values() call, but not a MyEnum#$VALUES static get.
+  // numEnums is the number of elements in the enum.
+  public static int[] values(int numEnums) {
+    int[] ints = new int[numEnums];
+    for (int i = 0; i < ints.length; i++) {
+      ints[i] = i + 1;
+    }
+    return ints;
+  }
+
+  // We assume the enum could be unboxed only if both parameters were proven to be of the same
+  // enum types, so we do not check they belong to the same Enum type.
+  // We need the 0 checks for null pointer exception.
+  public static int compareTo(int unboxedEnum1, int unboxedEnum2) {
+    if (unboxedEnum1 == 0 || unboxedEnum2 == 0) {
+      throw new NullPointerException();
+    }
+    // Formula: unboxedEnum1 - 1 - (unboxedEnum2 - 1), simplified as follow:
+    return unboxedEnum1 - unboxedEnum2;
+  }
+
+  // Equals on Enum is implemented using directly ==. The invoke raises a NPE if the
+  // receiver is null, but returns false if the parameter is null.
+  public static boolean equals(int unboxedEnum1, int unboxedEnum2) {
+    if (unboxedEnum1 == 0) {
+      throw new NullPointerException();
+    }
+    return unboxedEnum1 == unboxedEnum2;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
similarity index 86%
rename from src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java
rename to src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
index c0eeaf4..4f13c05 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
@@ -7,16 +7,17 @@
 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.FailingEnumUnboxingAnalysisTest.EnumInstanceFieldMain.EnumInstanceField;
-import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumInterfaceMain.EnumInterface;
-import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumStaticFieldMain.EnumStaticField;
-import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumStaticMethodMain.EnumStaticMethod;
-import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumVirtualMethodMain.EnumVirtualMethod;
+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;
@@ -25,7 +26,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class FailingEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+public class FailingEnumUnboxingTest extends EnumUnboxingTestBase {
 
   private static final Class<?>[] FAILURES = {
     EnumInterface.class,
@@ -44,7 +45,7 @@
     return enumUnboxingTestParameters();
   }
 
-  public FailingEnumUnboxingAnalysisTest(
+  public FailingEnumUnboxingTest(
       TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
@@ -54,7 +55,7 @@
   @Test
   public void testEnumUnboxingFailure() throws Exception {
     R8FullTestBuilder r8FullTestBuilder =
-        testForR8(parameters.getBackend()).addInnerClasses(FailingEnumUnboxingAnalysisTest.class);
+        testForR8(parameters.getBackend()).addInnerClasses(FailingEnumUnboxingTest.class);
     for (Class<?> failure : FAILURES) {
       r8FullTestBuilder.addKeepMainRule(failure.getEnclosingClass());
     }
@@ -62,6 +63,7 @@
         r8FullTestBuilder
             .noTreeShaking() // Disabled to avoid merging Itf into EnumInterface.
             .enableInliningAnnotations()
+            .enableNeverClassInliningAnnotations()
             .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
@@ -96,6 +98,7 @@
       System.out.println(0);
     }
 
+    @NeverClassInline
     enum EnumInterface implements Itf {
       A,
       B,
@@ -119,6 +122,7 @@
       System.out.println(0);
     }
 
+    @NeverClassInline
     enum EnumStaticField {
       A,
       B,
@@ -129,6 +133,7 @@
 
   static class EnumInstanceFieldMain {
 
+    @NeverClassInline
     enum EnumInstanceField {
       A(10),
       B(20),
@@ -150,6 +155,7 @@
 
   static class EnumStaticMethodMain {
 
+    @NeverClassInline
     enum EnumStaticMethod {
       A,
       B,
@@ -181,6 +187,7 @@
       System.out.println(-1);
     }
 
+    @NeverClassInline
     enum EnumVirtualMethod {
       A,
       B,
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
similarity index 92%
rename from src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
rename to src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
index 944c9c3..0391875 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
@@ -7,6 +7,7 @@
 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.R8TestCompileResult;
 import com.android.tools.r8.R8TestRunResult;
@@ -20,7 +21,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class FailingMethodEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+public class FailingMethodEnumUnboxingTest extends EnumUnboxingTestBase {
 
   private static final Class<?>[] FAILURES = {
     InstanceFieldPutObject.class,
@@ -39,7 +40,7 @@
     return enumUnboxingTestParameters();
   }
 
-  public FailingMethodEnumUnboxingAnalysisTest(
+  public FailingMethodEnumUnboxingTest(
       TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
@@ -50,12 +51,13 @@
   public void testEnumUnboxingFailure() throws Exception {
     R8TestCompileResult compile =
         testForR8(parameters.getBackend())
-            .addInnerClasses(FailingMethodEnumUnboxingAnalysisTest.class)
+            .addInnerClasses(FailingMethodEnumUnboxingTest.class)
             .addKeepMainRules(FAILURES)
             .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .enableInliningAnnotations()
+            .enableNeverClassInliningAnnotations()
             .setMinApi(parameters.getApiLevel())
             .compile()
             .inspect(this::assertEnumsAsExpected);
@@ -90,6 +92,7 @@
 
   static class InstanceFieldPutObject {
 
+    @NeverClassInline
     enum MyEnum {
       A,
       B,
@@ -114,6 +117,7 @@
 
   static class StaticFieldPutObject {
 
+    @NeverClassInline
     enum MyEnum {
       A,
       B,
@@ -137,6 +141,7 @@
 
   static class ToString {
 
+    @NeverClassInline
     enum MyEnum {
       A,
       B,
@@ -152,6 +157,7 @@
 
   static class EnumSetTest {
 
+    @NeverClassInline
     enum MyEnum {
       A,
       B,
@@ -167,6 +173,7 @@
 
   static class FailingPhi {
 
+    @NeverClassInline
     enum MyEnum {
       A,
       B,
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
similarity index 90%
rename from src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java
rename to src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
index daaf504..129230a 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.R8TestRunResult;
@@ -17,7 +18,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class FieldPutEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+public class FieldPutEnumUnboxingTest extends EnumUnboxingTestBase {
 
   private static final Class<?>[] INPUTS =
       new Class<?>[] {InstanceFieldPut.class, StaticFieldPut.class};
@@ -31,7 +32,7 @@
     return enumUnboxingTestParameters();
   }
 
-  public FieldPutEnumUnboxingAnalysisTest(
+  public FieldPutEnumUnboxingTest(
       TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
@@ -42,12 +43,13 @@
   public void testEnumUnboxing() throws Exception {
     R8TestCompileResult compile =
         testForR8(parameters.getBackend())
-            .addInnerClasses(FieldPutEnumUnboxingAnalysisTest.class)
+            .addInnerClasses(FieldPutEnumUnboxingTest.class)
             .addKeepMainRules(INPUTS)
             .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .enableInliningAnnotations()
+            .enableNeverClassInliningAnnotations()
             .setMinApi(parameters.getApiLevel())
             .noMinification()
             .compile()
@@ -72,6 +74,7 @@
 
   static class InstanceFieldPut {
 
+    @NeverClassInline
     enum MyEnum {
       A,
       B,
@@ -90,10 +93,12 @@
       System.out.println(1);
     }
 
+    @NeverInline
     void setA() {
       e = MyEnum.A;
     }
 
+    @NeverInline
     void setB() {
       e = MyEnum.B;
     }
@@ -101,6 +106,7 @@
 
   static class StaticFieldPut {
 
+    @NeverClassInline
     enum MyEnum {
       A,
       B,
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java b/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java
new file mode 100644
index 0000000..706cdc7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java
@@ -0,0 +1,65 @@
+// 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 static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Comparator;
+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 GenerateEnumUnboxingMethods extends MethodGenerationBase {
+
+  private final DexType GENERATED_TYPE =
+      factory.createType("Lcom/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods;");
+  private final List<Class<?>> METHOD_TEMPLATE_CLASSES =
+      ImmutableList.of(EnumUnboxingMethods.class);
+
+  protected final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntime(CfVm.JDK9).build();
+  }
+
+  public GenerateEnumUnboxingMethods(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Override
+  protected DexType getGeneratedType() {
+    return GENERATED_TYPE;
+  }
+
+  @Override
+  protected List<Class<?>> getMethodTemplateClasses() {
+    return METHOD_TEMPLATE_CLASSES;
+  }
+
+  @Test
+  public void testEnumUtilityMethodsGenerated() throws Exception {
+    ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());
+    sorted.sort(Comparator.comparing(Class::getTypeName));
+    assertEquals("Classes should be listed in sorted order", sorted, getMethodTemplateClasses());
+    assertEquals(
+        FileUtils.readTextFile(getGeneratedFile(), StandardCharsets.UTF_8), generateMethods());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new GenerateEnumUnboxingMethods(null).generateMethodsAndWriteThemToFile();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingTest.java
similarity index 90%
rename from src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java
rename to src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingTest.java
index afd2e6a..91cd718 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.enumunboxing;
 
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
@@ -13,7 +14,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class OrdinalEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+public class OrdinalEnumUnboxingTest extends EnumUnboxingTestBase {
 
   private static final Class<?> ENUM_CLASS = MyEnum.class;
 
@@ -26,7 +27,7 @@
     return enumUnboxingTestParameters();
   }
 
-  public OrdinalEnumUnboxingAnalysisTest(
+  public OrdinalEnumUnboxingTest(
       TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
@@ -41,6 +42,7 @@
             .addProgramClasses(classToTest, ENUM_CLASS)
             .addKeepMainRule(classToTest)
             .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
+            .enableNeverClassInliningAnnotations()
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
@@ -52,6 +54,7 @@
     assertLines2By2Correct(run.getStdOut());
   }
 
+  @NeverClassInline
   enum MyEnum {
     A,
     B,
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java
similarity index 91%
rename from src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java
rename to src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java
index b231d32..62ccaf8 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java
@@ -4,6 +4,7 @@
 
 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;
@@ -14,7 +15,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class PhiEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+public class PhiEnumUnboxingTest extends EnumUnboxingTestBase {
 
   private static final Class<?> ENUM_CLASS = MyEnum.class;
 
@@ -27,7 +28,7 @@
     return enumUnboxingTestParameters();
   }
 
-  public PhiEnumUnboxingAnalysisTest(
+  public PhiEnumUnboxingTest(
       TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
@@ -43,6 +44,7 @@
             .addKeepMainRule(classToTest)
             .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
             .enableInliningAnnotations()
+            .enableNeverClassInliningAnnotations()
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
@@ -54,6 +56,7 @@
     assertLines2By2Correct(run.getStdOut());
   }
 
+  @NeverClassInline
   enum MyEnum {
     A,
     B,
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
similarity index 89%
rename from src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java
rename to src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
index a40dec0..88dd987 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
@@ -4,6 +4,7 @@
 
 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;
@@ -14,7 +15,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class SwitchEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+public class SwitchEnumUnboxingTest extends EnumUnboxingTestBase {
 
   private static final Class<?> ENUM_CLASS = MyEnum.class;
 
@@ -27,7 +28,7 @@
     return enumUnboxingTestParameters();
   }
 
-  public SwitchEnumUnboxingAnalysisTest(
+  public SwitchEnumUnboxingTest(
       TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
@@ -39,10 +40,11 @@
     Class<Switch> classToTest = Switch.class;
     R8TestRunResult run =
         testForR8(parameters.getBackend())
-            .addInnerClasses(SwitchEnumUnboxingAnalysisTest.class)
+            .addInnerClasses(SwitchEnumUnboxingTest.class)
             .addKeepMainRule(classToTest)
             .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
             .enableInliningAnnotations()
+            .enableNeverClassInliningAnnotations()
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
@@ -54,6 +56,7 @@
     assertLines2By2Correct(run.getStdOut());
   }
 
+  @NeverClassInline
   enum MyEnum {
     A,
     B,
diff --git a/src/test/java/com/android/tools/r8/graph/MethodWithoutCodeAttributeTest.java b/src/test/java/com/android/tools/r8/graph/MethodWithoutCodeAttributeTest.java
new file mode 100644
index 0000000..c857e00
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/MethodWithoutCodeAttributeTest.java
@@ -0,0 +1,104 @@
+// 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.graph;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+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 MethodWithoutCodeAttributeTest extends TestBase {
+  private static final String MAIN = "com.android.tools.r8.Test";
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  public MethodWithoutCodeAttributeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue("D8 tests.", parameters.isDexRuntime());
+    try {
+      testForD8()
+          .addProgramClassFileData(TestDump.dump())
+          .compile();
+      fail("Expected to fail due to multiple annotations");
+    } catch (CompilationFailedException e) {
+      assertThat(
+          e.getCause().getMessage(),
+          containsString("Absent Code attribute in method that is not native or abstract"));
+    }
+  }
+
+  @Test
+  public void testJVMOutput() throws Exception {
+    assumeTrue("Only run JVM reference on CF runtimes", parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClassFileData(TestDump.dump())
+        .run(parameters.getRuntime(), MAIN)
+        .assertFailureWithErrorThatMatches(
+            containsString("Absent Code attribute in method that is not native or abstract"));
+  }
+
+  static class TestDump implements Opcodes {
+    public static byte[] dump () throws Exception {
+      ClassWriter classWriter = new ClassWriter(0);
+      MethodVisitor methodVisitor;
+
+      classWriter.visit(
+          V1_8, ACC_SUPER,
+          "com/android/tools/r8/Test",
+          null,
+          "java/lang/Object",
+          null);
+
+      {
+        methodVisitor = classWriter.visitMethod(
+            ACC_PUBLIC | ACC_SYNTHETIC, "foo", "(Ljava/lang/Object;)V", null, null);
+        methodVisitor.visitAnnotableParameterCount(1, false);
+        // b/149808321: no code attribute
+        methodVisitor.visitEnd();
+      }
+
+      {
+        methodVisitor = classWriter.visitMethod(0, "main", "([Ljava/lang/String;)V", null, null);
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(42, label0);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("Test::main");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(44, label1);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(2, 1);
+        methodVisitor.visitEnd();
+      }
+      classWriter.visitEnd();
+
+      return classWriter.toByteArray();
+    }
+  }
+}
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 fc8f3bf..7d04499 100644
--- a/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
@@ -4,7 +4,9 @@
 package com.android.tools.r8.internal;
 
 import static com.android.tools.r8.ToolHelper.isLocalDevelopment;
-import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
index 4c3085e..774209e 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -10,8 +10,8 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
@@ -33,16 +33,14 @@
 
 public class R8GMSCoreLookupTest extends TestBase {
 
-  static final String APP_DIR = "third_party/gmscore/v5/";
-  private AndroidApp app;
+  private static final String APP_DIR = "third_party/gmscore/v5/";
   private DirectMappedDexApplication program;
-  private AppInfoWithSubtyping appInfo;
-  private AppView<? extends AppInfoWithClassHierarchy> appView;
+  private AppView<? extends AppInfoWithSubtyping> appView;
 
   @Before
   public void readGMSCore() throws Exception {
     Path directory = Paths.get(APP_DIR);
-    app = ToolHelper.builderFromProgramDirectory(directory).build();
+    AndroidApp app = ToolHelper.builderFromProgramDirectory(directory).build();
     Path mapFile = directory.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE);
     StringResource proguardMap = null;
     if (Files.exists(mapFile)) {
@@ -54,19 +52,24 @@
         new ApplicationReader(app, new InternalOptions(), timing)
             .read(proguardMap, executorService)
             .toDirect();
-    appInfo = new AppInfoWithSubtyping(program);
-    appView = computeAppViewWithSubtyping(app);
+    InternalOptions options = new InternalOptions();
+    appView = AppView.createForR8(new AppInfoWithSubtyping(program), options);
+    appView.setAppServices(AppServices.builder(appView).build());
+  }
+
+  private AppInfoWithSubtyping appInfo() {
+    return appView.appInfo();
   }
 
   private void testVirtualLookup(DexProgramClass clazz, DexEncodedMethod method) {
     // Check lookup will produce the same result.
     DexMethod id = method.method;
-    assertEquals(appInfo.resolveMethod(id.holder, method.method).getSingleTarget(), method);
+    assertEquals(appInfo().resolveMethod(id.holder, method.method).getSingleTarget(), method);
 
     // Check lookup targets with include method.
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(clazz, method.method);
+    ResolutionResult resolutionResult = appInfo().resolveMethodOnClass(clazz, method.method);
     LookupResult lookupResult =
-        resolutionResult.lookupVirtualDispatchTargets(clazz, appView, appInfo);
+        resolutionResult.lookupVirtualDispatchTargets(clazz, appView, appInfo());
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<DexEncodedMethod> targets = lookupResult.asLookupResultSuccess().getMethodTargets();
     assertTrue(targets.contains(method));
@@ -74,13 +77,13 @@
 
   private void testInterfaceLookup(DexProgramClass clazz, DexEncodedMethod method) {
     LookupResult lookupResult =
-        appInfo
+        appInfo()
             .resolveMethodOnInterface(clazz, method.method)
-            .lookupVirtualDispatchTargets(clazz, appView, appInfo);
+            .lookupVirtualDispatchTargets(clazz, appView, appInfo());
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<DexEncodedMethod> targets = lookupResult.asLookupResultSuccess().getMethodTargets();
-    if (appInfo.subtypes(method.method.holder).stream()
-        .allMatch(t -> appInfo.definitionFor(t).isInterface())) {
+    if (appInfo().subtypes(method.method.holder).stream()
+        .allMatch(t -> appInfo().definitionFor(t).isInterface())) {
       assertEquals(
           0,
           targets.stream()
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index 6a4bd17..43c8e17 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -216,6 +216,11 @@
     }
 
     @Override
+    public boolean shouldApplyCodeRewritings(DexEncodedMethod method) {
+      return false;
+    }
+
+    @Override
     public boolean isProcessedConcurrently(DexEncodedMethod method) {
       return false;
     }
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index 306c93a..05c11c4 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -1,33 +1,19 @@
 // Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
+
 package com.android.tools.r8.ir.desugar.backports;
 
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.cf.CfCodePrinter;
-import com.android.tools.r8.graph.ClassKind;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.JarApplicationReader;
-import com.android.tools.r8.graph.JarClassFileReader;
-import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.PrintStream;
 import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
@@ -36,38 +22,12 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
-// Class to generate and validate CfCode for backport methods.
 @RunWith(Parameterized.class)
-public class GenerateBackportMethods extends TestBase {
+public class GenerateBackportMethods extends MethodGenerationBase {
 
-  static final Path googleFormatDir = Paths.get(ToolHelper.THIRD_PARTY_DIR, "google-java-format");
-  static final Path googleFormatJar =
-      googleFormatDir.resolve("google-java-format-1.7-all-deps.jar");
-  static final Path backportMethodsFile =
-      Paths.get(
-          ToolHelper.SOURCE_DIR,
-          "com",
-          "android",
-          "tools",
-          "r8",
-          "ir",
-          "desugar",
-          "backports",
-          "BackportedMethods.java");
-
-  static final String header =
-      StringUtils.lines(
-          "// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file",
-          "// for details. All rights reserved. Use of this source code is governed by a",
-          "// BSD-style license that can be found in the LICENSE file.",
-          "",
-          "// ***********************************************************************************",
-          "// GENERATED FILE. DO NOT EDIT! Changes should be made to GenerateBackportMethods.java",
-          "// ***********************************************************************************",
-          "",
-          "package com.android.tools.r8.ir.desugar.backports;");
-
-  static final List<Class<?>> methodTemplateClasses =
+  private final DexType GENERATED_TYPE =
+      factory.createType("Lcom/android/tools/r8/ir/desugar/backports/BackportedMethods;");
+  private final List<Class<?>> METHOD_TEMPLATE_CLASSES =
       ImmutableList.of(
           BooleanMethods.class,
           ByteMethods.class,
@@ -87,7 +47,7 @@
           StreamMethods.class,
           StringMethods.class);
 
-  final TestParameters parameters;
+  protected final TestParameters parameters;
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -98,73 +58,26 @@
     this.parameters = parameters;
   }
 
+  @Override
+  protected DexType getGeneratedType() {
+    return GENERATED_TYPE;
+  }
+
+  @Override
+  protected List<Class<?>> getMethodTemplateClasses() {
+    return METHOD_TEMPLATE_CLASSES;
+  }
+
   @Test
-  public void test() throws Exception {
-    ArrayList<Class<?>> sorted = new ArrayList<>(methodTemplateClasses);
+  public void testBackportsGenerated() throws Exception {
+    ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());
     sorted.sort(Comparator.comparing(Class::getTypeName));
-    assertEquals("Classes should be listed in sorted order", sorted, methodTemplateClasses);
+    assertEquals("Classes should be listed in sorted order", sorted, getMethodTemplateClasses());
     assertEquals(
-        FileUtils.readTextFile(backportMethodsFile, StandardCharsets.UTF_8),
-        generateBackportMethods(parameters.getRuntime().asCf().getJavaExecutable().toString()));
+        FileUtils.readTextFile(getGeneratedFile(), StandardCharsets.UTF_8), generateMethods());
   }
 
-  // Running this method will regenerate / overwrite the content of the backport methods.
   public static void main(String[] args) throws Exception {
-    FileUtils.writeToFile(
-        backportMethodsFile,
-        null,
-        generateBackportMethods(ToolHelper.getSystemJavaExecutable()).getBytes());
-  }
-
-  private static String generateBackportMethods(String javaExecutable) throws IOException {
-    InternalOptions options = new InternalOptions();
-    CfCodePrinter codePrinter = new CfCodePrinter();
-    JarClassFileReader reader =
-        new JarClassFileReader(
-            new JarApplicationReader(options),
-            clazz -> {
-              for (DexEncodedMethod method : clazz.allMethodsSorted()) {
-                if (method.isInitializer()) {
-                  continue;
-                }
-                String methodName =
-                    method.method.holder.getName() + "_" + method.method.name.toString();
-                codePrinter.visitMethod(methodName, method.getCode().asCfCode());
-              }
-            });
-    for (Class<?> clazz : methodTemplateClasses) {
-      try (InputStream stream = Files.newInputStream(ToolHelper.getClassFileForTestClass(clazz))) {
-        reader.read(Origin.unknown(), ClassKind.PROGRAM, stream);
-      }
-    }
-
-    Path outfile = Paths.get(ToolHelper.BUILD_DIR, "backports.java");
-    try (PrintStream printer = new PrintStream(Files.newOutputStream(outfile))) {
-      printer.println(header);
-      codePrinter.getImports().forEach(i -> printer.println("import " + i + ";"));
-      printer.println("public final class BackportedMethods {\n");
-      codePrinter.getMethods().forEach(printer::println);
-      printer.println("}");
-    }
-
-    ProcessBuilder builder =
-        new ProcessBuilder(
-            ImmutableList.of(
-                javaExecutable,
-                "-jar",
-                googleFormatJar.toString(),
-                outfile.toAbsolutePath().toString()));
-    String commandString = String.join(" ", builder.command());
-    System.out.println(commandString);
-    Process process = builder.start();
-    ProcessResult result = ToolHelper.drainProcessOutputStreams(process, commandString);
-    if (result.exitCode != 0) {
-      throw new IllegalStateException(result.toString());
-    }
-    String content = result.stdout;
-    if (!StringUtils.LINE_SEPARATOR.equals("\n")) {
-      return content.replace(StringUtils.LINE_SEPARATOR, "\n");
-    }
-    return content;
+    new GenerateBackportMethods(null).generateMethodsAndWriteThemToFile();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java
index 7b0a595..24ffad1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java
@@ -12,14 +12,33 @@
 import com.android.tools.r8.KeepConstantArguments;
 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.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
 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.MethodSubject;
+import java.util.Objects;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class InliningAfterClassInitializationTest extends TestBase {
 
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InliningAfterClassInitializationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
   @Test
   public void testClass1() throws Exception {
     Class<TestClass1> mainClass = TestClass1.class;
@@ -177,15 +196,35 @@
     assertThat(testMethod, invokesMethod(notInlineableMethod));
   }
 
-  private CodeInspector buildAndRun(Class<?> mainClass, String expectedOutput) throws Exception {
-    testForJvm().addTestClasspath().run(mainClass).assertSuccessWithOutput(expectedOutput);
+  @Test
+  public void testClass10() throws Exception {
+    Class<TestClass10> mainClass = TestClass10.class;
+    CodeInspector inspector =
+        buildAndRun(mainClass, StringUtils.lines("In A.<clinit>()", "In A.inlineable()"));
 
-    return testForR8(Backend.DEX)
+    ClassSubject classA = inspector.clazz(A.class);
+    assertThat(classA, isPresent());
+
+    MethodSubject inlineableMethod = classA.uniqueMethodWithName("inlineable");
+    assertThat(inlineableMethod, not(isPresent()));
+  }
+
+  private CodeInspector buildAndRun(Class<?> mainClass, String expectedOutput) throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addTestClasspath()
+          .run(parameters.getRuntime(), mainClass)
+          .assertSuccessWithOutput(expectedOutput);
+    }
+
+    return testForR8(parameters.getBackend())
         .addInnerClasses(InliningAfterClassInitializationTest.class)
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
         .addKeepMainRule(mainClass)
         .enableConstantArgumentAnnotations()
         .enableInliningAnnotations()
-        .run(mainClass)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), mainClass)
         .assertSuccessWithOutput(expectedOutput)
         .inspector();
   }
@@ -347,6 +386,21 @@
     }
   }
 
+  static class TestClass10 {
+
+    public static void main(String[] args) {
+      test(new A());
+    }
+
+    @NeverInline
+    private static void test(A obj) {
+      Objects.requireNonNull(obj);
+
+      // A is guaranteed to be initialized since requireNonNull() will throw if `obj` is null.
+      A.inlineable();
+    }
+  }
+
   static class A {
 
     public String instanceField = "Field A.instanceField";
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B149468959.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B149468959.java
new file mode 100644
index 0000000..dff369b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B149468959.java
@@ -0,0 +1,61 @@
+// 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.membervaluepropagation;
+
+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 B149468959 extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public B149468959(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(B149468959.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("A");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      mutate();
+      for (MyEnum x : MyEnum.values()) {
+        if (x.include) {
+          System.out.println(x);
+        }
+      }
+    }
+
+    static void mutate() {
+      MyEnum.B.include = false;
+    }
+  }
+
+  enum MyEnum {
+    A,
+    B;
+
+    boolean include = true;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
index a74ac5f..2c8f4b3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
@@ -75,12 +75,10 @@
     MethodSubject testMaybeNullMethodSubject =
         testClassSubject.uniqueMethodWithName("testMaybeNull");
     assertThat(testMaybeNullMethodSubject, isPresent());
-    // TODO(b/125282093): Should synthesize a null-check and still propagate the field values even
-    //  when the receiver is nullable.
     assertTrue(
         testMaybeNullMethodSubject
             .streamInstructions()
-            .anyMatch(InstructionSubject::isInstanceGet));
+            .noneMatch(InstructionSubject::isInstanceGet));
     // TODO(b/125282093): Should be able to remove the new-instance instruction since the instance
     //  ends up being unused.
     assertTrue(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentForwardingConstructorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentForwardingConstructorTest.java
index a118c85..5d0acf1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentForwardingConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentForwardingConstructorTest.java
@@ -96,10 +96,12 @@
 
     int x;
 
+    @NeverInline
     A() {
       this(42);
     }
 
+    @NeverInline
     A(int x) {
       this.x = x;
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentMultipleConstructorsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentMultipleConstructorsTest.java
index 6663b91..b6fe557 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentMultipleConstructorsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentMultipleConstructorsTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.membervaluepropagation.fields;
 
 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;
@@ -50,8 +51,7 @@
     ClassSubject testClassSubject = inspector.clazz(TestClass.class);
     assertThat(testClassSubject, isPresent());
     assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
-    // TODO(b/147652121): Should be absent.
-    assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+    assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedBySameConstantInMultipleConstructorsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedBySameConstantInMultipleConstructorsTest.java
index 2f0cf08..3033e29 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedBySameConstantInMultipleConstructorsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedBySameConstantInMultipleConstructorsTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.membervaluepropagation.fields;
 
 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;
@@ -50,8 +51,7 @@
     ClassSubject testClassSubject = inspector.clazz(TestClass.class);
     assertThat(testClassSubject, isPresent());
     assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
-    // TODO(b/147652121): Should be absent.
-    assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+    assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java
new file mode 100644
index 0000000..18badf8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java
@@ -0,0 +1,84 @@
+// 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.sealed;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiFunction;
+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 SealedClassTest extends KotlinTestBase {
+
+  private static final String MAIN = "com.android.tools.r8.kotlin.sealed.kt.FormatKt";
+  private static final String[] EXPECTED = new String[] {"ZIP"};
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), KotlinTargetVersion.values());
+  }
+
+  public SealedClassTest(TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  private static BiFunction<TestRuntime, KotlinTargetVersion, Path> compilationResults =
+      memoizeBiFunction(SealedClassTest::compileKotlinCode);
+
+  private static Path compileKotlinCode(TestRuntime runtime, KotlinTargetVersion targetVersion)
+      throws IOException {
+    CfRuntime cfRuntime = runtime.isCf() ? runtime.asCf() : TestRuntime.getCheckedInJdk9();
+    return kotlinc(cfRuntime, getStaticTemp(), KOTLINC, targetVersion)
+        .addSourceFiles(getFilesInTestFolderRelativeToClass(SealedClassTest.class, "kt", ".kt"))
+        .compile();
+  }
+
+  @Test
+  public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
+    testForRuntime(parameters)
+        .addProgramFiles(compilationResults.apply(parameters.getRuntime(), targetVersion))
+        .addRunClasspathFiles(buildOnDexRuntime(parameters, ToolHelper.getKotlinStdlibJar()))
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws ExecutionException, CompilationFailedException, IOException {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(compilationResults.apply(parameters.getRuntime(), targetVersion))
+        .addProgramFiles(buildOnDexRuntime(parameters, ToolHelper.getKotlinStdlibJar()))
+        .setMinApi(parameters.getApiLevel())
+        .allowAccessModification()
+        .allowDiagnosticWarningMessages(parameters.isCfRuntime())
+        .addKeepMainRule(MAIN)
+        .compileWithExpectedDiagnostics(
+            diagnosticMessages -> {
+              diagnosticMessages.assertAllWarningMessagesMatch(
+                  containsString("Resource 'META-INF/MANIFEST.MF' already exists."));
+            })
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/sealed/kt/Format.kt b/src/test/java/com/android/tools/r8/kotlin/sealed/kt/Format.kt
new file mode 100644
index 0000000..2c1e189
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/sealed/kt/Format.kt
@@ -0,0 +1,20 @@
+// 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.sealed.kt
+
+public sealed class Format(val name: String) {
+    object Zip : Format("ZIP")
+    object Directory : Format("DIRECTORY")
+}
+
+fun main() {
+  val value = when ("ZIP") {
+      Format.Zip.name -> Format.Zip
+      Format.Directory.name -> Format.Directory
+      else -> throw IllegalArgumentException(
+          "Valid formats: ${Format.Zip.name} or ${Format.Directory.name}.")
+  }
+  println(value.name)
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java b/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java
new file mode 100644
index 0000000..8fccb56
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java
@@ -0,0 +1,135 @@
+// 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.resolution;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 InvokeSuperCallInStaticTest extends TestBase {
+
+  private static final String[] EXPECTED = new String[] {"Base.collect()"};
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvokeSuperCallInStaticTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(Base.class, Main.class)
+                .addClassProgramData(getAWithRewrittenInvokeSpecialToBase())
+                .build(),
+            Main.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexMethod method = buildNullaryVoidMethod(Base.class, "collect", appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+    assertTrue(resolutionResult.isSingleResolution());
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
+    DexEncodedMethod lookedUpMethod =
+        resolutionResult.lookupInvokeSuperTarget(context, appView.appInfo());
+    assertNotNull(lookedUpMethod);
+    assertEquals(lookedUpMethod.method, method);
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addProgramClasses(Base.class, Main.class)
+        .addProgramClassFileData(getAWithRewrittenInvokeSpecialToBase())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Base.class, Main.class)
+        .addProgramClassFileData(getAWithRewrittenInvokeSpecialToBase())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED)
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(A.class), not(isPresent()));
+            });
+  }
+
+  private byte[] getAWithRewrittenInvokeSpecialToBase() throws IOException {
+    return transformer(A.class)
+        .transformMethodInsnInMethod(
+            "callSuper",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              continuation.apply(
+                  INVOKESPECIAL,
+                  DescriptorUtils.getBinaryNameFromJavaType(Base.class.getTypeName()),
+                  name,
+                  descriptor,
+                  false);
+            })
+        .transform();
+  }
+
+  public static class Base {
+
+    public void collect() {
+      System.out.println("Base.collect()");
+    }
+  }
+
+  public static class A extends Base {
+
+    @Override
+    public void collect() {
+      System.out.println("A.collect()");
+    }
+
+    public static void callSuper(A a) {
+      a.collect(); // Will be rewritten from invoke-virtual to invoke-special Base.collect();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      A.callSuper(new A());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
index 9136188..ce8ce66 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.resolution.interfacetargets;
 
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
@@ -130,14 +129,13 @@
   @Test
   public void testR8WithIndirectDefault()
       throws IOException, CompilationFailedException, ExecutionException {
-    // TODO(b/148686556): Fix test expectation.
     testForR8(parameters.getBackend())
         .addProgramClasses(I.class, J.class, K.class, Main.class)
         .addProgramClassFileData(setAimplementsIandK())
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatMatches(containsString("java.lang.AbstractMethodError"));
+        .assertSuccessWithOutputLines(EXPECTED);
   }
 
   private byte[] setAImplementsIAndJ() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java
index da24bcb..8eb3399 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java
@@ -4,9 +4,11 @@
 
 package com.android.tools.r8.resolution.virtualtargets;
 
+import static junit.framework.TestCase.assertNull;
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertFalse;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -15,6 +17,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -43,16 +46,27 @@
   @Test
   public void testResolution() throws Exception {
     assumeTrue(parameters.useRuntimeAsNoneRuntime());
-    AppView<AppInfoWithLiveness> appView =
-        computeAppViewWithLiveness(
-            buildClasses(A.class).addClassProgramData(getMainWithModifiedReceiverCall()).build(),
-            Main.class);
-    AppInfoWithLiveness appInfo = appView.appInfo();
-    DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    DexType mainType = buildType(Main.class, appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethod(mainType, method);
-    // TODO(b/149516194): This should be failed resolution.
-    assertFalse(resolutionResult.isFailedResolution());
+    AssertionError foo =
+        assertThrows(
+            AssertionError.class,
+            () -> {
+              AppView<AppInfoWithLiveness> appView =
+                  computeAppViewWithLiveness(
+                      buildClasses(A.class)
+                          .addClassProgramData(getMainWithModifiedReceiverCall())
+                          .build(),
+                      Main.class);
+              AppInfoWithLiveness appInfo = appView.appInfo();
+              DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
+              ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+              assertTrue(resolutionResult.isSingleResolution());
+              DexType mainType = buildType(Main.class, appInfo.dexItemFactory());
+              DexProgramClass main = appView.definitionForProgramType(mainType);
+              assertNull(resolutionResult.lookupVirtualDispatchTarget(main, appView));
+            });
+    assertThat(
+        foo.getMessage(),
+        containsString(Main.class.getTypeName() + " is not a subtype of " + A.class.getTypeName()));
   }
 
   @Test
@@ -66,17 +80,20 @@
 
   @Test
   public void testR8() throws IOException, CompilationFailedException, ExecutionException {
-    CompilationFailedException compilationFailedException =
-        assertThrows(
-            CompilationFailedException.class,
-            () -> {
-              testForR8(parameters.getBackend())
-                  .addProgramClasses(A.class)
-                  .addProgramClassFileData(getMainWithModifiedReceiverCall())
-                  .setMinApi(parameters.getApiLevel())
-                  .addKeepMainRule(Main.class)
-                  .compile();
-            });
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForR8(parameters.getBackend())
+                .addProgramClasses(A.class)
+                .addProgramClassFileData(getMainWithModifiedReceiverCall())
+                .setMinApi(parameters.getApiLevel())
+                .addKeepMainRule(Main.class)
+                .compileWithExpectedDiagnostics(
+                    diagnosticMessages -> {
+                      diagnosticMessages.assertErrorMessageThatMatches(
+                          containsString(
+                              "The receiver lower bound does not match the receiver type"));
+                    }));
   }
 
   private byte[] getMainWithModifiedReceiverCall() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/shaking/B149831282.java b/src/test/java/com/android/tools/r8/shaking/B149831282.java
new file mode 100644
index 0000000..910d44b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/B149831282.java
@@ -0,0 +1,107 @@
+// 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 org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+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 B149831282 extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public B149831282(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .apply(this::addProgramInputs)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("In A.m()");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .apply(this::addProgramInputs)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("In A.m()");
+  }
+
+  private void addProgramInputs(TestBuilder<?, ?> builder) throws Exception {
+    builder
+        .addProgramClasses(A.class, B.class)
+        .addProgramClassFileData(
+            transformer(TestClass.class)
+                .transformTypeInsnInMethod(
+                    "main",
+                    (opcode, type, continuation) -> {
+                      assertEquals(binaryName(C.class), type);
+                      continuation.apply(opcode, "b149831282/C");
+                    })
+                .transformMethodInsnInMethod(
+                    "main",
+                    (opcode, owner, name, descriptor, isInterface, continuation) -> {
+                      assertEquals(binaryName(C.class), owner);
+                      continuation.apply(opcode, "b149831282/C", name, descriptor, isInterface);
+                    })
+                .transform())
+        .addProgramClassFileData(
+            transformer(C.class).setClassDescriptor("Lb149831282/C;").transform());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new C().m();
+    }
+  }
+
+  @NeverMerge
+  static class A {
+
+    @NeverInline
+    protected void m() {
+      System.out.println("In A.m()");
+    }
+  }
+
+  @NeverMerge
+  public static class B extends A {}
+
+  @NeverClassInline
+  public static class /*b149831282.*/ C extends B {
+
+    @NeverInline
+    @Override
+    public void m() {
+      super.m();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java b/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
index dfd14da..397c054 100644
--- a/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.jasmin.JasminTestBase;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
@@ -37,7 +38,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public FieldReadsJasminTest(TestParameters parameters) {
@@ -99,7 +100,7 @@
     if (parameters.isDexRuntime()) {
       testForD8()
           .addProgramClassFileData(classes)
-          .setMinApi(parameters.getRuntime())
+          .setMinApi(parameters.getApiLevel())
           .compile()
           .inspect(inspector -> ensureNoFieldsRead(inspector, clazz.name, false));
     }
@@ -107,7 +108,7 @@
     testForR8(parameters.getBackend())
         .addProgramClassFileData(classes)
         .addKeepRules("-keep class * { <methods>; }")
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(inspector -> ensureNoFieldsRead(inspector, clazz.name, true));
   }
@@ -167,20 +168,21 @@
     testForR8(parameters.getBackend())
         .addProgramClassFileData(app.buildClasses())
         .addKeepRules("-keep class * { <methods>; }")
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
-        .inspect(inspector -> {
-          FieldSubject fld = inspector.clazz(fieldHolder.name).uniqueFieldWithName(fieldName);
-          assertThat(fld, isRenamed());
+        .inspect(
+            inspector -> {
+              FieldSubject fld = inspector.clazz(fieldHolder.name).uniqueFieldWithName(fieldName);
+              assertThat(fld, isRenamed());
 
-          ClassSubject classSubject = inspector.clazz(clazz.name);
-          assertThat(classSubject, isPresent());
-          MethodSubject methodSubject = classSubject.uniqueMethodWithName(method.name);
-          assertThat(methodSubject, isPresent());
-          Iterator<InstructionSubject> it =
-              methodSubject.iterateInstructions(InstructionSubject::isFieldAccess);
-          assertFalse(it.hasNext());
-        });
+              ClassSubject classSubject = inspector.clazz(clazz.name);
+              assertThat(classSubject, isPresent());
+              MethodSubject methodSubject = classSubject.uniqueMethodWithName(method.name);
+              assertThat(methodSubject, isPresent());
+              Iterator<InstructionSubject> it =
+                  methodSubject.iterateInstructions(InstructionSubject::isFieldAccess);
+              assertFalse(it.hasNext());
+            });
   }
 
   @Test
@@ -205,7 +207,27 @@
         "  invokestatic Empty/foo(L" + CLS + ";)V",
         "  return");
 
-    ensureFieldExistsAndReadOnlyOnce(builder, empty, foo, empty, "aField");
+    inspect(
+        builder,
+        inspector ->
+            ensureFieldExistsAndReadOnlyOnce(
+                inspector, empty.name, foo.name, empty, "aField", false),
+        inspector -> {
+          ClassSubject emptyClassSubject = inspector.clazz(CLS);
+          assertThat(emptyClassSubject, isPresent());
+          assertTrue(emptyClassSubject.allFields().isEmpty());
+
+          MethodSubject fooMethodSubject = emptyClassSubject.uniqueMethodWithName("foo");
+          assertThat(fooMethodSubject, isPresent());
+          assertTrue(
+              fooMethodSubject
+                  .streamInstructions()
+                  .filter(InstructionSubject::isInvoke)
+                  .anyMatch(
+                      invoke ->
+                          invoke.getMethod().toSourceString().contains("requireNonNull")
+                              || invoke.getMethod().toSourceString().contains("getClass")));
+        });
   }
 
   @Test
@@ -275,6 +297,29 @@
     ensureFieldExistsAndReadOnlyOnce(builder, main, mainMethod, main, "sField");
   }
 
+  private void inspect(
+      JasminBuilder app,
+      ThrowingConsumer<CodeInspector, RuntimeException> d8Inspector,
+      ThrowingConsumer<CodeInspector, RuntimeException> r8Inspector)
+      throws Exception {
+    List<byte[]> classes = app.buildClasses();
+
+    if (parameters.isDexRuntime()) {
+      testForD8()
+          .addProgramClassFileData(classes)
+          .setMinApi(parameters.getApiLevel())
+          .compile()
+          .inspect(d8Inspector);
+    }
+
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(classes)
+        .addKeepRules("-keep class * { <methods>; }")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(r8Inspector);
+  }
+
   private void ensureFieldExistsAndReadOnlyOnce(
       JasminBuilder app,
       ClassBuilder clazz,
@@ -282,24 +327,14 @@
       ClassBuilder fieldHolder,
       String fieldName)
       throws Exception {
-    List<byte[]> classes = app.buildClasses();
-
-    if (parameters.isDexRuntime()) {
-      testForD8()
-          .addProgramClassFileData(classes)
-          .setMinApi(parameters.getRuntime())
-          .compile()
-          .inspect(inspector -> ensureFieldExistsAndReadOnlyOnce(
-              inspector, clazz.name, method.name, fieldHolder, fieldName, false));
-    }
-
-    testForR8(parameters.getBackend())
-        .addProgramClassFileData(classes)
-        .addKeepRules("-keep class * { <methods>; }")
-        .setMinApi(parameters.getRuntime())
-        .compile()
-        .inspect(inspector -> ensureFieldExistsAndReadOnlyOnce(
-            inspector, clazz.name, method.name, fieldHolder, fieldName, true));
+    inspect(
+        app,
+        inspector ->
+            ensureFieldExistsAndReadOnlyOnce(
+                inspector, clazz.name, method.name, fieldHolder, fieldName, false),
+        inspector ->
+            ensureFieldExistsAndReadOnlyOnce(
+                inspector, clazz.name, method.name, fieldHolder, fieldName, true));
   }
 
   private void ensureFieldExistsAndReadOnlyOnce(
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInInterfaceMarkingTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInInterfaceMarkingTest.java
new file mode 100644
index 0000000..bff8192
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInInterfaceMarkingTest.java
@@ -0,0 +1,111 @@
+// 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 org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import java.util.AbstractList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LibraryMethodOverrideInInterfaceMarkingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public LibraryMethodOverrideInInterfaceMarkingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(LibraryMethodOverrideInInterfaceMarkingTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(
+            options -> options.testing.enqueuerInspector = this::verifyLibraryOverrideInformation)
+        .enableMergeAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("true", "true");
+  }
+
+  private void verifyLibraryOverrideInformation(AppInfoWithLiveness appInfo, Enqueuer.Mode mode) {
+    DexItemFactory dexItemFactory = appInfo.dexItemFactory();
+    verifyIsEmptyMarkedAsOverridingLibraryMethod(
+        appInfo, dexItemFactory.createType(descriptor(A.class)));
+    verifyIsEmptyMarkedAsOverridingLibraryMethod(
+        appInfo, dexItemFactory.createType(descriptor(I.class)));
+  }
+
+  private void verifyIsEmptyMarkedAsOverridingLibraryMethod(
+      AppInfoWithLiveness appInfo, DexType type) {
+    DexProgramClass clazz = appInfo.definitionFor(type).asProgramClass();
+    DexEncodedMethod method =
+        clazz.lookupVirtualMethod(m -> m.method.name.toString().equals("isEmpty"));
+    assertTrue(method.isLibraryMethodOverride().isTrue());
+  }
+
+  static class TestClass {
+
+    static void onA(A a) {
+      System.out.println(a.isEmpty());
+    }
+
+    static void onI(I i) {
+      System.out.println(i.isEmpty());
+    }
+
+    public static void main(String[] args) {
+      Object object = args.length == 42 ? null : new B();
+      onA((A) object);
+      onI((I) object);
+    }
+  }
+
+  @NeverMerge
+  abstract static class A extends AbstractList<Object> {
+
+    @Override
+    public boolean isEmpty() {
+      return true;
+    }
+
+    @Override
+    public int size() {
+      return 0;
+    }
+
+    @Override
+    public A get(int index) {
+      return null;
+    }
+  }
+
+  @NeverMerge
+  interface I {
+
+    boolean isEmpty();
+  }
+
+  @NeverMerge
+  static class B extends A implements I {
+    // Intentionally empty.
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java
new file mode 100644
index 0000000..e3925ca
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java
@@ -0,0 +1,110 @@
+// 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 org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.Enqueuer.Mode;
+import java.util.Iterator;
+import java.util.Spliterator;
+import java.util.function.Consumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LibraryMethodOverrideInLambdaMarkingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public LibraryMethodOverrideInLambdaMarkingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(LibraryMethodOverrideInLambdaMarkingTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(
+            options -> options.testing.enqueuerInspector = this::verifyLibraryOverrideInformation)
+        .enableMergeAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("null", "null");
+  }
+
+  private void verifyLibraryOverrideInformation(AppInfoWithLiveness appInfo, Mode mode) {
+    DexItemFactory dexItemFactory = appInfo.dexItemFactory();
+    verifyIteratorMethodMarkedAsOverridingLibraryMethod(
+        appInfo, dexItemFactory.createType(descriptor(I.class)));
+    verifyIteratorMethodMarkedAsOverridingLibraryMethod(
+        appInfo, dexItemFactory.createType(descriptor(J.class)));
+  }
+
+  private void verifyIteratorMethodMarkedAsOverridingLibraryMethod(
+      AppInfoWithLiveness appInfo, DexType type) {
+    DexProgramClass clazz = appInfo.definitionFor(type).asProgramClass();
+    DexEncodedMethod method =
+        clazz.lookupVirtualMethod(m -> m.method.name.toString().equals("iterator"));
+    // TODO(b/149976493): Mark library overrides from lambda instances.
+    assertTrue(method.isLibraryMethodOverride().isFalse());
+  }
+
+  static class TestClass {
+
+    public static void onI(I i) {
+      System.out.println(i.iterator());
+    }
+
+    public static void onJ(J j) {
+      System.out.println(j.iterator());
+    }
+
+    public static void main(String[] args) {
+      Object f = ((I & J) () -> null);
+      onI((I) f);
+      onJ((J) f);
+    }
+  }
+
+  @FunctionalInterface
+  @NeverMerge
+  interface I {
+
+    Iterator<Object> iterator();
+  }
+
+  @FunctionalInterface
+  @NeverMerge
+  interface J extends Iterable<Object> {
+
+    @Override
+    Iterator<Object> iterator();
+
+    @Override
+    default void forEach(Consumer<? super Object> action) {
+      // Intentionally empty.
+    }
+
+    @Override
+    default Spliterator<Object> spliterator() {
+      return null;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideMarkingTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideMarkingTest.java
new file mode 100644
index 0000000..4afc009
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideMarkingTest.java
@@ -0,0 +1,90 @@
+// 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LibraryMethodOverrideMarkingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public LibraryMethodOverrideMarkingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(LibraryMethodOverrideMarkingTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(
+            options -> options.testing.enqueuerInspector = this::verifyLibraryOverrideInformation)
+        .enableMergeAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile();
+  }
+
+  private void verifyLibraryOverrideInformation(AppInfoWithLiveness appInfo, Enqueuer.Mode mode) {
+    DexItemFactory dexItemFactory = appInfo.dexItemFactory();
+    verifySingleVirtualMethodMarkedAsOverridingLibraryMethod(
+        appInfo, dexItemFactory.createType(descriptor(A.class)));
+    verifySingleVirtualMethodMarkedAsOverridingLibraryMethod(
+        appInfo, dexItemFactory.createType(descriptor(B.class)));
+  }
+
+  private void verifySingleVirtualMethodMarkedAsOverridingLibraryMethod(
+      AppInfoWithLiveness appInfo, DexType type) {
+    DexProgramClass clazz = appInfo.definitionFor(type).asProgramClass();
+    assertEquals(1, clazz.virtualMethods().size());
+    DexEncodedMethod method = clazz.virtualMethods().get(0);
+    assertTrue(method.isLibraryMethodOverride().isTrue());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(new C());
+    }
+  }
+
+  @NeverMerge
+  static class A {
+
+    @Override
+    public String toString() {
+      return super.toString();
+    }
+  }
+
+  @NeverMerge
+  static class B extends A {
+
+    @Override
+    public String toString() {
+      return super.toString();
+    }
+  }
+
+  static class C extends B {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/B149729626.java b/src/test/java/com/android/tools/r8/shaking/annotations/B149729626.java
new file mode 100644
index 0000000..ab3c663
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/B149729626.java
@@ -0,0 +1,116 @@
+// 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.annotations;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class B149729626 extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public B149729626(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8WithKeepClassMembersRule() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(B149729626.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules(
+            "-keepclassmembers @" + Marker.class.getTypeName() + " class * {",
+            "  <init>(...);",
+            "}",
+            // TODO(b/149729626): Should not be required.
+            "-keep class " + Marked.class.getTypeName(),
+            // TODO(b/149729626): Should not be required.
+            "-keep class " + TestClass.class.getTypeName() + " { void makeMarkerLive(); }")
+        // TODO(b/149729626): Should not be required.
+        .addKeepRuntimeVisibleAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccess();
+  }
+
+  @Test
+  public void testR8WithIfRule() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(B149729626.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules(
+            "-if @" + Marker.class.getTypeName() + " class *",
+            "-keep class <1> {",
+            "  <init>(...);",
+            "}",
+            // TODO(b/149729626): Should not be required.
+            "-keep class " + TestClass.class.getTypeName() + " { void makeMarkerLive(); }")
+        // TODO(b/149729626): Should not be required.
+        .addKeepRuntimeVisibleAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccess();
+  }
+
+  @Test
+  public void testCompat() throws Exception {
+    testForR8Compat(parameters.getBackend())
+        .addInnerClasses(B149729626.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccess();
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject markedClassSubject = inspector.clazz(Marked.class);
+    assertThat(markedClassSubject, isPresent());
+    assertThat(markedClassSubject.init(), isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(Marked.class);
+    }
+
+    static void makeMarkerLive() {
+      System.out.println(Marker.class);
+    }
+  }
+
+  @Target(ElementType.TYPE)
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface Marker {}
+
+  @Marker
+  static class Marked {}
+}
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 eb3929c..baadc68 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -348,6 +348,18 @@
     void apply(int opcode, String owner, String name, String descriptor, boolean isInterface);
   }
 
+  /** Abstraction of the MethodVisitor.visitTypeInsn method with its continuation. */
+  @FunctionalInterface
+  public interface TypeInsnTransform {
+    void visitTypeInsn(int opcode, String type, TypeInsnTransformContinuation continuation);
+  }
+
+  /** Continuation for transforming a method. Will continue with the super visitor if called. */
+  @FunctionalInterface
+  public interface TypeInsnTransformContinuation {
+    void apply(int opcode, String type);
+  }
+
   public ClassFileTransformer transformMethodInsnInMethod(
       String methodName, MethodInsnTransform transform) {
     return addMethodTransformer(
@@ -365,6 +377,21 @@
         });
   }
 
+  public ClassFileTransformer transformTypeInsnInMethod(
+      String methodName, TypeInsnTransform transform) {
+    return addMethodTransformer(
+        new MethodTransformer() {
+          @Override
+          public void visitTypeInsn(int opcode, String type) {
+            if (getContext().method.getMethodName().equals(methodName)) {
+              transform.visitTypeInsn(opcode, type, super::visitTypeInsn);
+            } else {
+              super.visitTypeInsn(opcode, type);
+            }
+          }
+        });
+  }
+
   /** Abstraction of the MethodVisitor.visitLdcInsn method with its continuation. */
   @FunctionalInterface
   public interface LdcInsnTransform {
diff --git a/tools/r8_release.py b/tools/r8_release.py
index a9e66237..502666f 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -14,9 +14,7 @@
 import xml
 import xml.etree.ElementTree as et
 import zipfile
-
 import archive_desugar_jdk_libs
-import update_prebuilds_in_android
 import utils
 
 R8_DEV_BRANCH = '2.1'
@@ -31,6 +29,18 @@
 
 GITHUB_DESUGAR_JDK_LIBS = 'https://github.com/google/desugar_jdk_libs'
 
+def checkout_r8(temp, branch):
+  subprocess.check_call(['git', 'clone', utils.REPO_SOURCE, temp])
+  with utils.ChangedWorkingDirectory(temp):
+    subprocess.check_call([
+      'git',
+      'new-branch',
+      '--upstream',
+      'origin/%s' % branch,
+      'dev-release'])
+  return temp
+
+
 def prepare_release(args):
   if args.version:
     print "Cannot manually specify version when making a dev release."
@@ -40,15 +50,7 @@
     commithash = args.dev_release
 
     with utils.TempDir() as temp:
-      subprocess.check_call(['git', 'clone', utils.REPO_SOURCE, temp])
-      with utils.ChangedWorkingDirectory(temp):
-        subprocess.check_call([
-          'git',
-          'new-branch',
-          '--upstream',
-          'origin/%s' % R8_DEV_BRANCH,
-          'dev-release'])
-
+      with utils.ChangedWorkingDirectory(checkout_r8(temp, R8_DEV_BRANCH)):
         # Compute the current and new version on the branch.
         result = None
         for line in open(R8_VERSION_FILE, 'r'):
@@ -123,6 +125,7 @@
   maybe_check_call(args, [
     'git', 'push', 'origin', 'refs/tags/%s' % version])
 
+
 def version_change_diff(diff, old_version, new_version):
   invalid_line = None
   for line in diff.splitlines():
@@ -160,11 +163,12 @@
     return subprocess.check_call(cmd)
 
 
-def update_prebuilds(version, checkout):
-  update_prebuilds_in_android.main_download('', True, 'lib', checkout, version)
+def update_prebuilds(r8_checkout, version, checkout):
+  path = os.path.join(r8_checkout, 'tools', 'update_prebuilds_in_android.py')
+  subprocess.check_call([path, '--targets=lib', '--maps', '--version=' + version, checkout])
 
 
-def release_studio_or_aosp(path, options, git_message):
+def release_studio_or_aosp(r8_checkout, path, options, git_message):
   with utils.ChangedWorkingDirectory(path):
     if not options.use_existing_work_branch:
       subprocess.call(['repo', 'abandon', 'update-r8'])
@@ -177,7 +181,7 @@
       with utils.ChangedWorkingDirectory(prebuilts_r8):
         subprocess.check_call(['repo', 'start', 'update-r8'])
 
-    update_prebuilds(options.version, path)
+    update_prebuilds(r8_checkout, options.version, path)
 
     with utils.ChangedWorkingDirectory(prebuilts_r8):
       if not options.use_existing_work_branch:
@@ -213,7 +217,8 @@
 
 Test: TARGET_PRODUCT=aosp_arm64 m -j core-oj"""
                    % (args.version, args.version, args.version))
-    return release_studio_or_aosp(args.aosp, options, git_message)
+    return release_studio_or_aosp(
+      utils.REPO_ROOT, args.aosp, options, git_message)
 
   return release_aosp
 
@@ -236,6 +241,7 @@
 
 Built here: go/r8-releases/raw/%s/
 Test: ./gradlew check
+
 Bug: %s """ % (version, version, '\nBug: '.join(bugs))
 
 
@@ -249,10 +255,16 @@
     if options.dry_run:
       return 'DryRun: omitting studio release for %s' % options.version
 
-    git_message = (git_message_dev(options.version)
-                   if 'dev' in options.version
-                   else git_message_release(options.version, options.bug))
-    return release_studio_or_aosp(args.studio, options, git_message)
+    if 'dev' in options.version:
+      git_message = git_message_dev(options.version)
+      r8_checkout = utils.REPO_ROOT
+      return release_studio_or_aosp(
+        r8_checkout, args.studio, options, git_message)
+    else:
+      with utils.TempDir() as temp:
+        checkout_r8(temp, options.version[0:options.version.rindex('.')])
+        git_message = git_message_release(options.version, options.bug)
+        return release_studio_or_aosp(temp, args.studio, options, git_message)
 
   return release_studio