Merge commit 'b3e71c33f5f9f60c893ddcbc62a9c08f428dfccf' into dev-release
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index fda08a6..f66fff8 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -111,8 +111,9 @@
           "\n",
           Iterables.concat(
               Arrays.asList(
-                  "Usage: d8 [options] <input-files>",
+                  "Usage: d8 [options] [@<argfile>] <input-files>",
                   " where <input-files> are any combination of dex, class, zip, jar, or apk files",
+                  " and each <argfile> is a file containing additional arguments (one per line)",
                   " and options are:",
                   "  --debug                 # Compile with debugging information (default).",
                   "  --release               # Compile without debugging information.",
@@ -258,6 +259,8 @@
           builder.error(new StringDiagnostic("Unknown option: " + arg, origin));
           continue;
         }
+      } else if (arg.startsWith("@")) {
+        builder.error(new StringDiagnostic("Recursive @argfiles are not supported: ", origin));
       } else {
         builder.addProgramFiles(Paths.get(arg));
       }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 7db3d7c..75f862e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -35,7 +35,6 @@
 import com.android.tools.r8.ir.desugar.NestedPrivateMethodLense;
 import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
-import com.android.tools.r8.ir.optimize.EnumInfoMapCollector;
 import com.android.tools.r8.ir.optimize.MethodPoolCollection;
 import com.android.tools.r8.ir.optimize.NestReducer;
 import com.android.tools.r8.ir.optimize.SwitchMapCollector;
@@ -43,6 +42,7 @@
 import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.UninstantiatedTypeOptimizationGraphLense;
 import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector;
 import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector.UnusedArgumentsGraphLense;
+import com.android.tools.r8.ir.optimize.enums.EnumInfoMapCollector;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.kotlin.Kotlin;
@@ -308,7 +308,7 @@
 
         // Compute kotlin info before setting the roots and before
         // kotlin metadata annotation is removed.
-        computeKotlinInfoForProgramClasses(application, appView);
+        computeKotlinInfoForProgramClasses(application, appView, executorService);
 
         // Add synthesized -assumenosideeffects from min api if relevant.
         if (options.isGeneratingDex()) {
@@ -508,6 +508,8 @@
       // Collect switch maps and ordinals maps.
       if (options.enableEnumValueOptimization) {
         appViewWithLiveness.setAppInfo(new SwitchMapCollector(appViewWithLiveness).run());
+      }
+      if (options.enableEnumValueOptimization || options.enableEnumUnboxing) {
         appViewWithLiveness.setAppInfo(new EnumInfoMapCollector(appViewWithLiveness).run());
       }
 
@@ -725,7 +727,8 @@
         // Rewrite signature annotations for applications that are not minified.
         if (appView.appInfo().hasLiveness()) {
           // TODO(b/124726014): Rewrite signature annotations in lens rewriting instead of here?
-          new GenericSignatureRewriter(appView.withLiveness()).run(appView.appInfo().classes());
+          new GenericSignatureRewriter(appView.withLiveness())
+              .run(appView.appInfo().classes(), executorService);
         }
         namingLens = NamingLens.getIdentityLens();
       }
@@ -884,17 +887,23 @@
     throw new CompilationError("Discard checks failed.");
   }
 
-  private void computeKotlinInfoForProgramClasses(DexApplication application, AppView<?> appView) {
+  private void computeKotlinInfoForProgramClasses(
+      DexApplication application, AppView<?> appView, ExecutorService executorService)
+      throws ExecutionException{
     if (appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations) {
       return;
     }
     Kotlin kotlin = appView.dexItemFactory().kotlin;
     Reporter reporter = options.reporter;
-    for (DexProgramClass programClass : application.classes()) {
-      KotlinInfo kotlinInfo = kotlin.getKotlinInfo(programClass, reporter);
-      programClass.setKotlinInfo(kotlinInfo);
-      KotlinMemberInfo.markKotlinMemberInfo(programClass, kotlinInfo, reporter);
-    }
+    ThreadUtils.processItems(
+        application.classes(),
+        programClass -> {
+          KotlinInfo kotlinInfo = kotlin.getKotlinInfo(programClass, reporter);
+          programClass.setKotlinInfo(kotlinInfo);
+          KotlinMemberInfo.markKotlinMemberInfo(programClass, kotlinInfo, reporter);
+        },
+        executorService
+    );
   }
 
   private static boolean verifyNoJarApplicationReaders(List<DexProgramClass> classes) {
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index f1541bd..22ebb5e 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -52,8 +52,9 @@
           "\n",
           Iterables.concat(
               Arrays.asList(
-                  "Usage: r8 [options] <input-files>",
+                  "Usage: r8 [options] [@<argfile>] <input-files>",
                   " where <input-files> are any combination of dex, class, zip, jar, or apk files",
+                  " and each <argfile> is a file containing additional arguments (one per line)",
                   " and options are:",
                   "  --release               # Compile without debugging information (default).",
                   "  --debug                 # Compile with debugging information.",
@@ -223,6 +224,8 @@
           builder.error(new StringDiagnostic("Unknown option: " + arg, argsOrigin));
           continue;
         }
+      } else if (arg.startsWith("@")) {
+        builder.error(new StringDiagnostic("Recursive @argfiles are not supported: ", argsOrigin));
       } else {
         builder.addProgramFiles(Paths.get(arg));
       }
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index c69d8e0..df91f96 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.ClassKind;
-import com.android.tools.r8.graph.Descriptor;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -33,6 +32,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexMemberAnnotation;
 import com.android.tools.r8.graph.DexMemberAnnotation.DexFieldAnnotation;
 import com.android.tools.r8.graph.DexMemberAnnotation.DexMethodAnnotation;
@@ -570,21 +570,21 @@
     return new DexDebugInfo(start, parameters, events.toArray(DexDebugEvent.EMPTY_ARRAY));
   }
 
-  private static class MemberAnnotationIterator<S extends Descriptor<?, S>, T extends DexItem> {
+  private static class MemberAnnotationIterator<R extends DexMember<?, R>, T extends DexItem> {
 
     private int index = 0;
-    private final DexMemberAnnotation<S, T>[] annotations;
+    private final DexMemberAnnotation<R, T>[] annotations;
     private final Supplier<T> emptyValue;
 
-    private MemberAnnotationIterator(DexMemberAnnotation<S, T>[] annotations,
-        Supplier<T> emptyValue) {
+    private MemberAnnotationIterator(
+        DexMemberAnnotation<R, T>[] annotations, Supplier<T> emptyValue) {
       this.annotations = annotations;
       this.emptyValue = emptyValue;
     }
 
     // Get the annotation set for an item. This method assumes that it is always called with
     // an item that is higher in the sorting order than the last item.
-    T getNextFor(S item) {
+    T getNextFor(R item) {
       // TODO(ager): We could use the indices from the file to make this search faster using
       // compareTo instead of slowCompareTo. That would require us to assign indices during
       // reading. Those indices should be cleared after reading to make sure that we resort
@@ -1189,7 +1189,7 @@
     MethodHandleType type = MethodHandleType.getKind(dexReader.getUshort());
     dexReader.getUshort(); // unused
     int indexFieldOrMethod = dexReader.getUshort();
-    Descriptor<? extends DexItem, ? extends Descriptor<?, ?>> fieldOrMethod;
+    DexMember<? extends DexItem, ? extends DexMember<?, ?>> fieldOrMethod;
     switch (type) {
       case INSTANCE_GET:
       case INSTANCE_PUT:
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 53f65b6..8b0cd00 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.ByteBufferProvider;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.graph.Descriptor;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationDirectory;
 import com.android.tools.r8.graph.DexAnnotationElement;
@@ -29,6 +28,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItem;
+import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
@@ -577,10 +577,10 @@
     }
   }
 
-  private <S extends Descriptor<T, S>, T extends DexEncodedMember<S>> void writeMemberAnnotations(
-      List<T> items, ToIntFunction<T> getter) {
-    for (T item : items) {
-      dest.putInt(item.getKey().getOffset(mapping));
+  private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> void writeMemberAnnotations(
+      List<D> items, ToIntFunction<D> getter) {
+    for (D item : items) {
+      dest.putInt(item.toReference().getOffset(mapping));
       dest.putInt(getter.applyAsInt(item));
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index c709a31..a6a9471 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -150,6 +150,10 @@
     return visibilityOrdinal() == 1 || visibilityOrdinal() == 2;
   }
 
+  public boolean isPackagePrivate() {
+    return !isPublic() && !isPrivate() && !isProtected();
+  }
+
   public boolean isPublic() {
     return isSet(Constants.ACC_PUBLIC);
   }
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 29a5995..141f742 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -29,8 +29,8 @@
 
   private final DexApplication app;
   private final DexItemFactory dexItemFactory;
-  private final ConcurrentHashMap<DexType, Map<Descriptor<?, ?>, DexEncodedMember<?>>> definitions =
-      new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexType, Map<DexMember<?, ?>, DexEncodedMember<?, ?>>>
+      definitions = new ConcurrentHashMap<>();
   // For some optimizations, e.g. optimizing synthetic classes, we may need to resolve the current
   // class being optimized.
   final ConcurrentHashMap<DexType, DexProgramClass> synthesizedClasses = new ConcurrentHashMap<>();
@@ -101,12 +101,12 @@
     return Collections.unmodifiableCollection(synthesizedClasses.values());
   }
 
-  private Map<Descriptor<?, ?>, DexEncodedMember<?>> computeDefinitions(DexType type) {
-    Builder<Descriptor<?, ?>, DexEncodedMember<?>> builder = ImmutableMap.builder();
+  private Map<DexMember<?, ?>, DexEncodedMember<?, ?>> computeDefinitions(DexType type) {
+    Builder<DexMember<?, ?>, DexEncodedMember<?, ?>> builder = ImmutableMap.builder();
     DexClass clazz = definitionFor(type);
     if (clazz != null) {
-      clazz.forEachMethod(method -> builder.put(method.getKey(), method));
-      clazz.forEachField(field -> builder.put(field.getKey(), field));
+      clazz.forEachMethod(method -> builder.put(method.toReference(), method));
+      clazz.forEachField(field -> builder.put(field.toReference(), field));
     }
     return builder.build();
   }
@@ -186,14 +186,14 @@
     return (DexEncodedField) getDefinitions(field.holder).get(field);
   }
 
-  private Map<Descriptor<?, ?>, DexEncodedMember<?>> getDefinitions(DexType type) {
-    Map<Descriptor<?, ?>, DexEncodedMember<?>> typeDefinitions = definitions.get(type);
+  private Map<DexMember<?, ?>, DexEncodedMember<?, ?>> getDefinitions(DexType type) {
+    Map<DexMember<?, ?>, DexEncodedMember<?, ?>> typeDefinitions = definitions.get(type);
     if (typeDefinitions != null) {
       return typeDefinitions;
     }
 
     typeDefinitions = computeDefinitions(type);
-    Map<Descriptor<?, ?>, DexEncodedMember<?>> existing =
+    Map<DexMember<?, ?>, DexEncodedMember<?, ?>> existing =
         definitions.putIfAbsent(type, typeDefinitions);
     return existing != null ? existing : typeDefinitions;
   }
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 495c0b3..813ea84 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -7,6 +7,8 @@
 
 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;
@@ -20,11 +22,13 @@
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
-import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.function.Consumer;
 import java.util.function.Function;
 
-public class AppInfoWithSubtyping extends AppInfoWithClassHierarchy implements LiveSubTypeInfo {
+public class AppInfoWithSubtyping extends AppInfoWithClassHierarchy
+    implements InstantiatedSubTypeInfo {
 
   private static final int ROOT_LEVEL = 0;
   private static final int UNKNOWN_LEVEL = -1;
@@ -34,16 +38,23 @@
   private static final Set<DexType> NO_DIRECT_SUBTYPE = ImmutableSet.of();
 
   @Override
-  public LiveSubTypeResult getLiveSubTypes(DexType type) {
-    // TODO(b/139464956): Remove this when we start to have live type information in the enqueuer.
-    Set<DexProgramClass> programClasses = new HashSet<>();
-    for (DexType subtype : subtypes(type)) {
-      DexProgramClass subClass = definitionForProgramType(subtype);
-      if (subClass != null) {
-        programClasses.add(subClass);
+  public void forEachInstantiatedSubType(
+      DexType type,
+      Consumer<DexProgramClass> subTypeConsumer,
+      Consumer<DexCallSite> callSiteConsumer) {
+    WorkList<DexType> workList = new WorkList<>(EqualityTest.IDENTITY);
+    workList.addIfNotSeen(type);
+    workList.addIfNotSeen(allImmediateSubtypes(type));
+    while (workList.hasNext()) {
+      DexType subType = workList.next();
+      DexProgramClass clazz = definitionForProgramType(subType);
+      if (clazz != null) {
+        subTypeConsumer.accept(clazz);
       }
+      workList.addIfNotSeen(allImmediateSubtypes(subType));
     }
-    return new LiveSubTypeResult(programClasses, null);
+    // TODO(b/148769279): Change this when we have information about callsites.
+    callSiteConsumer.accept(null);
   }
 
   private static class TypeInfo {
@@ -71,7 +82,7 @@
 
     private void ensureDirectSubTypeSet() {
       if (directSubtypes == NO_DIRECT_SUBTYPE) {
-        directSubtypes = new TreeSet<>(DexType::slowCompareTo);
+        directSubtypes = new ConcurrentSkipListSet<>(DexType::slowCompareTo);
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 9f7b4cf..1f24d35 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
 import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
 import com.android.tools.r8.ir.optimize.library.LibraryMethodOptimizer;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
@@ -45,6 +46,8 @@
   private final InternalOptions options;
   private RootSet rootSet;
   private final AbstractValueFactory abstractValueFactory = new AbstractValueFactory();
+  private final InstanceFieldInitializationInfoFactory instanceFieldInitializationInfoFactory =
+      new InstanceFieldInitializationInfoFactory();
 
   // Desugared library prefix rewriter.
   public final PrefixRewritingMapper rewritePrefix;
@@ -125,6 +128,10 @@
     return abstractValueFactory;
   }
 
+  public InstanceFieldInitializationInfoFactory instanceFieldInitializationInfoFactory() {
+    return instanceFieldInitializationInfoFactory;
+  }
+
   public T appInfo() {
     return appInfo;
   }
@@ -149,6 +156,10 @@
     allCodeProcessed = true;
   }
 
+  public GraphLense clearCodeRewritings() {
+    return graphLense = graphLense.withCodeRewritingsApplied();
+  }
+
   public AppServices appServices() {
     return appServices;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
index 18f8fe6..a05883a 100644
--- a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
@@ -8,7 +8,6 @@
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import java.util.IdentityHashMap;
-import java.util.List;
 import java.util.Map;
 
 /**
@@ -29,7 +28,7 @@
       new IdentityHashMap<>();
 
   public AppliedGraphLens(
-      AppView<? extends AppInfoWithSubtyping> appView, List<DexProgramClass> classes) {
+      AppView<? extends AppInfoWithSubtyping> appView, Iterable<DexProgramClass> classes) {
     this.appView = appView;
 
     for (DexProgramClass clazz : classes) {
@@ -135,4 +134,9 @@
   public boolean isContextFreeForMethods() {
     return true;
   }
+
+  @Override
+  public boolean hasCodeRewritings() {
+    return false;
+  }
 }
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 cbc8303..5c8de46 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -469,7 +469,7 @@
       } else {
         continue;
       }
-      if (index >= 0) {
+      if (index >= 0 && indexToNumber.containsKey(index)) {
         registry.register(indexToNumber.get(index));
       }
     }
diff --git a/src/main/java/com/android/tools/r8/graph/Descriptor.java b/src/main/java/com/android/tools/r8/graph/Descriptor.java
deleted file mode 100644
index f0ba1b6..0000000
--- a/src/main/java/com/android/tools/r8/graph/Descriptor.java
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2016, 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;
-
-public abstract class Descriptor<T extends DexItem, S extends Descriptor<T,S>>
-    extends DexReference implements PresortedComparable<S> {
-
-  public abstract boolean match(S entry);
-
-  public abstract boolean match(T entry);
-
-  @Override
-  public boolean isDescriptor() {
-    return true;
-  }
-
-  @Override
-  public Descriptor asDescriptor() {
-    return this;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
index e616e9e..2d0667b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
@@ -107,9 +107,9 @@
     throw new Unreachable();
   }
 
-  private static <T extends PresortedComparable<T>> boolean isSorted(
-      List<? extends DexEncodedMember<T>> items) {
-    return isSorted(items, DexEncodedMember::getKey);
+  private static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> boolean isSorted(
+      List<D> items) {
+    return isSorted(items, DexEncodedMember::toReference);
   }
 
   private static <S, T extends Comparable<T>> boolean isSorted(
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 3e1a144..3601754 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -129,7 +129,7 @@
         Iterables.filter(Arrays.asList(staticFields), predicate::test));
   }
 
-  public Iterable<DexEncodedMember<?>> members() {
+  public Iterable<DexEncodedMember<?, ?>> members() {
     return Iterables.concat(fields(), methods());
   }
 
@@ -611,8 +611,9 @@
         && method.method.proto.parameters.values[0] != factory.objectArrayType;
   }
 
-  private <T extends DexItem, S extends Descriptor<T, S>> T lookupTarget(T[] items, S descriptor) {
-    for (T entry : items) {
+  private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> D lookupTarget(
+      D[] items, R descriptor) {
+    for (D entry : items) {
       if (descriptor.match(entry)) {
         return entry;
       }
@@ -773,11 +774,15 @@
   }
 
   public boolean classInitializationMayHaveSideEffects(AppView<?> appView) {
-    return classInitializationMayHaveSideEffects(appView, Predicates.alwaysFalse());
+    return classInitializationMayHaveSideEffects(
+        appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet());
   }
 
   public boolean classInitializationMayHaveSideEffects(
-      AppView<?> appView, Predicate<DexType> ignore) {
+      AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) {
+    if (!seen.add(type)) {
+      return false;
+    }
     if (ignore.test(type)) {
       return false;
     }
@@ -795,7 +800,7 @@
     if (defaultValuesForStaticFieldsMayTriggerAllocation()) {
       return true;
     }
-    return initializationOfParentTypesMayHaveSideEffects(appView, ignore);
+    return initializationOfParentTypesMayHaveSideEffects(appView, ignore, seen);
   }
 
   private boolean hasClassInitializerThatCannotBePostponed() {
@@ -820,17 +825,19 @@
   }
 
   public boolean initializationOfParentTypesMayHaveSideEffects(AppView<?> appView) {
-    return initializationOfParentTypesMayHaveSideEffects(appView, Predicates.alwaysFalse());
+    return initializationOfParentTypesMayHaveSideEffects(
+        appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet());
   }
 
   public boolean initializationOfParentTypesMayHaveSideEffects(
-      AppView<?> appView, Predicate<DexType> ignore) {
+      AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) {
     for (DexType iface : interfaces.values) {
-      if (iface.classInitializationMayHaveSideEffects(appView, ignore)) {
+      if (iface.classInitializationMayHaveSideEffects(appView, ignore, seen)) {
         return true;
       }
     }
-    if (superType != null && superType.classInitializationMayHaveSideEffects(appView, ignore)) {
+    if (superType != null
+        && superType.classInitializationMayHaveSideEffects(appView, ignore, seen)) {
       return true;
     }
     return false;
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
new file mode 100644
index 0000000..281b58a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.errors.Unreachable;
+
+public class DexClassAndMethod {
+
+  private final DexClass holder;
+  private final DexEncodedMethod method;
+
+  protected DexClassAndMethod(DexClass holder, DexEncodedMethod method) {
+    assert holder.type == method.method.holder;
+    this.holder = holder;
+    this.method = method;
+  }
+
+  public static DexClassAndMethod create(DexClass holder, DexEncodedMethod method) {
+    if (holder.isProgramClass()) {
+      return new ProgramMethod(holder.asProgramClass(), method);
+    } else {
+      return new DexClassAndMethod(holder, method);
+    }
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    throw new Unreachable("Unsupported attempt at comparing Class and DexClassAndMethod");
+  }
+
+  @Override
+  public int hashCode() {
+    throw new Unreachable("Unsupported attempt at computing the hashcode of DexClassAndMethod");
+  }
+
+  public DexClass getHolder() {
+    return holder;
+  }
+
+  public DexEncodedMethod getMethod() {
+    return method;
+  }
+
+  public boolean isProgramMethod() {
+    return false;
+  }
+
+  public ProgramMethod asProgramMethod() {
+    return null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinition.java b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
index dbf18c3..7f6796d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
@@ -51,7 +51,7 @@
     return false;
   }
 
-  public DexEncodedMember<?> asDexEncodedMember() {
+  public DexEncodedMember<?, ?> asDexEncodedMember() {
     return null;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 4f332f9..ce89a41 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -18,8 +18,9 @@
 import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
 import com.android.tools.r8.kotlin.KotlinMemberInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
 
-public class DexEncodedField extends DexEncodedMember<DexField> {
+public class DexEncodedField extends DexEncodedMember<DexEncodedField, DexField> {
   public static final DexEncodedField[] EMPTY_ARRAY = {};
 
   public final DexField field;
@@ -110,12 +111,7 @@
   }
 
   @Override
-  public DexField getKey() {
-    return field;
-  }
-
-  @Override
-  public DexReference toReference() {
+  public DexField toReference() {
     return field;
   }
 
@@ -222,7 +218,8 @@
         appView,
         // Types that are a super type of the current context are guaranteed to be initialized
         // already.
-        type -> appView.isSubtype(context, type).isTrue())) {
+        type -> appView.isSubtype(context, type).isTrue(),
+        Sets.newIdentityHashSet())) {
       // Ignore class initialization side-effects for dead proto extension fields to ensure that
       // we force replace these field reads by null.
       boolean ignore =
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 6987227..0e436d6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -3,13 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-public abstract class DexEncodedMember<T extends PresortedComparable<T>> extends DexDefinition {
+public abstract class DexEncodedMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+    extends DexDefinition {
 
   public DexEncodedMember(DexAnnotationSet annotations) {
     super(annotations);
   }
 
-  public abstract T getKey();
+  @Override
+  public abstract R toReference();
 
   @Override
   public boolean isDexEncodedMember() {
@@ -17,7 +19,7 @@
   }
 
   @Override
-  public DexEncodedMember<?> asDexEncodedMember() {
+  public DexEncodedMember<D, R> asDexEncodedMember() {
     return this;
   }
 
@@ -27,11 +29,11 @@
       return true;
     }
     return other.getClass() == getClass()
-        && ((DexEncodedMember<?>) other).getKey().equals(getKey());
+        && ((DexEncodedMember<?, ?>) other).toReference().equals(toReference());
   }
 
   @Override
   public final int hashCode() {
-    return getKey().hashCode();
+    return toReference().hashCode();
   }
 }
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 184e15e..b755c49 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -76,7 +76,7 @@
 import java.util.function.IntPredicate;
 import org.objectweb.asm.Opcodes;
 
-public class DexEncodedMethod extends DexEncodedMember<DexMethod> {
+public class DexEncodedMethod extends DexEncodedMember<DexEncodedMethod, DexMethod> {
 
   public static final String CONFIGURATION_DEBUGGING_PREFIX = "Shaking error: Missing method in ";
 
@@ -1163,14 +1163,7 @@
   }
 
   @Override
-  public DexMethod getKey() {
-    // Here, we can't check if the current instance of DexEncodedMethod is obsolete
-    // because itself can be used as a key while making mappings to avoid using obsolete instances.
-    return method;
-  }
-
-  @Override
-  public DexReference toReference() {
+  public DexMethod toReference() {
     checkIfObsolete();
     return method;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index dfd3203..02cb985 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -7,15 +7,13 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.naming.NamingLens;
 
-public class DexField extends Descriptor<DexEncodedField, DexField> implements
-    PresortedComparable<DexField> {
+public class DexField extends DexMember<DexEncodedField, DexField> {
 
-  public final DexType holder;
   public final DexType type;
   public final DexString name;
 
   DexField(DexType holder, DexType type, DexString name, boolean skipNameValidationForTesting) {
-    this.holder = holder;
+    super(holder);
     this.type = type;
     this.name = name;
     if (!skipNameValidationForTesting && !name.isValidFieldName()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index df2402f..8cb71bb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1539,7 +1539,7 @@
 
   public DexMethodHandle createMethodHandle(
       MethodHandleType type,
-      Descriptor<? extends DexItem, ? extends Descriptor<?, ?>> fieldOrMethod,
+      DexMember<? extends DexItem, ? extends DexMember<?, ?>> fieldOrMethod,
       boolean isInterface) {
     assert !sorted;
     DexMethodHandle methodHandle = new DexMethodHandle(type, fieldOrMethod, isInterface);
diff --git a/src/main/java/com/android/tools/r8/graph/DexMember.java b/src/main/java/com/android/tools/r8/graph/DexMember.java
new file mode 100644
index 0000000..96afd57
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2016, 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;
+
+public abstract class DexMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+    extends DexReference implements PresortedComparable<R> {
+
+  public final DexType holder;
+
+  public DexMember(DexType holder) {
+    this.holder = holder;
+  }
+
+  public abstract boolean match(R entry);
+
+  public abstract boolean match(D entry);
+
+  @Override
+  public boolean isDexMember() {
+    return true;
+  }
+
+  @Override
+  public DexMember<D, R> asDexMember() {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java
index 6cc2fb4..d9868a5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java
@@ -6,12 +6,12 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 
-public class DexMemberAnnotation<T extends Descriptor<?,?>, S extends DexItem> extends DexItem {
+public class DexMemberAnnotation<R extends DexMember<?, R>, S extends DexItem> extends DexItem {
 
-  public final T item;
+  public final R item;
   public final S annotations;
 
-  public DexMemberAnnotation(T item, S annotations) {
+  public DexMemberAnnotation(R item, S annotations) {
     this.item = item;
     this.annotations = annotations;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index a1e98f7..a29eb31 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -9,10 +9,8 @@
 import com.google.common.collect.Maps;
 import java.util.Map;
 
-public class DexMethod extends Descriptor<DexEncodedMethod, DexMethod>
-    implements PresortedComparable<DexMethod> {
+public class DexMethod extends DexMember<DexEncodedMethod, DexMethod> {
 
-  public final DexType holder;
   public final DexProto proto;
   public final DexString name;
 
@@ -20,7 +18,7 @@
   private Map<DexType, DexEncodedMethod> singleTargetCache;
 
   DexMethod(DexType holder, DexProto proto, DexString name, boolean skipNameValidationForTesting) {
-    this.holder = holder;
+    super(holder);
     this.proto = proto;
     this.name = name;
     if (!skipNameValidationForTesting && !name.isValidMethodName()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
index 336842f..fbe8a9f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
@@ -187,7 +187,7 @@
   public final MethodHandleType type;
 
   // Field or method that the method handle is targeting.
-  public final Descriptor<? extends DexItem, ? extends Descriptor<?,?>> fieldOrMethod;
+  public final DexMember<? extends DexItem, ? extends DexMember<?, ?>> fieldOrMethod;
 
   public final boolean isInterface;
 
@@ -204,7 +204,7 @@
 
   public DexMethodHandle(
       MethodHandleType type,
-      Descriptor<? extends DexItem, ? extends Descriptor<?, ?>> fieldOrMethod,
+      DexMember<? extends DexItem, ? extends DexMember<?, ?>> fieldOrMethod,
       boolean isInterface) {
     this.type = type;
     this.fieldOrMethod = fieldOrMethod;
@@ -214,7 +214,7 @@
 
   public DexMethodHandle(
       MethodHandleType type,
-      Descriptor<? extends DexItem, ? extends Descriptor<?, ?>> fieldOrMethod,
+      DexMember<? extends DexItem, ? extends DexMember<?, ?>> fieldOrMethod,
       boolean isInterface,
       DexMethod rewrittenTarget) {
     this.type = type;
@@ -226,7 +226,7 @@
   public static DexMethodHandle fromAsmHandle(
       Handle handle, JarApplicationReader application, DexType clazz) {
     MethodHandleType methodHandleType = MethodHandleType.fromAsmHandle(handle, application, clazz);
-    Descriptor<? extends DexItem, ? extends Descriptor<?, ?>> descriptor =
+    DexMember<? extends DexItem, ? extends DexMember<?, ?>> descriptor =
         methodHandleType.isFieldType()
             ? application.getField(handle.getOwner(), handle.getName(), handle.getDesc())
             : application.getMethod(handle.getOwner(), handle.getName(), handle.getDesc());
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 1ac7c58..78a59c2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -352,11 +352,11 @@
         && isSorted(instanceFields);
   }
 
-  private static <T extends DexEncodedMember<S>, S extends PresortedComparable<S>> boolean isSorted(
-      T[] items) {
+  private static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> boolean isSorted(
+      D[] items) {
     synchronized (items) {
-      T[] sorted = items.clone();
-      Arrays.sort(sorted, Comparator.comparing(DexEncodedMember::getKey));
+      D[] sorted = items.clone();
+      Arrays.sort(sorted, Comparator.comparing(DexEncodedMember::toReference));
       return Arrays.equals(items, sorted);
     }
   }
@@ -441,7 +441,7 @@
    * entire scope.
    */
   public boolean hasReachabilitySensitiveAnnotation(DexItemFactory factory) {
-    for (DexEncodedMember<?> member : members()) {
+    for (DexEncodedMember<?, ?> member : members()) {
       for (DexAnnotation annotation : member.annotations().annotations) {
         if (annotation.annotation.type == factory.annotationReachabilitySensitive) {
           return true;
diff --git a/src/main/java/com/android/tools/r8/graph/DexReference.java b/src/main/java/com/android/tools/r8/graph/DexReference.java
index d51810e..42286db 100644
--- a/src/main/java/com/android/tools/r8/graph/DexReference.java
+++ b/src/main/java/com/android/tools/r8/graph/DexReference.java
@@ -21,11 +21,11 @@
     return null;
   }
 
-  public boolean isDescriptor() {
+  public boolean isDexMember() {
     return false;
   }
 
-  public Descriptor asDescriptor() {
+  public DexMember<?, ?> asDexMember() {
     return null;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 9a8a73c..08d4d61 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -25,9 +25,11 @@
 import com.android.tools.r8.utils.Pair;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.function.Predicate;
 
 public class DexType extends DexReference implements PresortedComparable<DexType> {
@@ -60,23 +62,26 @@
   }
 
   public boolean classInitializationMayHaveSideEffects(AppView<?> appView) {
-    return classInitializationMayHaveSideEffects(appView, Predicates.alwaysFalse());
+    return classInitializationMayHaveSideEffects(
+        appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet());
   }
 
   public boolean classInitializationMayHaveSideEffects(
-      AppView<?> appView, Predicate<DexType> ignore) {
+      AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) {
     DexClass clazz = appView.definitionFor(this);
-    return clazz == null || clazz.classInitializationMayHaveSideEffects(appView, ignore);
+    return clazz == null || clazz.classInitializationMayHaveSideEffects(appView, ignore, seen);
   }
 
   public boolean initializationOfParentTypesMayHaveSideEffects(AppView<?> appView) {
-    return initializationOfParentTypesMayHaveSideEffects(appView, Predicates.alwaysFalse());
+    return initializationOfParentTypesMayHaveSideEffects(
+        appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet());
   }
 
   public boolean initializationOfParentTypesMayHaveSideEffects(
-      AppView<?> appView, Predicate<DexType> ignore) {
+      AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) {
     DexClass clazz = appView.definitionFor(this);
-    return clazz == null || clazz.initializationOfParentTypesMayHaveSideEffects(appView, ignore);
+    return clazz == null
+        || clazz.initializationOfParentTypesMayHaveSideEffects(appView, ignore, seen);
   }
 
   public boolean isAlwaysNull(AppView<AppInfoWithLiveness> appView) {
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index f1d1a34..e01f0ee 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -223,10 +223,21 @@
     return IdentityGraphLense.getInstance();
   }
 
+  public boolean hasCodeRewritings() {
+    return true;
+  }
+
   public final boolean isIdentityLense() {
     return this == getIdentityLense();
   }
 
+  public GraphLense withCodeRewritingsApplied() {
+    if (hasCodeRewritings()) {
+      return new ClearCodeRewritingGraphLens(this);
+    }
+    return this;
+  }
+
   public <T extends DexDefinition> boolean assertDefinitionsNotModified(Iterable<T> definitions) {
     for (DexDefinition definition : definitions) {
       DexReference reference = definition.toReference();
@@ -455,6 +466,51 @@
     public boolean isContextFreeForMethods() {
       return true;
     }
+
+    @Override
+    public boolean hasCodeRewritings() {
+      return false;
+    }
+  }
+
+  // This lens clears all code rewriting (lookup methods mimics identity lens behavior) but still
+  // relies on the previous lens for names (getRenamed/Original methods).
+  public static class ClearCodeRewritingGraphLens extends IdentityGraphLense {
+    private final GraphLense previous;
+
+    public ClearCodeRewritingGraphLens(GraphLense previous) {
+      this.previous = previous;
+    }
+
+    @Override
+    public DexType getOriginalType(DexType type) {
+      return previous.getOriginalType(type);
+    }
+
+    @Override
+    public DexField getOriginalFieldSignature(DexField field) {
+      return previous.getOriginalFieldSignature(field);
+    }
+
+    @Override
+    public DexMethod getOriginalMethodSignature(DexMethod method) {
+      return previous.getOriginalMethodSignature(method);
+    }
+
+    @Override
+    public DexField getRenamedFieldSignature(DexField originalField) {
+      return previous.getRenamedFieldSignature(originalField);
+    }
+
+    @Override
+    public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+      return previous.getRenamedMethodSignature(originalMethod);
+    }
+
+    @Override
+    public DexType lookupType(DexType type) {
+      return previous.lookupType(type);
+    }
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/graph/InstantiatedSubTypeInfo.java b/src/main/java/com/android/tools/r8/graph/InstantiatedSubTypeInfo.java
new file mode 100644
index 0000000..e4942bb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/InstantiatedSubTypeInfo.java
@@ -0,0 +1,16 @@
+// 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 java.util.function.Consumer;
+
+@FunctionalInterface
+public interface InstantiatedSubTypeInfo {
+
+  void forEachInstantiatedSubType(
+      DexType type,
+      Consumer<DexProgramClass> subTypeConsumer,
+      Consumer<DexCallSite> callSiteConsumer);
+}
diff --git a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
index 27f4c9c..8b5977f 100644
--- a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
@@ -104,7 +104,7 @@
 
   public DexMethodHandle getMethodHandle(
       MethodHandleType type,
-      Descriptor<? extends DexItem, ? extends Descriptor<?, ?>> fieldOrMethod,
+      DexMember<? extends DexItem, ? extends DexMember<?, ?>> fieldOrMethod,
       boolean isInterface) {
     return options.itemFactory.createMethodHandle(type, fieldOrMethod, isInterface);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/LiveSubTypeInfo.java b/src/main/java/com/android/tools/r8/graph/LiveSubTypeInfo.java
deleted file mode 100644
index f812d7e..0000000
--- a/src/main/java/com/android/tools/r8/graph/LiveSubTypeInfo.java
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.graph;
-
-import java.util.Set;
-
-@FunctionalInterface
-public interface LiveSubTypeInfo {
-
-  LiveSubTypeResult getLiveSubTypes(DexType type);
-
-  class LiveSubTypeResult {
-
-    private final Set<DexProgramClass> programClasses;
-    private final Set<DexCallSite> callSites;
-
-    public LiveSubTypeResult(Set<DexProgramClass> programClasses, Set<DexCallSite> callSites) {
-      // TODO(b/149006127): Enable when we no longer rely on callSites == null.
-      this.programClasses = programClasses;
-      this.callSites = callSites;
-    }
-
-    public Set<DexProgramClass> getProgramClasses() {
-      return programClasses;
-    }
-
-    public Set<DexCallSite> getCallSites() {
-      return callSites;
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index fa13c74..af2bdd4 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -33,6 +33,11 @@
     return new Builder(trackAllocationSites, reporter);
   }
 
+  public void markNoLongerInstantiated(DexProgramClass clazz) {
+    classesWithAllocationSiteTracking.remove(clazz);
+    classesWithoutAllocationSiteTracking.remove(clazz);
+  }
+
   @Override
   public void forEachClassWithKnownAllocationSites(
       BiConsumer<DexProgramClass, Set<DexEncodedMethod>> consumer) {
diff --git a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
index 81e6d56..075c187 100644
--- a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
+++ b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
@@ -10,9 +10,9 @@
 
 public interface PresortedComparable<T> extends Presorted, Comparable<T> {
 
-  static <T extends PresortedComparable<T>> boolean isSorted(
-      List<? extends DexEncodedMember<T>> items) {
-    return isSorted(items, DexEncodedMember::getKey);
+  static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> boolean isSorted(
+      List<? extends DexEncodedMember<D, R>> items) {
+    return isSorted(items, DexEncodedMember::toReference);
   }
 
   static <S, T extends Comparable<T>> boolean isSorted(S[] items, Function<S, T> getter) {
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 1e33e66..4924d0c 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -3,26 +3,27 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.errors.Unreachable;
-
 /** Type representing a method definition in the programs compilation unit and its holder. */
-public class ProgramMethod {
-  public final DexProgramClass holder;
-  public final DexEncodedMethod method;
+public final class ProgramMethod extends DexClassAndMethod {
 
-  public ProgramMethod(DexProgramClass holder,  DexEncodedMethod method) {
-    assert holder.type == method.method.holder;
-    this.holder = holder;
-    this.method = method;
+  public ProgramMethod(DexProgramClass holder, DexEncodedMethod method) {
+    super(holder, method);
   }
 
   @Override
-  public boolean equals(Object obj) {
-    throw new Unreachable("Unsupported attempt at comparing ProgramMethod");
+  public boolean isProgramMethod() {
+    return true;
   }
 
   @Override
-  public int hashCode() {
-    throw new Unreachable("Unsupported attempt at computing the hashcode of ProgramMethod");
+  public ProgramMethod asProgramMethod() {
+    return this;
+  }
+
+  @Override
+  public DexProgramClass getHolder() {
+    DexClass holder = super.getHolder();
+    assert holder.isProgramClass();
+    return holder.asProgramClass();
   }
 }
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 c2bd3a4..feffac7 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -4,10 +4,8 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.graph.LiveSubTypeInfo.LiveSubTypeResult;
 import com.android.tools.r8.graph.LookupResult.LookupResultSuccess.LookupResultCollectionState;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Sets;
 import java.util.Collection;
 import java.util.Collections;
@@ -79,12 +77,18 @@
       DexProgramClass context, AppInfoWithClassHierarchy appInfo);
 
   public abstract LookupResult lookupVirtualDispatchTargets(
-      AppView<?> appView, LiveSubTypeInfo liveSubTypes);
+      DexProgramClass context,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      InstantiatedSubTypeInfo instantiatedInfo);
 
-  public final LookupResult lookupVirtualDispatchTargets(AppView<AppInfoWithLiveness> appView) {
-    return lookupVirtualDispatchTargets(appView, appView.appInfo());
+  public final LookupResult lookupVirtualDispatchTargets(
+      DexProgramClass context, AppView<AppInfoWithLiveness> appView) {
+    return lookupVirtualDispatchTargets(context, appView, appView.appInfo());
   }
 
+  public abstract DexClassAndMethod lookupVirtualDispatchTarget(
+      DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView);
+
   /** Result for a resolution that succeeds with a known declaration/definition. */
   public static class SingleResolutionResult extends ResolutionResult {
     private final DexClass initialResolutionHolder;
@@ -102,6 +106,8 @@
       this.resolvedHolder = resolvedHolder;
       this.resolvedMethod = resolvedMethod;
       this.initialResolutionHolder = initialResolutionHolder;
+      assert !resolvedMethod.isPrivateMethod()
+          || initialResolutionHolder.type == resolvedMethod.method.holder;
     }
 
     public DexClass getResolvedHolder() {
@@ -331,132 +337,186 @@
 
     @Override
     public LookupResult lookupVirtualDispatchTargets(
-        AppView<?> appView, LiveSubTypeInfo liveSubTypeInfo) {
-      return initialResolutionHolder.isInterface()
-          ? lookupInterfaceTargets(appView, liveSubTypeInfo)
-          : lookupVirtualTargets(appView, liveSubTypeInfo);
-    }
-
-    // TODO(b/140204899): Leverage refined receiver type if available.
-    private LookupResult lookupVirtualTargets(AppView<?> appView, LiveSubTypeInfo liveSubTypeInfo) {
-      assert !initialResolutionHolder.isInterface();
+        DexProgramClass context,
+        AppView<? extends AppInfoWithClassHierarchy> appView,
+        InstantiatedSubTypeInfo instantiatedInfo) {
+      // Check that the initial resolution holder is accessible from the context.
+      if (context != null && !isAccessibleFrom(context, appView.appInfo())) {
+        return LookupResult.createFailedResult();
+      }
       if (resolvedMethod.isPrivateMethod()) {
         // If the resolved reference is private there is no dispatch.
         // This is assuming that the method is accessible, which implies self/nest access.
-        return LookupResult.createResult(
-            Collections.singleton(resolvedMethod), LookupResultCollectionState.Complete);
-      }
-      assert resolvedMethod.isNonPrivateVirtualMethod();
-      // First add the target for receiver type method.type.
-      Set<DexEncodedMethod> result = SetUtils.newIdentityHashSet(resolvedMethod);
-      // Add all matching targets from the subclass hierarchy.
-      DexMethod method = resolvedMethod.method;
-      // TODO(b/140204899): Instead of subtypes of holder, we could iterate subtypes of refined
-      //   receiver type if available.
-      for (DexProgramClass programClass :
-          liveSubTypeInfo.getLiveSubTypes(method.holder).getProgramClasses()) {
-        if (!programClass.isInterface()) {
-          ResolutionResult methods = appView.appInfo().resolveMethodOnClass(programClass, method);
-          DexEncodedMethod target = methods.getSingleTarget();
-          if (target != null && target.isNonPrivateVirtualMethod()) {
-            result.add(target);
-          }
-        }
-      }
-      return LookupResult.createResult(result, LookupResultCollectionState.Complete);
-    }
-
-    // TODO(b/140204899): Leverage refined receiver type if available.
-    private LookupResult lookupInterfaceTargets(
-        AppView<?> appView, LiveSubTypeInfo liveSubTypeInfo) {
-      assert initialResolutionHolder.isInterface();
-      if (resolvedMethod.isPrivateMethod()) {
-        // If the resolved reference is private there is no dispatch.
-        // This is assuming that the method is accessible, which implies self/nest access.
-        assert resolvedMethod.hasCode();
+        // Only include if the target has code or is native.
         return LookupResult.createResult(
             Collections.singleton(resolvedMethod), LookupResultCollectionState.Complete);
       }
       assert resolvedMethod.isNonPrivateVirtualMethod();
       Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
-      // Add default interface methods to the list of targets.
-      //
-      // This helps to make sure we take into account synthesized lambda classes
-      // that we are not aware of. Like in the following example, we know that all
-      // classes, XX in this case, override B::bar(), but there are also synthesized
-      // classes for lambda which don't, so we still need default method to be live.
-      //
-      //   public static void main(String[] args) {
-      //     X x = () -> {};
-      //     x.bar();
-      //   }
-      //
-      //   interface X {
-      //     void foo();
-      //     default void bar() { }
-      //   }
-      //
-      //   class XX implements X {
-      //     public void foo() { }
-      //     public void bar() { }
-      //   }
-      //
-      addIfDefaultMethodWithLambdaInstantiations(liveSubTypeInfo, resolvedMethod, result);
-
-      DexMethod method = resolvedMethod.method;
-      Consumer<DexEncodedMethod> addIfNotAbstract =
-          m -> {
-            if (!m.accessFlags.isAbstract()) {
-              result.add(m);
+      instantiatedInfo.forEachInstantiatedSubType(
+          resolvedHolder.type,
+          subClass -> {
+            DexClassAndMethod dexClassAndMethod = lookupVirtualDispatchTarget(subClass, appView);
+            if (dexClassAndMethod != null) {
+              addVirtualDispatchTarget(
+                  dexClassAndMethod.getMethod(), resolvedHolder.isInterface(), result);
             }
-          };
-      // Default methods are looked up when looking at a specific subtype that does not override
-      // them. Otherwise, we would look up default methods that are actually never used. However, we
-      // have to add bridge methods, otherwise we can remove a bridge that will be used.
-      Consumer<DexEncodedMethod> addIfNotAbstractAndBridge =
-          m -> {
-            if (!m.accessFlags.isAbstract() && m.accessFlags.isBridge()) {
-              result.add(m);
-            }
-          };
-
-      // TODO(b/140204899): Instead of subtypes of holder, we could iterate subtypes of refined
-      //   receiver type if available.
-      for (DexProgramClass clazz :
-          liveSubTypeInfo.getLiveSubTypes(method.holder).getProgramClasses()) {
-        if (clazz.isInterface()) {
-          ResolutionResult targetMethods =
-              appView.appInfo().resolveMethodOnInterface(clazz, method);
-          if (targetMethods.isSingleResolution()) {
-            // Sub-interfaces can have default implementations that override the resolved method.
-            // Therefore we have to add default methods in sub interfaces.
-            DexEncodedMethod singleTarget = targetMethods.getSingleTarget();
-            addIfDefaultMethodWithLambdaInstantiations(liveSubTypeInfo, singleTarget, result);
-            addIfNotAbstractAndBridge.accept(singleTarget);
-          }
-        } else {
-          ResolutionResult targetMethods = appView.appInfo().resolveMethodOnClass(clazz, method);
-          if (targetMethods.isSingleResolution()) {
-            addIfNotAbstract.accept(targetMethods.getSingleTarget());
-          }
-        }
-      }
+          },
+          dexCallSite -> {
+            // TODO(b/148769279): We need to look at the call site to see if it overrides
+            //   the resolved method or not.
+          });
       return LookupResult.createResult(result, LookupResultCollectionState.Complete);
     }
 
-    private void addIfDefaultMethodWithLambdaInstantiations(
-        LiveSubTypeInfo liveSubTypesInfo, DexEncodedMethod method, Set<DexEncodedMethod> result) {
-      if (method == null) {
-        return;
+    private static void addVirtualDispatchTarget(
+        DexEncodedMethod target, boolean holderIsInterface, Set<DexEncodedMethod> result) {
+      assert !target.isPrivateMethod();
+      if (holderIsInterface) {
+        // Add default interface methods to the list of targets.
+        //
+        // This helps to make sure we take into account synthesized lambda classes
+        // that we are not aware of. Like in the following example, we know that all
+        // classes, XX in this case, override B::bar(), but there are also synthesized
+        // classes for lambda which don't, so we still need default method to be live.
+        //
+        //   public static void main(String[] args) {
+        //     X x = () -> {};
+        //     x.bar();
+        //   }
+        //
+        //   interface X {
+        //     void foo();
+        //     default void bar() { }
+        //   }
+        //
+        //   class XX implements X {
+        //     public void foo() { }
+        //     public void bar() { }
+        //   }
+        //
+        if (target.isDefaultMethod()) {
+          result.add(target);
+        }
+        // Default methods are looked up when looking at a specific subtype that does not override
+        // them. Otherwise, we would look up default methods that are actually never used.
+        // However, we have to add bridge methods, otherwise we can remove a bridge that will be
+        // used.
+        if (!target.accessFlags.isAbstract() && target.accessFlags.isBridge()) {
+          result.add(target);
+        }
+      } else {
+        result.add(target);
       }
-      if (method.hasCode()) {
-        LiveSubTypeResult liveSubTypes = liveSubTypesInfo.getLiveSubTypes(method.method.holder);
-        // TODO(b/148769279): The below is basically if (true), but should be changed when we
-        //  have live sub type information.
-        if (liveSubTypes.getCallSites() == null || liveSubTypes.getCallSites() != null) {
-          result.add(method);
+    }
+
+    /**
+     * This implements the logic for the actual method selection for a virtual target, according to
+     * https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokevirtual where
+     * we have an object ref on the stack.
+     */
+    @Override
+    public DexClassAndMethod lookupVirtualDispatchTarget(
+        DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView) {
+      // TODO(b/148591377): Enable this assertion.
+      // The dynamic type cannot be an interface.
+      // assert !dynamicInstance.isInterface();
+      boolean allowPackageBlocked = resolvedMethod.accessFlags.isPackagePrivate();
+      DexClass current = dynamicInstance;
+      DexEncodedMethod overrideTarget = resolvedMethod;
+      while (current != null) {
+        DexEncodedMethod candidate = lookupOverrideCandidate(overrideTarget, current);
+        if (candidate == DexEncodedMethod.SENTINEL && allowPackageBlocked) {
+          overrideTarget = findWideningOverride(resolvedMethod, current, appView);
+          allowPackageBlocked = false;
+          continue;
+        }
+        if (candidate == null || candidate == DexEncodedMethod.SENTINEL) {
+          // We cannot find a target above the resolved method.
+          if (current.type == overrideTarget.method.holder) {
+            return null;
+          }
+          current = current.superType == null ? null : appView.definitionFor(current.superType);
+          continue;
+        }
+        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);
+    }
+
+    private DexEncodedMethod lookupMaximallySpecificDispatchTarget(
+        DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView) {
+      ResolutionResult maximallySpecificResult =
+          appView.appInfo().resolveMaximallySpecificMethods(dynamicInstance, resolvedMethod.method);
+      if (maximallySpecificResult.isSingleResolution()) {
+        return maximallySpecificResult.asSingleResolution().resolvedMethod;
+      }
+      return null;
+    }
+
+    /**
+     * C contains a declaration for an instance method m that overrides (§5.4.5) the resolved
+     * method, then m is the method to be invoked. If the candidate is not a valid override, we
+     * return sentinel to indicate that we have to search for a method that is widening access
+     * inside the package.
+     */
+    private static DexEncodedMethod lookupOverrideCandidate(
+        DexEncodedMethod method, DexClass clazz) {
+      DexEncodedMethod candidate = clazz.lookupVirtualMethod(method.method);
+      assert candidate == null || !candidate.isPrivateMethod();
+      if (candidate != null) {
+        return isOverriding(method, candidate) ? candidate : DexEncodedMethod.SENTINEL;
+      }
+      return null;
+    }
+
+    private static DexEncodedMethod findWideningOverride(
+        DexEncodedMethod resolvedMethod,
+        DexClass clazz,
+        AppView<? extends AppInfoWithClassHierarchy> appView) {
+      // Otherwise, lookup to first override that is distinct from resolvedMethod.
+      assert resolvedMethod.accessFlags.isPackagePrivate();
+      while (clazz.superType != null) {
+        clazz = appView.definitionFor(clazz.superType);
+        if (clazz == null) {
+          return resolvedMethod;
+        }
+        DexEncodedMethod otherOverride = clazz.lookupVirtualMethod(resolvedMethod.method);
+        if (otherOverride != null
+            && isOverriding(resolvedMethod, otherOverride)
+            && (otherOverride.accessFlags.isPublic() || otherOverride.accessFlags.isProtected())) {
+          assert resolvedMethod != otherOverride;
+          return otherOverride;
         }
       }
+      return resolvedMethod;
+    }
+
+    /**
+     * Implementation of method overriding according to the jvm specification
+     * https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.5
+     *
+     * <p>The implementation assumes that the holder of the candidate is a subtype of the holder of
+     * the resolved method. It also assumes that resolvedMethod is the actual method to find a
+     * lookup for (that is, it is either mA or m').
+     */
+    private static boolean isOverriding(
+        DexEncodedMethod resolvedMethod, DexEncodedMethod candidate) {
+      assert resolvedMethod.method.match(candidate.method);
+      assert !candidate.isPrivateMethod();
+      if (resolvedMethod.accessFlags.isPublic() || resolvedMethod.accessFlags.isProtected()) {
+        return true;
+      }
+      // For package private methods, a valid override has to be inside the package.
+      assert resolvedMethod.accessFlags.isPackagePrivate();
+      return resolvedMethod.method.holder.isSamePackage(candidate.method.holder);
     }
   }
 
@@ -488,9 +548,17 @@
 
     @Override
     public LookupResult lookupVirtualDispatchTargets(
-        AppView<?> appView, LiveSubTypeInfo liveSubTypeInfo) {
+        DexProgramClass context,
+        AppView<? extends AppInfoWithClassHierarchy> appView,
+        InstantiatedSubTypeInfo instantiatedInfo) {
       return LookupResult.getIncompleteEmptyResult();
     }
+
+    @Override
+    public DexClassAndMethod lookupVirtualDispatchTarget(
+        DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView) {
+      return null;
+    }
   }
 
   /** Singleton result for the special case resolving the array clone() method. */
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
index 5c22878..3beb9ba 100644
--- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
+++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
@@ -8,11 +8,9 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.IteratorUtils;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import it.unimi.dsi.fastutil.ints.IntBidirectionalIterator;
 import java.util.function.Consumer;
 
 public class RewrittenPrototypeDescription {
@@ -21,15 +19,9 @@
 
     public static class Builder {
 
-      private int argumentIndex = -1;
       private boolean isAlwaysNull = false;
       private DexType type = null;
 
-      public Builder setArgumentIndex(int argumentIndex) {
-        this.argumentIndex = argumentIndex;
-        return this;
-      }
-
       public Builder setIsAlwaysNull() {
         this.isAlwaysNull = true;
         return this;
@@ -41,18 +33,15 @@
       }
 
       public RemovedArgumentInfo build() {
-        assert argumentIndex >= 0;
         assert type != null;
-        return new RemovedArgumentInfo(argumentIndex, isAlwaysNull, type);
+        return new RemovedArgumentInfo(isAlwaysNull, type);
       }
     }
 
-    private final int argumentIndex;
     private final boolean isAlwaysNull;
     private final DexType type;
 
-    private RemovedArgumentInfo(int argumentIndex, boolean isAlwaysNull, DexType type) {
-      this.argumentIndex = argumentIndex;
+    private RemovedArgumentInfo(boolean isAlwaysNull, DexType type) {
       this.isAlwaysNull = isAlwaysNull;
       this.type = type;
     }
@@ -61,10 +50,6 @@
       return new Builder();
     }
 
-    public int getArgumentIndex() {
-      return argumentIndex;
-    }
-
     public DexType getType() {
       return type;
     }
@@ -76,61 +61,40 @@
     public boolean isNeverUsed() {
       return !isAlwaysNull;
     }
-
-    RemovedArgumentInfo withArgumentIndex(int argumentIndex) {
-      return this.argumentIndex != argumentIndex
-          ? new RemovedArgumentInfo(argumentIndex, isAlwaysNull, type)
-          : this;
-    }
   }
 
-  public static class RemovedArgumentsInfo {
+  public static class RemovedArgumentInfoCollection {
 
-    private static final RemovedArgumentsInfo empty = new RemovedArgumentsInfo(null);
+    private static final RemovedArgumentInfoCollection EMPTY = new RemovedArgumentInfoCollection();
 
-    private final List<RemovedArgumentInfo> removedArguments;
+    private final Int2ReferenceSortedMap<RemovedArgumentInfo> removedArguments;
 
-    public RemovedArgumentsInfo(List<RemovedArgumentInfo> removedArguments) {
-      assert verifyRemovedArguments(removedArguments);
+    // Specific constructor for empty.
+    private RemovedArgumentInfoCollection() {
+      this.removedArguments = new Int2ReferenceLinkedOpenHashMap<>();
+    }
+
+    private RemovedArgumentInfoCollection(
+        Int2ReferenceSortedMap<RemovedArgumentInfo> removedArguments) {
+      assert removedArguments != null : "should use empty.";
+      assert !removedArguments.isEmpty() : "should use empty.";
       this.removedArguments = removedArguments;
     }
 
-    private static boolean verifyRemovedArguments(List<RemovedArgumentInfo> removedArguments) {
-      if (removedArguments != null && !removedArguments.isEmpty()) {
-        // Check that list is sorted by argument indices.
-        int lastArgumentIndex = removedArguments.get(0).getArgumentIndex();
-        for (int i = 1; i < removedArguments.size(); ++i) {
-          int currentArgumentIndex = removedArguments.get(i).getArgumentIndex();
-          assert lastArgumentIndex < currentArgumentIndex;
-          lastArgumentIndex = currentArgumentIndex;
-        }
-      }
-      return true;
+    public static RemovedArgumentInfoCollection empty() {
+      return EMPTY;
     }
 
-    public static RemovedArgumentsInfo empty() {
-      return empty;
-    }
-
-    public ListIterator<RemovedArgumentInfo> iterator() {
-      return removedArguments == null
-          ? Collections.emptyListIterator()
-          : removedArguments.listIterator();
+    public RemovedArgumentInfo getArgumentInfo(int argIndex) {
+      return removedArguments.get(argIndex);
     }
 
     public boolean hasRemovedArguments() {
-      return removedArguments != null && !removedArguments.isEmpty();
+      return !removedArguments.isEmpty();
     }
 
     public boolean isArgumentRemoved(int argumentIndex) {
-      if (removedArguments != null) {
-        for (RemovedArgumentInfo info : removedArguments) {
-          if (info.getArgumentIndex() == argumentIndex) {
-            return true;
-          }
-        }
-      }
-      return false;
+      return removedArguments.containsKey(argumentIndex);
     }
 
     public DexType[] rewriteParameters(DexEncodedMethod encodedMethod) {
@@ -156,8 +120,7 @@
       return removedArguments != null ? removedArguments.size() : 0;
     }
 
-    public RemovedArgumentsInfo combine(RemovedArgumentsInfo info) {
-      assert info != null;
+    public RemovedArgumentInfoCollection combine(RemovedArgumentInfoCollection info) {
       if (hasRemovedArguments()) {
         if (!info.hasRemovedArguments()) {
           return this;
@@ -166,19 +129,32 @@
         return info;
       }
 
-      List<RemovedArgumentInfo> newRemovedArguments = new LinkedList<>(removedArguments);
-      ListIterator<RemovedArgumentInfo> iterator = newRemovedArguments.listIterator();
+      Int2ReferenceSortedMap<RemovedArgumentInfo> newRemovedArguments =
+          new Int2ReferenceLinkedOpenHashMap<>();
+      newRemovedArguments.putAll(removedArguments);
+      IntBidirectionalIterator iterator = removedArguments.keySet().iterator();
       int offset = 0;
-      for (RemovedArgumentInfo pending : info.removedArguments) {
-        RemovedArgumentInfo next = IteratorUtils.peekNext(iterator);
-        while (next != null && next.getArgumentIndex() <= pending.getArgumentIndex() + offset) {
-          iterator.next();
-          next = IteratorUtils.peekNext(iterator);
+      for (int pendingArgIndex : info.removedArguments.keySet()) {
+        int nextArgindex = peekNextOrMax(iterator);
+        while (nextArgindex <= pendingArgIndex + offset) {
+          iterator.nextInt();
+          nextArgindex = peekNextOrMax(iterator);
           offset++;
         }
-        iterator.add(pending.withArgumentIndex(pending.getArgumentIndex() + offset));
+        assert !newRemovedArguments.containsKey(pendingArgIndex + offset);
+        newRemovedArguments.put(
+            pendingArgIndex + offset, info.removedArguments.get(pendingArgIndex));
       }
-      return new RemovedArgumentsInfo(newRemovedArguments);
+      return new RemovedArgumentInfoCollection(newRemovedArguments);
+    }
+
+    static int peekNextOrMax(IntBidirectionalIterator iterator) {
+      if (iterator.hasNext()) {
+        int i = iterator.nextInt();
+        iterator.previousInt();
+        return i;
+      }
+      return Integer.MAX_VALUE;
     }
 
     public Consumer<DexEncodedMethod.Builder> createParameterAnnotationsRemover(
@@ -192,22 +168,46 @@
       }
       return null;
     }
+
+    public static Builder builder() {
+      return new Builder();
+    }
+
+    public static class Builder {
+      private Int2ReferenceSortedMap<RemovedArgumentInfo> removedArguments;
+
+      public Builder addRemovedArgument(int argIndex, RemovedArgumentInfo argInfo) {
+        if (removedArguments == null) {
+          removedArguments = new Int2ReferenceLinkedOpenHashMap<>();
+        }
+        assert !removedArguments.containsKey(argIndex);
+        removedArguments.put(argIndex, argInfo);
+        return this;
+      }
+
+      public RemovedArgumentInfoCollection build() {
+        if (removedArguments == null || removedArguments.isEmpty()) {
+          return EMPTY;
+        }
+        return new RemovedArgumentInfoCollection(removedArguments);
+      }
+    }
   }
 
   private static final RewrittenPrototypeDescription none = new RewrittenPrototypeDescription();
 
   private final boolean hasBeenChangedToReturnVoid;
   private final boolean extraNullParameter;
-  private final RemovedArgumentsInfo removedArgumentsInfo;
+  private final RemovedArgumentInfoCollection removedArgumentsInfo;
 
   private RewrittenPrototypeDescription() {
-    this(false, false, RemovedArgumentsInfo.empty());
+    this(false, false, RemovedArgumentInfoCollection.empty());
   }
 
   private RewrittenPrototypeDescription(
       boolean hasBeenChangedToReturnVoid,
       boolean extraNullParameter,
-      RemovedArgumentsInfo removedArgumentsInfo) {
+      RemovedArgumentInfoCollection removedArgumentsInfo) {
     assert removedArgumentsInfo != null;
     this.extraNullParameter = extraNullParameter;
     this.hasBeenChangedToReturnVoid = hasBeenChangedToReturnVoid;
@@ -215,7 +215,7 @@
   }
 
   public static RewrittenPrototypeDescription createForUninstantiatedTypes(
-      boolean hasBeenChangedToReturnVoid, RemovedArgumentsInfo removedArgumentsInfo) {
+      boolean hasBeenChangedToReturnVoid, RemovedArgumentInfoCollection removedArgumentsInfo) {
     return new RewrittenPrototypeDescription(
         hasBeenChangedToReturnVoid, false, removedArgumentsInfo);
   }
@@ -227,7 +227,7 @@
   public boolean isEmpty() {
     return !extraNullParameter
         && !hasBeenChangedToReturnVoid
-        && !getRemovedArgumentsInfo().hasRemovedArguments();
+        && !getRemovedArgumentInfoCollection().hasRemovedArguments();
   }
 
   public boolean hasExtraNullParameter() {
@@ -238,7 +238,7 @@
     return hasBeenChangedToReturnVoid;
   }
 
-  public RemovedArgumentsInfo getRemovedArgumentsInfo() {
+  public RemovedArgumentInfoCollection getRemovedArgumentInfoCollection() {
     return removedArgumentsInfo;
   }
 
@@ -276,7 +276,7 @@
         : this;
   }
 
-  public RewrittenPrototypeDescription withRemovedArguments(RemovedArgumentsInfo other) {
+  public RewrittenPrototypeDescription withRemovedArguments(RemovedArgumentInfoCollection other) {
     return new RewrittenPrototypeDescription(
         hasBeenChangedToReturnVoid, extraNullParameter, removedArgumentsInfo.combine(other));
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
index b463c75..1e8bb52 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
@@ -4,11 +4,15 @@
 
 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.DexProgramClass;
 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.NewInstance;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
@@ -56,20 +60,33 @@
 
   public void recordFieldAccesses(
       IRCode code, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
-    if (!code.metadata().mayHaveFieldInstruction() || !methodProcessor.isPrimary()) {
+    if (!methodProcessor.isPrimary()) {
       return;
     }
 
-    Iterable<FieldInstruction> fieldInstructions =
-        code.instructions(Instruction::isFieldInstruction);
-    for (FieldInstruction fieldInstruction : fieldInstructions) {
-      DexEncodedField encodedField = appView.appInfo().resolveField(fieldInstruction.getField());
-      if (encodedField != null && encodedField.isProgramField(appView)) {
-        if (fieldAssignmentTracker != null) {
-          fieldAssignmentTracker.recordFieldAccess(fieldInstruction, encodedField, code.method);
+    if (!code.metadata().mayHaveFieldInstruction() && !code.metadata().mayHaveNewInstance()) {
+      return;
+    }
+
+    for (Instruction instruction : code.instructions()) {
+      if (instruction.isFieldInstruction()) {
+        FieldInstruction fieldInstruction = instruction.asFieldInstruction();
+        DexEncodedField encodedField = appView.appInfo().resolveField(fieldInstruction.getField());
+        if (encodedField != null && encodedField.isProgramField(appView)) {
+          if (fieldAssignmentTracker != null) {
+            fieldAssignmentTracker.recordFieldAccess(fieldInstruction, encodedField, code.method);
+          }
+          if (fieldBitAccessAnalysis != null) {
+            fieldBitAccessAnalysis.recordFieldAccess(fieldInstruction, encodedField, feedback);
+          }
         }
-        if (fieldBitAccessAnalysis != null) {
-          fieldBitAccessAnalysis.recordFieldAccess(fieldInstruction, encodedField, feedback);
+      } else if (instruction.isNewInstance()) {
+        NewInstance newInstance = instruction.asNewInstance();
+        DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(newInstance.clazz));
+        if (clazz != null) {
+          if (fieldAssignmentTracker != null) {
+            fieldAssignmentTracker.recordAllocationSite(newInstance, clazz, code.method);
+          }
         }
       }
     }
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 0b0b542..0bef6b1 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
@@ -7,13 +7,22 @@
 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.FieldAccessInfoCollection;
+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.UnknownValue;
 import com.android.tools.r8.ir.code.FieldInstruction;
+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.FieldOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldArgumentInitializationInfo;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
@@ -21,9 +30,11 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.IdentityHashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 
 public class FieldAssignmentTracker {
@@ -35,12 +46,52 @@
   // processed when a field no longer has any incoming edges.
   private final FieldAccessGraph fieldAccessGraph;
 
+  // An object allocation graph with edges from methods to the classes they instantiate. Edges are
+  // removed from the graph as we process methods, such that we can conclude that all allocation
+  // sites have been seen when a class no longer has any incoming edges.
+  private final ObjectAllocationGraph objectAllocationGraph;
+
   // The set of fields that may store a non-zero value.
   private final Set<DexEncodedField> nonZeroFields = Sets.newConcurrentHashSet();
 
+  private final Map<DexProgramClass, Map<DexEncodedField, AbstractValue>>
+      abstractInstanceFieldValues = new ConcurrentHashMap<>();
+
   FieldAssignmentTracker(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
     this.fieldAccessGraph = new FieldAccessGraph(appView);
+    this.objectAllocationGraph = new ObjectAllocationGraph(appView);
+    initializeAbstractInstanceFieldValues();
+  }
+
+  /**
+   * For each class with known allocation sites, adds a mapping from clazz -> instance field ->
+   * bottom.
+   *
+   * <p>If an entry (clazz, instance field) is missing in {@link #abstractInstanceFieldValues}, it
+   * is interpreted as if we known nothing about the value of the field.
+   */
+  private void initializeAbstractInstanceFieldValues() {
+    ObjectAllocationInfoCollection objectAllocationInfos =
+        appView.appInfo().getObjectAllocationInfoCollection();
+    objectAllocationInfos.forEachClassWithKnownAllocationSites(
+        (clazz, allocationSites) -> {
+          if (appView.appInfo().isInstantiatedIndirectly(clazz)) {
+            // TODO(b/147652121): Handle classes that are instantiated indirectly.
+            return;
+          }
+          List<DexEncodedField> instanceFields = clazz.instanceFields();
+          if (instanceFields.isEmpty()) {
+            // No instance fields to track.
+            return;
+          }
+          Map<DexEncodedField, AbstractValue> abstractInstanceFieldValuesForClass =
+              new IdentityHashMap<>();
+          for (DexEncodedField field : clazz.instanceFields()) {
+            abstractInstanceFieldValuesForClass.put(field, BottomValue.getInstance());
+          }
+          abstractInstanceFieldValues.put(clazz, abstractInstanceFieldValuesForClass);
+        });
   }
 
   private boolean isAlwaysZero(DexEncodedField field) {
@@ -72,16 +123,98 @@
     }
   }
 
-  private void recordAllFieldPutsProcessed(DexEncodedField field, OptimizationFeedback feedback) {
+  void recordAllocationSite(
+      NewInstance instruction, DexProgramClass clazz, DexEncodedMethod context) {
+    Map<DexEncodedField, AbstractValue> abstractInstanceFieldValuesForClass =
+        abstractInstanceFieldValues.get(clazz);
+    if (abstractInstanceFieldValuesForClass == null) {
+      // We are not tracking the value of any of clazz' instance fields.
+      return;
+    }
+
+    InvokeDirect invoke = instruction.getUniqueConstructorInvoke(appView.dexItemFactory());
+    if (invoke == null) {
+      // We just lost track.
+      abstractInstanceFieldValues.remove(clazz);
+      return;
+    }
+
+    DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context.method.holder);
+    if (singleTarget == null) {
+      // We just lost track.
+      abstractInstanceFieldValues.remove(clazz);
+      return;
+    }
+
+    InstanceFieldInitializationInfoCollection initializationInfoCollection =
+        singleTarget.getOptimizationInfo().getInstanceInitializerInfo().fieldInitializationInfos();
+
+    // Synchronize on the lattice element (abstractInstanceFieldValuesForClass) in case we process
+    // another allocation site of `clazz` concurrently.
+    synchronized (abstractInstanceFieldValuesForClass) {
+      Iterator<Map.Entry<DexEncodedField, AbstractValue>> iterator =
+          abstractInstanceFieldValuesForClass.entrySet().iterator();
+      while (iterator.hasNext()) {
+        Map.Entry<DexEncodedField, AbstractValue> entry = iterator.next();
+        DexEncodedField field = entry.getKey();
+        InstanceFieldInitializationInfo initializationInfo =
+            initializationInfoCollection.get(field);
+        if (initializationInfo.isArgumentInitializationInfo()) {
+          InstanceFieldArgumentInitializationInfo argumentInitializationInfo =
+              initializationInfo.asArgumentInitializationInfo();
+          Value argument = invoke.arguments().get(argumentInitializationInfo.getArgumentIndex());
+          AbstractValue abstractValue =
+              argument.getAbstractValue(appView, context.method.holder).join(entry.getValue());
+          assert !abstractValue.isBottom();
+          if (!abstractValue.isUnknown()) {
+            entry.setValue(abstractValue);
+            continue;
+          }
+        } else {
+          assert initializationInfo.isUnknown();
+        }
+
+        // We just lost track for this field.
+        iterator.remove();
+      }
+    }
+  }
+
+  private void recordAllFieldPutsProcessed(
+      DexEncodedField field, OptimizationFeedbackDelayed feedback) {
     if (isAlwaysZero(field)) {
       feedback.recordFieldHasAbstractValue(
           field, appView, appView.abstractValueFactory().createSingleNumberValue(0));
     }
   }
 
-  public void waveDone(Collection<DexEncodedMethod> wave, OptimizationFeedback feedback) {
+  private void recordAllAllocationsSitesProcessed(
+      DexProgramClass clazz, OptimizationFeedbackDelayed feedback) {
+    Map<DexEncodedField, AbstractValue> abstractInstanceFieldValuesForClass =
+        abstractInstanceFieldValues.get(clazz);
+    if (abstractInstanceFieldValuesForClass == null) {
+      return;
+    }
+
+    for (DexEncodedField field : clazz.instanceFields()) {
+      AbstractValue abstractValue =
+          abstractInstanceFieldValuesForClass.getOrDefault(field, UnknownValue.getInstance());
+      if (abstractValue.isBottom()) {
+        feedback.modifyAppInfoWithLiveness(modifier -> modifier.removeInstantiatedType(clazz));
+        break;
+      }
+      if (abstractValue.isUnknown()) {
+        continue;
+      }
+      feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
+    }
+  }
+
+  public void waveDone(Collection<DexEncodedMethod> wave, OptimizationFeedbackDelayed feedback) {
     for (DexEncodedMethod method : wave) {
       fieldAccessGraph.markProcessed(method, field -> recordAllFieldPutsProcessed(field, feedback));
+      objectAllocationGraph.markProcessed(
+          method, clazz -> recordAllAllocationsSitesProcessed(clazz, feedback));
     }
   }
 
@@ -139,4 +272,42 @@
       }
     }
   }
+
+  static class ObjectAllocationGraph {
+
+    // The classes instantiated by each method.
+    private final Map<DexEncodedMethod, List<DexProgramClass>> objectAllocations =
+        new IdentityHashMap<>();
+
+    // The number of allocation sites that have not yet been processed per class.
+    private final Reference2IntMap<DexProgramClass> pendingObjectAllocations =
+        new Reference2IntOpenHashMap<>();
+
+    ObjectAllocationGraph(AppView<AppInfoWithLiveness> appView) {
+      ObjectAllocationInfoCollection objectAllocationInfos =
+          appView.appInfo().getObjectAllocationInfoCollection();
+      objectAllocationInfos.forEachClassWithKnownAllocationSites(
+          (clazz, contexts) -> {
+            for (DexEncodedMethod context : contexts) {
+              objectAllocations.computeIfAbsent(context, ignore -> new ArrayList<>()).add(clazz);
+            }
+            pendingObjectAllocations.put(clazz, contexts.size());
+          });
+    }
+
+    void markProcessed(
+        DexEncodedMethod method, Consumer<DexProgramClass> allAllocationsSitesSeenConsumer) {
+      List<DexProgramClass> allocationSitesInMethod = objectAllocations.get(method);
+      if (allocationSitesInMethod != null) {
+        for (DexProgramClass type : allocationSitesInMethod) {
+          int numberOfPendingAllocationSites = pendingObjectAllocations.removeInt(type) - 1;
+          if (numberOfPendingAllocationSites > 0) {
+            pendingObjectAllocations.put(type, numberOfPendingAllocationSites);
+          } else {
+            allAllocationsSitesSeenConsumer.accept(type);
+          }
+        }
+      }
+    }
+  }
 }
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 29dc41b..2289a28 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
@@ -275,7 +275,7 @@
     return true;
   }
 
-  private void updateFieldOptimizationInfo(DexEncodedField field, Value value) {
+  void updateFieldOptimizationInfo(DexEncodedField field, Value value) {
     // Abstract value.
     Value root = value.getAliasedValue();
     AbstractValue abstractValue = computeAbstractValue(root);
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 d636715..ecc93b7 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
@@ -5,15 +5,29 @@
 package com.android.tools.r8.ir.analysis.fieldvalueanalysis;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.info.field.EmptyInstanceFieldInitializationInfoCollection;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public class InstanceFieldValueAnalysis extends FieldValueAnalysis {
 
+  // Information about how this instance constructor initializes the fields on the newly created
+  // instance.
+  private final InstanceFieldInitializationInfoCollection.Builder builder =
+      InstanceFieldInitializationInfoCollection.builder();
+
+  private final InstanceFieldInitializationInfoFactory factory;
+
   private InstanceFieldValueAnalysis(
       AppView<AppInfoWithLiveness> appView,
       IRCode code,
@@ -21,9 +35,14 @@
       DexProgramClass clazz,
       DexEncodedMethod method) {
     super(appView, code, feedback, clazz, method);
+    factory = appView.instanceFieldInitializationInfoFactory();
   }
 
-  public static void run(
+  /**
+   * Returns information about how this instance constructor initializes the fields on the newly
+   * created instance.
+   */
+  public static InstanceFieldInitializationInfoCollection run(
       AppView<?> appView,
       IRCode code,
       ClassInitializerDefaultsResult classInitializerDefaultsResult,
@@ -34,16 +53,32 @@
     assert method.isInstanceInitializer();
     DexProgramClass clazz = appView.definitionFor(method.method.holder).asProgramClass();
     if (!appView.options().enableValuePropagationForInstanceFields) {
-      return;
+      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;
+      return EmptyInstanceFieldInitializationInfoCollection.getInstance();
     }
-    new InstanceFieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method)
-        .computeFieldOptimizationInfo(classInitializerDefaultsResult);
+    InstanceFieldValueAnalysis analysis =
+        new InstanceFieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method);
+    analysis.computeFieldOptimizationInfo(classInitializerDefaultsResult);
+    return analysis.builder.build();
+  }
+
+  @Override
+  void updateFieldOptimizationInfo(DexEncodedField field, Value value) {
+    super.updateFieldOptimizationInfo(field, value);
+
+    // If this instance field is initialized with an argument, 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()));
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index 37a8dc6..717df30 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -21,9 +21,9 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
-import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.inliner.FixedInliningReasonStrategy;
 import com.android.tools.r8.utils.PredicateSet;
@@ -91,7 +91,7 @@
   public void inlineCallsToDynamicMethod(
       DexEncodedMethod method,
       IRCode code,
-      CodeRewriter codeRewriter,
+      EnumValueOptimizer enumValueOptimizer,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
       Inliner inliner) {
@@ -103,8 +103,8 @@
 
     // Run the enum optimization to optimize all Enum.ordinal() invocations. This is required to
     // get rid of the enum switch in dynamicMethod().
-    if (appView.options().enableEnumValueOptimization) {
-      codeRewriter.rewriteConstantEnumMethodCalls(code);
+    if (enumValueOptimizer != null) {
+      enumValueOptimizer.rewriteConstantEnumMethodCalls(code);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index 007fd9f..5371255 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -8,6 +8,10 @@
 
   public abstract boolean isNonTrivial();
 
+  public boolean isBottom() {
+    return false;
+  }
+
   public boolean isZero() {
     return false;
   }
@@ -69,7 +73,13 @@
   }
 
   public AbstractValue join(AbstractValue other) {
-    if (this.equals(other)) {
+    if (isBottom() || other.isUnknown()) {
+      return other;
+    }
+    if (isUnknown() || other.isBottom()) {
+      return this;
+    }
+    if (equals(other)) {
       return this;
     }
     return UnknownValue.getInstance();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/BottomValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/BottomValue.java
new file mode 100644
index 0000000..ca376e7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/BottomValue.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+public class BottomValue extends AbstractValue {
+
+  private static final BottomValue INSTANCE = new BottomValue();
+
+  private BottomValue() {}
+
+  public static BottomValue getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public boolean isBottom() {
+    return true;
+  }
+
+  @Override
+  public boolean isNonTrivial() {
+    return true;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    return this == o;
+  }
+
+  @Override
+  public int hashCode() {
+    return System.identityHashCode(this);
+  }
+
+  @Override
+  public String toString() {
+    return "BottomValue";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index 93c39ec..1db2ba6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -21,12 +21,33 @@
  */
 public class Argument extends Instruction {
 
+  private final int index;
   private final boolean knownToBeBoolean;
 
-  public Argument(Value outValue, boolean knownToBeBoolean) {
+  public Argument(Value outValue, int index, boolean knownToBeBoolean) {
     super(outValue);
+    this.index = index;
     this.knownToBeBoolean = knownToBeBoolean;
-    outValue.markAsArgument();
+  }
+
+  public int getIndex() {
+    assert verifyIndex();
+    return index;
+  }
+
+  private boolean verifyIndex() {
+    int index = 0;
+    InstructionIterator instructionIterator = getBlock().iterator();
+    while (instructionIterator.hasNext()) {
+      Instruction instruction = instructionIterator.next();
+      assert instruction.isArgument();
+      if (instruction == this) {
+        assert index == this.index;
+        return true;
+      }
+      index++;
+    }
+    return false;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index f4bb790..584ea2c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -621,6 +621,10 @@
     return instructions.isEmpty();
   }
 
+  public int size() {
+    return instructions.size();
+  }
+
   public boolean isReturnBlock() {
     return exit().isReturn();
   }
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 39b6fb8..c8d6abc 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
@@ -21,6 +21,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
 import java.util.Collections;
 import java.util.List;
 
@@ -128,7 +129,8 @@
       if (field.holder.classInitializationMayHaveSideEffects(
           appView,
           // Types that are a super type of `context` are guaranteed to be initialized already.
-          type -> appView.isSubtype(context, type).isTrue())) {
+          type -> appView.isSubtype(context, type).isTrue(),
+          Sets.newIdentityHashSet())) {
         return AbstractError.top();
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java b/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java
index f7411d9..b074e44 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import java.util.function.Predicate;
 
 // Value that has a fixed register allocated. These are used for inserting spill, restore, and phi
 // moves in the spilling register allocator.
@@ -50,6 +51,11 @@
   }
 
   @Override
+  public boolean isDefinedByInstructionSatisfying(Predicate<Instruction> predicate) {
+    return false;
+  }
+
+  @Override
   public boolean isFixedRegisterValue() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 39c3d39..e620cb1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -826,6 +826,9 @@
       } else if (instruction.isMul()) {
         assert metadata.mayHaveMul() && metadata.mayHaveArithmeticOrLogicalBinop()
             : "IR metadata should indicate that code has a mul";
+      } else if (instruction.isNewInstance()) {
+        assert metadata.mayHaveNewInstance()
+            : "IR metadata should indicate that code has a new-instance";
       } else if (instruction.isRem()) {
         assert metadata.mayHaveRem() && metadata.mayHaveArithmeticOrLogicalBinop()
             : "IR metadata should indicate that code has a rem";
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
index c4d55d8..8db09e9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
@@ -206,6 +206,10 @@
     return get(Opcodes.MUL);
   }
 
+  public boolean mayHaveNewInstance() {
+    return get(Opcodes.NEW_INSTANCE);
+  }
+
   public boolean mayHaveOr() {
     return get(Opcodes.OR);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index bdba162..df0c10d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -88,7 +88,8 @@
         appView
             .appInfo()
             .resolveMethod(method.holder, method)
-            .lookupVirtualDispatchTargets(appView.withLiveness())
+            .lookupVirtualDispatchTargets(
+                appView.definitionForProgramType(invocationContext), appView.withLiveness())
             .asLookupResultSuccess();
     if (lookupResult == null) {
       return null;
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 6ba4a3a..3a7969a 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
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
 import java.util.List;
 import java.util.function.Predicate;
 
@@ -197,7 +198,8 @@
                     appView,
                     // Types that are a super type of `context` are guaranteed to be initialized
                     // already.
-                    type -> appView.isSubtype(context, type).isTrue());
+                    type -> appView.isSubtype(context, type).isTrue(),
+                    Sets.newIdentityHashSet());
       }
 
       return targetMayHaveSideEffects;
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index aed4b9e..cd88da6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.google.common.collect.Sets;
 
 public class NewInstance extends Instruction {
 
@@ -188,7 +189,8 @@
     if (definition.classInitializationMayHaveSideEffects(
         appView,
         // Types that are a super type of `context` are guaranteed to be initialized already.
-        type -> appView.isSubtype(context, type).isTrue())) {
+        type -> appView.isSubtype(context, type).isTrue(),
+        Sets.newIdentityHashSet())) {
       return true;
     }
 
@@ -228,7 +230,8 @@
       return clazz.classInitializationMayHaveSideEffects(
           appView,
           // Types that are a super type of `context` are guaranteed to be initialized already.
-          type -> appView.isSubtype(context, type).isTrue());
+          type -> appView.isSubtype(context, type).isTrue(),
+          Sets.newIdentityHashSet());
     } else {
       // In D8, this instruction may trigger class initialization if the holder of the field is
       // different from the current context.
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index eecf8c3..95c2608 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -30,6 +30,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.function.Predicate;
 
 public class Phi extends Value implements InstructionOrPhi {
 
@@ -62,6 +63,11 @@
   }
 
   @Override
+  public boolean isDefinedByInstructionSatisfying(Predicate<Instruction> predicate) {
+    return false;
+  }
+
+  @Override
   public boolean isPhi() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index f59eb4b..08285c5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.google.common.collect.Sets;
 import java.util.Set;
 
 public class StaticGet extends FieldInstruction {
@@ -236,7 +237,8 @@
       return holder.classInitializationMayHaveSideEffects(
           appView,
           // Types that are a super type of `context` are guaranteed to be initialized already.
-          type -> appView.isSubtype(context, type).isTrue());
+          type -> appView.isSubtype(context, type).isTrue(),
+          Sets.newIdentityHashSet());
     } else {
       // In D8, this instruction may trigger class initialization if the holder of the field is
       // different from the current context.
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index 63e5c10..c9bab8b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardMemberRule;
+import com.google.common.collect.Sets;
 
 public class StaticPut extends FieldInstruction {
 
@@ -235,7 +236,8 @@
       return holder.classInitializationMayHaveSideEffects(
           appView,
           // Types that are a super type of `context` are guaranteed to be initialized already.
-          type -> appView.isSubtype(context, type).isTrue());
+          type -> appView.isSubtype(context, type).isTrue(),
+          Sets.newIdentityHashSet());
     } else {
       // In D8, this instruction may trigger class initialization if the holder of the field is
       // different from the current context.
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 8e75d62..5566552 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -226,7 +226,6 @@
   private LiveIntervals liveIntervals;
   private int needsRegister = -1;
   private boolean isThis = false;
-  private boolean isArgument = false;
   private LongInterval valueRange;
   private DebugData debugData;
   protected TypeLatticeElement typeLattice;
@@ -893,6 +892,10 @@
     return root.definition.getAbstractValue(appView, context);
   }
 
+  public boolean isDefinedByInstructionSatisfying(Predicate<Instruction> predicate) {
+    return predicate.test(definition);
+  }
+
   public boolean isPhi() {
     return false;
   }
@@ -910,14 +913,8 @@
         || typeLattice.nullability().isDefinitelyNotNull();
   }
 
-  public void markAsArgument() {
-    assert !isArgument;
-    assert !isThis;
-    isArgument = true;
-  }
-
   public boolean isArgument() {
-    return isArgument;
+    return isDefinedByInstructionSatisfying(Instruction::isArgument);
   }
 
   public boolean onlyDependsOnArgument() {
@@ -941,21 +938,6 @@
     }
   }
 
-  public int computeArgumentPosition(IRCode code) {
-    assert isArgument;
-    int position = 0;
-    InstructionIterator instructionIterator = code.entryBlock().iterator();
-    while (instructionIterator.hasNext()) {
-      Instruction instruction = instructionIterator.next();
-      assert instruction.isArgument();
-      if (instruction.outValue() == this) {
-        return position;
-      }
-      position++;
-    }
-    throw new Unreachable();
-  }
-
   public boolean knownToBeBoolean() {
     return knownToBeBoolean(null);
   }
@@ -987,7 +969,7 @@
   }
 
   public void markAsThis() {
-    assert isArgument;
+    assert isArgument();
     assert !isThis;
     isThis = true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index 56462db..2bb3869 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -169,7 +169,7 @@
         ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
         DexEncodedMethod target = resolutionResult.getSingleTarget();
         if (target != null) {
-          processInvokeWithDynamicDispatch(type, target);
+          processInvokeWithDynamicDispatch(type, target, context.holder);
         }
       } else {
         DexEncodedMethod singleTarget =
@@ -190,7 +190,7 @@
     }
 
     private void processInvokeWithDynamicDispatch(
-        Invoke.Type type, DexEncodedMethod encodedTarget) {
+        Invoke.Type type, DexEncodedMethod encodedTarget, DexType context) {
       DexMethod target = encodedTarget.method;
       DexClass clazz = appView.definitionFor(target.holder);
       if (clazz == null) {
@@ -214,7 +214,8 @@
                     appView.appInfo().resolveMethod(method.holder, method, isInterface);
                 if (resolution.isVirtualTarget()) {
                   LookupResult lookupResult =
-                      resolution.lookupVirtualDispatchTargets(appView, appView.appInfo());
+                      resolution.lookupVirtualDispatchTargets(
+                          appView.definitionForProgramType(context), appView);
                   if (lookupResult.isLookupResultSuccess()) {
                     return lookupResult.asLookupResultSuccess().getMethodTargets();
                   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index a7c90fe..8f6ea1e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -385,7 +385,7 @@
     inPrelude = true;
     state.buildPrelude(canonicalPositions.getPreamblePosition());
     setLocalVariableLists();
-    DexSourceCode.buildArgumentsWithUnusedArgumentStubs(builder, 0, method, state::write);
+    builder.buildArgumentsWithUnusedArgumentStubs(0, method, state::write);
     // Add debug information for all locals at the initial label.
     Int2ReferenceMap<DebugLocalInfo> locals = getLocalVariables(0).locals;
     if (!locals.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 01dcf5d..2436ed1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -40,10 +40,6 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentsInfo;
-import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.CanonicalPositions;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Position;
@@ -51,7 +47,6 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.BiConsumer;
@@ -142,8 +137,7 @@
     if (code.incomingRegisterSize == 0) {
       return;
     }
-    buildArgumentsWithUnusedArgumentStubs(
-        builder,
+    builder.buildArgumentsWithUnusedArgumentStubs(
         code.registerSize - code.incomingRegisterSize,
         method,
         DexSourceCode::doNothingWriteConsumer);
@@ -153,62 +147,6 @@
     // Intentionally empty.
   }
 
-  public static void buildArgumentsWithUnusedArgumentStubs(
-      IRBuilder builder,
-      int register,
-      DexEncodedMethod method,
-      BiConsumer<Integer, DexType> writeCallback) {
-    RemovedArgumentsInfo removedArgumentsInfo =
-        builder.getPrototypeChanges().getRemovedArgumentsInfo();
-    ListIterator<RemovedArgumentInfo> removedArgumentIterator = removedArgumentsInfo.iterator();
-    RemovedArgumentInfo nextRemovedArgument =
-        removedArgumentIterator.hasNext() ? removedArgumentIterator.next() : null;
-
-    // Fill in the Argument instructions (incomingRegisterSize last registers) in the argument
-    // block.
-    int argumentIndex = 0;
-
-    if (!method.isStatic()) {
-      writeCallback.accept(register, method.method.holder);
-      builder.addThisArgument(register);
-      ++argumentIndex;
-      ++register;
-    }
-
-    int numberOfArguments =
-        method.method.proto.parameters.values.length
-            + removedArgumentsInfo.numberOfRemovedArguments()
-            + (method.isStatic() ? 0 : 1);
-
-    for (int usedArgumentIndex = 0; argumentIndex < numberOfArguments; ++argumentIndex) {
-      TypeLatticeElement type;
-      if (nextRemovedArgument != null && nextRemovedArgument.getArgumentIndex() == argumentIndex) {
-        writeCallback.accept(register, nextRemovedArgument.getType());
-        type =
-            TypeLatticeElement.fromDexType(
-                nextRemovedArgument.getType(), Nullability.maybeNull(), builder.appView);
-        builder.addConstantOrUnusedArgument(register, nextRemovedArgument);
-        nextRemovedArgument =
-            removedArgumentIterator.hasNext() ? removedArgumentIterator.next() : null;
-      } else {
-        DexType dexType = method.method.proto.parameters.values[usedArgumentIndex++];
-        writeCallback.accept(register, dexType);
-        type =
-            TypeLatticeElement.fromDexType(
-                dexType,
-                Nullability.maybeNull(),
-                builder.appView);
-        if (dexType.isBooleanType()) {
-          builder.addBooleanNonThisArgument(register);
-        } else {
-          builder.addNonThisArgument(register, type);
-        }
-      }
-      register += type.requiredRegisters();
-    }
-    builder.flushArgumentInstructions();
-  }
-
   @Override
   public void buildPostlude(IRBuilder builder) {
     // Intentionally left empty. (Needed in the Java bytecode frontend for synchronization support.)
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 a39a66b..78b1405 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
@@ -36,6 +36,7 @@
 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;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -135,6 +136,7 @@
 import java.util.Map;
 import java.util.Queue;
 import java.util.Set;
+import java.util.function.BiConsumer;
 
 /**
  * Builder object for constructing high-level IR from dex bytecode.
@@ -448,11 +450,12 @@
     } else {
       this.prototypeChanges = appView.graphLense().lookupPrototypeChanges(method.method);
 
-      if (Log.ENABLED && prototypeChanges.getRemovedArgumentsInfo().hasRemovedArguments()) {
+      if (Log.ENABLED
+          && prototypeChanges.getRemovedArgumentInfoCollection().hasRemovedArguments()) {
         Log.info(
             getClass(),
             "Removed "
-                + prototypeChanges.getRemovedArgumentsInfo().numberOfRemovedArguments()
+                + prototypeChanges.getRemovedArgumentInfoCollection().numberOfRemovedArguments()
                 + " arguments from "
                 + method.toSourceString());
       }
@@ -503,6 +506,53 @@
     currentBlock = block;
   }
 
+  public void buildArgumentsWithUnusedArgumentStubs(
+      int register, DexEncodedMethod method, BiConsumer<Integer, DexType> writeCallback) {
+    RemovedArgumentInfoCollection removedArgumentsInfo =
+        prototypeChanges.getRemovedArgumentInfoCollection();
+
+    // Fill in the Argument instructions (incomingRegisterSize last registers) in the argument
+    // block.
+    int argumentIndex = 0;
+
+    if (!method.isStatic()) {
+      writeCallback.accept(register, method.method.holder);
+      addThisArgument(register);
+      argumentIndex++;
+      register++;
+    }
+
+    int numberOfArguments =
+        method.method.proto.parameters.values.length
+            + removedArgumentsInfo.numberOfRemovedArguments()
+            + (method.isStatic() ? 0 : 1);
+
+    int usedArgumentIndex = 0;
+    while (argumentIndex < numberOfArguments) {
+      TypeLatticeElement type;
+      if (removedArgumentsInfo.isArgumentRemoved(argumentIndex)) {
+        RemovedArgumentInfo argumentInfo = removedArgumentsInfo.getArgumentInfo(argumentIndex);
+        writeCallback.accept(register, argumentInfo.getType());
+        type =
+            TypeLatticeElement.fromDexType(
+                argumentInfo.getType(), Nullability.maybeNull(), appView);
+        addConstantOrUnusedArgument(register, argumentInfo);
+      } else {
+        DexType dexType = method.method.proto.parameters.values[usedArgumentIndex++];
+        writeCallback.accept(register, dexType);
+        type = TypeLatticeElement.fromDexType(dexType, Nullability.maybeNull(), appView);
+        if (dexType.isBooleanType()) {
+          addBooleanNonThisArgument(register);
+        } else {
+          addNonThisArgument(register, type);
+        }
+      }
+      register += type.requiredRegisters();
+      argumentIndex++;
+    }
+    flushArgumentInstructions();
+  }
+
   /**
    * Build the high-level IR in SSA form.
    *
@@ -864,7 +914,7 @@
   public void addThisArgument(int register, TypeLatticeElement receiverType) {
     DebugLocalInfo local = getOutgoingLocal(register);
     Value value = writeRegister(register, receiverType, ThrowingInfo.NO_THROW, local);
-    addInstruction(new Argument(value, false));
+    addInstruction(new Argument(value, currentBlock.size(), false));
     receiverValue = value;
     value.markAsThis();
   }
@@ -872,13 +922,13 @@
   public void addNonThisArgument(int register, TypeLatticeElement typeLattice) {
       DebugLocalInfo local = getOutgoingLocal(register);
       Value value = writeRegister(register, typeLattice, ThrowingInfo.NO_THROW, local);
-      addNonThisArgument(new Argument(value, false));
+    addNonThisArgument(new Argument(value, currentBlock.size(), false));
   }
 
   public void addBooleanNonThisArgument(int register) {
       DebugLocalInfo local = getOutgoingLocal(register);
       Value value = writeRegister(register, getInt(), ThrowingInfo.NO_THROW, local);
-      addNonThisArgument(new Argument(value, true));
+    addNonThisArgument(new Argument(value, currentBlock.size(), true));
   }
 
   private void addNonThisArgument(Argument argument) {
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 94df8fb..ce65222 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
@@ -59,7 +59,6 @@
 import com.android.tools.r8.ir.optimize.DeadCodeRemover;
 import com.android.tools.r8.ir.optimize.Devirtualizer;
 import com.android.tools.r8.ir.optimize.DynamicTypeOptimization;
-import com.android.tools.r8.ir.optimize.EnumUnboxer;
 import com.android.tools.r8.ir.optimize.IdempotentFunctionCallCanonicalizer;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -72,11 +71,14 @@
 import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
 import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
+import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoCollector;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
 import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer;
 import com.android.tools.r8.ir.optimize.string.StringBuilderOptimizer;
@@ -157,6 +159,7 @@
   private final TypeChecker typeChecker;
   private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
   private final ServiceLoaderRewriter serviceLoaderRewriter;
+  private final EnumValueOptimizer enumValueOptimizer;
   private final EnumUnboxer enumUnboxer;
 
   // Assumers that will insert Assume instructions.
@@ -246,6 +249,7 @@
       this.stringSwitchRemover = null;
       this.serviceLoaderRewriter = null;
       this.methodOptimizationInfoCollector = null;
+      this.enumValueOptimizer = null;
       this.enumUnboxer = null;
       return;
     }
@@ -325,6 +329,8 @@
               ? new DesugaredLibraryAPIConverter(
                   appView, Mode.ASSERT_CALLBACKS_AND_WRAPPERS_GENERATED)
               : null;
+      this.enumValueOptimizer =
+          options.enableEnumValueOptimization ? new EnumValueOptimizer(appViewWithLiveness) : null;
       this.enumUnboxer = options.enableEnumUnboxing ? new EnumUnboxer(appViewWithLiveness) : null;
     } else {
       this.classInliner = null;
@@ -349,6 +355,7 @@
               : null;
       this.serviceLoaderRewriter = null;
       this.methodOptimizationInfoCollector = null;
+      this.enumValueOptimizer = null;
       this.enumUnboxer = null;
     }
     this.stringSwitchRemover =
@@ -664,6 +671,10 @@
     // Assure that no more optimization feedback left after primary processing.
     assert feedback.noUpdatesLeft();
     appView.setAllCodeProcessed();
+    // All the code has been processed so the rewriting required by the lenses is done everywhere,
+    // we clear lens code rewriting so that the lens rewriter can be re-executed in phase 2 if new
+    // lenses with code rewriting are added.
+    graphLenseForIR = appView.clearCodeRewritings();
 
     if (libraryMethodOverrideAnalysis != null) {
       libraryMethodOverrideAnalysis.finish();
@@ -694,6 +705,11 @@
     }
     timing.end();
 
+    // All the code that should be impacted by the lenses inserted between phase 1 and phase 2
+    // have now been processed and rewritten, we clear code lens rewriting so that the class
+    // staticizer and phase 3 does not perform again the rewriting.
+    appView.clearCodeRewritings();
+
     // TODO(b/112831361): Implement support for staticizeClasses in CF backend.
     if (!options.isGeneratingClassFiles()) {
       printPhase("Class staticizer post processing");
@@ -702,6 +718,10 @@
       feedback.updateVisibleOptimizationInfo();
     }
 
+    // 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);
@@ -814,6 +834,7 @@
     if (options.enableFieldAssignmentTracker) {
       fieldAccessAnalysis.fieldAssignmentTracker().waveDone(wave, delayedOptimizationFeedback);
     }
+    delayedOptimizationFeedback.refineAppInfoWithLiveness(appView.appInfo().withLiveness());
     delayedOptimizationFeedback.updateVisibleOptimizationInfo();
     onWaveDoneActions.forEach(com.android.tools.r8.utils.Action::execute);
     onWaveDoneActions = null;
@@ -1105,18 +1126,17 @@
       codeRewriter.simplifyDebugLocals(code);
     }
 
+    if (appView.graphLense().hasCodeRewritings()) {
+      assert lensCodeRewriter != null;
+      timing.begin("Lens rewrite");
+      lensCodeRewriter.rewrite(code, method);
+      timing.end();
+    }
+
     if (method.isProcessed()) {
       assert !appView.enableWholeProgramOptimizations()
           || !appView.appInfo().withLiveness().neverReprocess.contains(method.method);
     } else {
-      if (lensCodeRewriter != null) {
-        timing.begin("Lens rewrite");
-        lensCodeRewriter.rewrite(code, method);
-        timing.end();
-      } else {
-        assert appView.graphLense().isIdentityLense();
-      }
-
       if (lambdaRewriter != null) {
         timing.begin("Desugar lambdas");
         lambdaRewriter.desugarLambdas(method, code);
@@ -1173,10 +1193,10 @@
       timing.end();
     }
 
-    if (options.enableEnumValueOptimization) {
+    if (enumValueOptimizer != null) {
       assert appView.enableWholeProgramOptimizations();
       timing.begin("Remove switch maps");
-      codeRewriter.removeSwitchMaps(code);
+      enumValueOptimizer.removeSwitchMaps(code);
       timing.end();
     }
 
@@ -1233,12 +1253,17 @@
       assert code.isConsistentSSA();
     }
 
+    assert code.verifyTypes(appView);
+
     if (devirtualizer != null) {
       assert code.verifyTypes(appView);
       timing.begin("Devirtualize invoke interface");
       devirtualizer.devirtualizeInvokeInterface(code, method.method.holder);
       timing.end();
     }
+
+    assert code.verifyTypes(appView);
+
     if (uninstantiatedTypeOptimization != null) {
       timing.begin("Rewrite uninstantiated types");
       uninstantiatedTypeOptimization.rewrite(code);
@@ -1246,14 +1271,15 @@
     }
 
     assert code.verifyTypes(appView);
+
     timing.begin("Remove trivial type checks/casts");
     codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code);
     timing.end();
 
-    if (options.enableEnumValueOptimization) {
+    if (enumValueOptimizer != null) {
       assert appView.enableWholeProgramOptimizations();
       timing.begin("Rewrite constant enum methods");
-      codeRewriter.rewriteConstantEnumMethodCalls(code);
+      enumValueOptimizer.rewriteConstantEnumMethodCalls(code);
       timing.end();
     }
 
@@ -1323,7 +1349,7 @@
 
     timing.begin("Optimize class initializers");
     ClassInitializerDefaultsResult classInitializerDefaultsResult =
-        classInitializerDefaultsOptimization.optimize(method, code);
+        classInitializerDefaultsOptimization.optimize(method, code, feedback);
     timing.end();
 
     if (Log.ENABLED) {
@@ -1364,6 +1390,7 @@
           appView.withLiveness(),
           codeRewriter,
           stringOptimizer,
+          enumValueOptimizer,
           method,
           code,
           feedback,
@@ -1563,18 +1590,19 @@
       return;
     }
 
-    methodOptimizationInfoCollector
-        .collectMethodOptimizationInfo(code.method, code, feedback, dynamicTypeOptimization);
-
+    InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos = null;
     if (method.isInitializer()) {
       if (method.isClassInitializer()) {
         StaticFieldValueAnalysis.run(
             appView, code, classInitializerDefaultsResult, feedback, code.method);
       } else {
-        InstanceFieldValueAnalysis.run(
-            appView, code, classInitializerDefaultsResult, feedback, code.method);
+        instanceFieldInitializationInfos =
+            InstanceFieldValueAnalysis.run(
+                appView, code, classInitializerDefaultsResult, feedback, code.method);
       }
     }
+    methodOptimizationInfoCollector.collectMethodOptimizationInfo(
+        code.method, code, feedback, dynamicTypeOptimization, instanceFieldInitializationInfos);
   }
 
   public void removeDeadCodeAndFinalizeIR(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 246d865..fc50632 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -27,9 +27,8 @@
 import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentsInfo;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection;
 import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
@@ -47,7 +46,6 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.InvokeCustom;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -169,15 +167,11 @@
               graphLense.lookupMethod(invokedMethod, method.method, invoke.getType());
           DexMethod actualTarget = lenseLookup.getMethod();
           Invoke.Type actualInvokeType = lenseLookup.getType();
-          if (actualInvokeType == Type.VIRTUAL) {
-            actualTarget =
-                rebindVirtualInvokeToMostSpecific(
-                    actualTarget, invoke.inValues().get(0), method.method.holder);
-          }
           if (actualTarget != invokedMethod || invoke.getType() != actualInvokeType) {
             RewrittenPrototypeDescription prototypeChanges =
                 graphLense.lookupPrototypeChanges(actualTarget);
-            RemovedArgumentsInfo removedArgumentsInfo = prototypeChanges.getRemovedArgumentsInfo();
+            RemovedArgumentInfoCollection removedArgumentsInfo =
+                prototypeChanges.getRemovedArgumentInfoCollection();
 
             ConstInstruction constantReturnMaterializingInstruction = null;
             if (prototypeChanges.hasBeenChangedToReturnVoid() && invoke.outValue() != null) {
@@ -597,80 +591,6 @@
     return newProto != oldProto ? new DexValueMethodType(newProto) : type;
   }
 
-  /**
-   * This rebinds invoke-virtual instructions to their most specific target.
-   *
-   * <p>As a simple example, consider the instruction "invoke-virtual A.foo(v0)", and assume that v0
-   * is defined by an instruction "new-instance v0, B". If B is a subtype of A, and B overrides the
-   * method foo(), then we rewrite the invocation into "invoke-virtual B.foo(v0)".
-   *
-   * <p>If A.foo() ends up being unused, this helps to ensure that we can get rid of A.foo()
-   * entirely. Without this rewriting, we would have to keep A.foo() because the method is targeted.
-   */
-  private DexMethod rebindVirtualInvokeToMostSpecific(
-      DexMethod target, Value receiver, DexType context) {
-    if (!receiver.getTypeLattice().isClassType()) {
-      return target;
-    }
-    DexEncodedMethod encodedTarget = appView.definitionFor(target);
-    if (encodedTarget == null
-        || !canInvokeTargetWithInvokeVirtual(encodedTarget)
-        || !hasAccessToInvokeTargetFromContext(encodedTarget, context)) {
-      // Don't rewrite this instruction as it could remove an error from the program.
-      return target;
-    }
-    DexType receiverType =
-        appView
-            .graphLense()
-            .lookupType(receiver.getTypeLattice().asClassTypeLatticeElement().getClassType());
-    if (receiverType == target.holder) {
-      // Virtual invoke is already as specific as it can get.
-      return target;
-    }
-    ResolutionResult resolutionResult = appView.appInfo().resolveMethod(receiverType, target);
-    DexEncodedMethod newTarget =
-        resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
-    if (newTarget == null || newTarget.method == target) {
-      // Most likely due to a missing class, or invoke is already as specific as it gets.
-      return target;
-    }
-    DexClass newTargetClass = appView.definitionFor(newTarget.method.holder);
-    if (newTargetClass == null
-        || newTargetClass.isLibraryClass()
-        || !canInvokeTargetWithInvokeVirtual(newTarget)
-        || !hasAccessToInvokeTargetFromContext(newTarget, context)) {
-      // Not safe to invoke `newTarget` with virtual invoke from the current context.
-      return target;
-    }
-    return newTarget.method;
-  }
-
-  private boolean canInvokeTargetWithInvokeVirtual(DexEncodedMethod target) {
-    return target.isNonPrivateVirtualMethod()
-        && appView.isInterface(target.method.holder).isFalse();
-  }
-
-  private boolean hasAccessToInvokeTargetFromContext(DexEncodedMethod target, DexType context) {
-    assert !target.accessFlags.isPrivate();
-    DexType holder = target.method.holder;
-    if (holder == context) {
-      // It is always safe to invoke a method from the same enclosing class.
-      return true;
-    }
-    DexClass clazz = appView.definitionFor(holder);
-    if (clazz == null) {
-      // Conservatively report an illegal access.
-      return false;
-    }
-    if (holder.isSamePackage(context)) {
-      // The class must be accessible (note that we have already established that the method is not
-      // private).
-      return !clazz.accessFlags.isPrivate();
-    }
-    // If the method is in another package, then the method and its holder must be public.
-    return clazz.accessFlags.isPublic() && target.accessFlags.isPublic();
-  }
-
   class InstructionReplacer {
 
     private final IRCode code;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index fae9325..0831e6d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -592,9 +592,9 @@
     @Override
     DexEncodedMethod ensureAccessibility(boolean allowMethodModification) {
       // We already found the static method to be called, just relax its accessibility.
-      target.method.accessFlags.unsetPrivate();
-      if (target.holder.isInterface()) {
-        target.method.accessFlags.setPublic();
+      target.getMethod().accessFlags.unsetPrivate();
+      if (target.getHolder().isInterface()) {
+        target.getMethod().accessFlags.setPublic();
       }
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index e35087a..1e02c2b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -45,6 +45,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.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo;
 import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo.ClassNameMapping;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -91,32 +92,20 @@
     }
   }
 
-  private class WaveDoneAction implements Action {
+  private static class WaveDoneAction implements Action {
 
     private final Map<DexEncodedField, DexValue> fieldsWithStaticValues = new IdentityHashMap<>();
-    private final Set<DexField> noLongerWrittenFields = Sets.newIdentityHashSet();
 
-    public WaveDoneAction(
-        Map<DexEncodedField, DexValue> fieldsWithStaticValues,
-        Set<DexField> noLongerWrittenFields) {
+    WaveDoneAction(Map<DexEncodedField, DexValue> fieldsWithStaticValues) {
       this.fieldsWithStaticValues.putAll(fieldsWithStaticValues);
-      this.noLongerWrittenFields.addAll(noLongerWrittenFields);
     }
 
-    public synchronized void join(
-        Map<DexEncodedField, DexValue> fieldsWithStaticValues,
-        Set<DexField> noLongerWrittenFields) {
+    public synchronized void join(Map<DexEncodedField, DexValue> fieldsWithStaticValues) {
       this.fieldsWithStaticValues.putAll(fieldsWithStaticValues);
-      this.noLongerWrittenFields.addAll(noLongerWrittenFields);
     }
 
     @Override
     public void execute() {
-      // Update AppInfo.
-      AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-      appViewWithLiveness.setAppInfo(
-          appViewWithLiveness.appInfo().withoutStaticFieldsWrites(noLongerWrittenFields));
-
       // Update static field values of classes.
       fieldsWithStaticValues.forEach(DexEncodedField::setStaticValue);
     }
@@ -134,7 +123,8 @@
     this.dexItemFactory = appView.dexItemFactory();
   }
 
-  public ClassInitializerDefaultsResult optimize(DexEncodedMethod method, IRCode code) {
+  public ClassInitializerDefaultsResult optimize(
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     if (appView.options().debug || method.getOptimizationInfo().isReachabilitySensitive()) {
       return ClassInitializerDefaultsResult.empty();
     }
@@ -269,17 +259,21 @@
           }
         }
 
-        // Finally, remove these fields from the set of assigned static fields.
+        // Remove these fields from the set of assigned static fields.
+        feedback.modifyAppInfoWithLiveness(
+            modifier -> candidates.forEach(modifier::removeWrittenField));
+
+        // Update the static value of the fields when the wave ends.
         synchronized (this) {
           if (waveDoneAction == null) {
-            waveDoneAction = new WaveDoneAction(fieldsWithStaticValues, candidates);
+            waveDoneAction = new WaveDoneAction(fieldsWithStaticValues);
             converter.addWaveDoneAction(
                 () -> {
                   waveDoneAction.execute();
                   waveDoneAction = null;
                 });
           } else {
-            waveDoneAction.join(fieldsWithStaticValues, candidates);
+            waveDoneAction.join(fieldsWithStaticValues);
           }
         }
       } else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 5b695a7..7f2cee9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexClass;
 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.DexItemFactory.ThrowableMethods;
 import com.android.tools.r8.graph.DexMethod;
@@ -57,11 +56,9 @@
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.JumpInstruction;
 import com.android.tools.r8.ir.code.Move;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
@@ -76,9 +73,7 @@
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
-import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOutputMode;
 import com.android.tools.r8.utils.LongInterval;
@@ -93,7 +88,6 @@
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
-import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
@@ -1043,69 +1037,6 @@
   }
 
   /**
-   * Inline the indirection of switch maps into the switch statement.
-   * <p>
-   * To ensure binary compatibility, javac generated code does not use ordinal values of enums
-   * directly in switch statements but instead generates a companion class that computes a mapping
-   * from switch branches to ordinals at runtime. As we have whole-program knowledge, we can
-   * analyze these maps and inline the indirection into the switch map again.
-   * <p>
-   * In particular, we look for code of the form
-   *
-   * <blockquote><pre>
-   * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) {
-   *   ...
-   * }
-   * </pre></blockquote>
-   */
-  public void removeSwitchMaps(IRCode code) {
-    for (BasicBlock block : code.blocks) {
-      JumpInstruction exit = block.exit();
-      // Pattern match a switch on a switch map as input.
-      if (exit.isIntSwitch()) {
-        IntSwitch switchInsn = exit.asIntSwitch();
-        EnumSwitchInfo info = SwitchUtils.analyzeSwitchOverEnum(switchInsn, appView.withLiveness());
-        if (info != null) {
-          Int2IntMap targetMap = new Int2IntArrayMap();
-          for (int i = 0; i < switchInsn.numberOfKeys(); i++) {
-            assert switchInsn.targetBlockIndices()[i] != switchInsn.getFallthroughBlockIndex();
-            EnumValueInfo valueInfo =
-                info.valueInfoMap.get(info.indexMap.get(switchInsn.getKey(i)));
-            targetMap.put(valueInfo.ordinal, switchInsn.targetBlockIndices()[i]);
-          }
-          int[] keys = targetMap.keySet().toIntArray();
-          Arrays.sort(keys);
-          int[] targets = new int[keys.length];
-          for (int i = 0; i < keys.length; i++) {
-            targets[i] = targetMap.get(keys[i]);
-          }
-
-          IntSwitch newSwitch =
-              new IntSwitch(
-                  info.ordinalInvoke.outValue(),
-                  keys,
-                  targets,
-                  switchInsn.getFallthroughBlockIndex());
-          // Replace the switch itself.
-          exit.replace(newSwitch, code);
-          // If the original input to the switch is now unused, remove it too. It is not dead
-          // as it might have side-effects but we ignore these here.
-          Instruction arrayGet = info.arrayGet;
-          if (!arrayGet.outValue().hasUsers()) {
-            arrayGet.inValues().forEach(v -> v.removeUser(arrayGet));
-            arrayGet.getBlock().removeInstruction(arrayGet);
-          }
-          Instruction staticGet = info.staticGet;
-          if (!staticGet.outValue().hasUsers()) {
-            assert staticGet.inValues().isEmpty();
-            staticGet.getBlock().removeInstruction(staticGet);
-          }
-        }
-      }
-    }
-  }
-
-  /**
    * Rewrite all branch targets to the destination of trivial goto chains when possible. Does not
    * rewrite fallthrough targets as that would require block reordering and the transformation only
    * makes sense after SSA destruction where there are no phis.
@@ -2966,80 +2897,6 @@
     return true;
   }
 
-  public void rewriteConstantEnumMethodCalls(IRCode code) {
-    if (!code.metadata().mayHaveInvokeMethodWithReceiver()) {
-      return;
-    }
-
-    InstructionListIterator iterator = code.instructionListIterator();
-    while (iterator.hasNext()) {
-      Instruction current = iterator.next();
-
-      if (!current.isInvokeMethodWithReceiver()) {
-        continue;
-      }
-      InvokeMethodWithReceiver methodWithReceiver = current.asInvokeMethodWithReceiver();
-      DexMethod invokedMethod = methodWithReceiver.getInvokedMethod();
-      boolean isOrdinalInvoke = invokedMethod == dexItemFactory.enumMethods.ordinal;
-      boolean isNameInvoke = invokedMethod == dexItemFactory.enumMethods.name;
-      boolean isToStringInvoke = invokedMethod == dexItemFactory.enumMethods.toString;
-      if (!isOrdinalInvoke && !isNameInvoke && !isToStringInvoke) {
-        continue;
-      }
-
-      Value receiver = methodWithReceiver.getReceiver().getAliasedValue();
-      if (receiver.isPhi()) {
-        continue;
-      }
-      Instruction definition = receiver.getDefinition();
-      if (!definition.isStaticGet()) {
-        continue;
-      }
-      DexField enumField = definition.asStaticGet().getField();
-
-      Map<DexField, EnumValueInfo> valueInfoMap =
-          appView.appInfo().withLiveness().getEnumValueInfoMapFor(enumField.type);
-      if (valueInfoMap == null) {
-        continue;
-      }
-
-      // The receiver value is identified as being from a constant enum field lookup by the fact
-      // that it is a static-get to a field whose type is the same as the enclosing class (which
-      // is known to be an enum type). An enum may still define a static field using the enum type
-      // so ensure the field is present in the ordinal map for final validation.
-      EnumValueInfo valueInfo = valueInfoMap.get(enumField);
-      if (valueInfo == null) {
-        continue;
-      }
-
-      Value outValue = methodWithReceiver.outValue();
-      if (isOrdinalInvoke) {
-        iterator.replaceCurrentInstruction(new ConstNumber(outValue, valueInfo.ordinal));
-      } else if (isNameInvoke) {
-        iterator.replaceCurrentInstruction(
-            new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
-      } else {
-        assert isToStringInvoke;
-        DexClass enumClazz = appView.appInfo().definitionFor(enumField.type);
-        if (!enumClazz.accessFlags.isFinal()) {
-          continue;
-        }
-        if (appView
-                .appInfo()
-                .resolveMethodOnClass(valueInfo.type, dexItemFactory.objectMethods.toString)
-                .getSingleTarget()
-                .method
-            != dexItemFactory.enumMethods.toString) {
-          continue;
-        }
-        iterator.replaceCurrentInstruction(
-            new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
-      }
-    }
-
-    assert code.isConsistentSSA();
-  }
-
   public void rewriteKnownArrayLengthCalls(IRCode code) {
     InstructionListIterator iterator = code.instructionListIterator();
     while (iterator.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 8370b69..f1c5dc9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -607,11 +607,11 @@
 
     for (Monitor monitor : inlinee.code.<Monitor>instructions(Instruction::isMonitorEnter)) {
       Value monitorEnterValue = monitor.object().getAliasedValue();
-      if (monitorEnterValue.isArgument()) {
+      if (monitorEnterValue.isDefinedByInstructionSatisfying(Instruction::isArgument)) {
         monitorEnterValue =
             invoke
                 .arguments()
-                .get(monitorEnterValue.computeArgumentPosition(inlinee.code))
+                .get(monitorEnterValue.definition.asArgument().getIndex())
                 .getAliasedValue();
       }
       addMonitorEnterValue(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index bebbbf4..0c53a4c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -6,7 +6,9 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.Assume;
@@ -17,6 +19,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeInterface;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
@@ -31,8 +34,14 @@
 import java.util.Set;
 
 /**
- * Rewrites all invoke-interface instructions that have a unique target on a class into
+ * Tries to rewrite virtual invokes to their most specific target by:
+ *
+ * <pre>
+ * 1) Rewriting all invoke-interface instructions that have a unique target on a class into
  * invoke-virtual with the corresponding unique target.
+ * 2) Rewriting all invoke-virtual instructions that have a more specific target to an
+ * invoke-virtual with the corresponding target.
+ * </pre>
  */
 public class Devirtualizer {
 
@@ -105,6 +114,20 @@
           }
         }
 
+        if (current.isInvokeVirtual()) {
+          InvokeVirtual invoke = current.asInvokeVirtual();
+          DexMethod invokedMethod = invoke.getInvokedMethod();
+          DexMethod reboundTarget =
+              rebindVirtualInvokeToMostSpecific(
+                  invokedMethod, invoke.getReceiver(), invocationContext);
+          if (reboundTarget != invokedMethod) {
+            Invoke newInvoke =
+                Invoke.create(
+                    Invoke.Type.VIRTUAL, reboundTarget, null, invoke.outValue(), invoke.inValues());
+            it.replaceCurrentInstruction(newInvoke);
+          }
+        }
+
         if (!current.isInvokeInterface()) {
           continue;
         }
@@ -226,4 +249,78 @@
     }
     assert code.isConsistentSSA();
   }
+
+  /**
+   * This rebinds invoke-virtual instructions to their most specific target.
+   *
+   * <p>As a simple example, consider the instruction "invoke-virtual A.foo(v0)", and assume that v0
+   * is defined by an instruction "new-instance v0, B". If B is a subtype of A, and B overrides the
+   * method foo(), then we rewrite the invocation into "invoke-virtual B.foo(v0)".
+   *
+   * <p>If A.foo() ends up being unused, this helps to ensure that we can get rid of A.foo()
+   * entirely. Without this rewriting, we would have to keep A.foo() because the method is targeted.
+   */
+  private DexMethod rebindVirtualInvokeToMostSpecific(
+      DexMethod target, Value receiver, DexType context) {
+    if (!receiver.getTypeLattice().isClassType()) {
+      return target;
+    }
+    DexEncodedMethod encodedTarget = appView.definitionFor(target);
+    if (encodedTarget == null
+        || !canInvokeTargetWithInvokeVirtual(encodedTarget)
+        || !hasAccessToInvokeTargetFromContext(encodedTarget, context)) {
+      // Don't rewrite this instruction as it could remove an error from the program.
+      return target;
+    }
+    DexType receiverType =
+        appView
+            .graphLense()
+            .lookupType(receiver.getTypeLattice().asClassTypeLatticeElement().getClassType());
+    if (receiverType == target.holder) {
+      // Virtual invoke is already as specific as it can get.
+      return target;
+    }
+    ResolutionResult resolutionResult = appView.appInfo().resolveMethod(receiverType, target);
+    DexEncodedMethod newTarget =
+        resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
+    if (newTarget == null || newTarget.method == target) {
+      // Most likely due to a missing class, or invoke is already as specific as it gets.
+      return target;
+    }
+    DexClass newTargetClass = appView.definitionFor(newTarget.method.holder);
+    if (newTargetClass == null
+        || newTargetClass.isLibraryClass()
+        || !canInvokeTargetWithInvokeVirtual(newTarget)
+        || !hasAccessToInvokeTargetFromContext(newTarget, context)) {
+      // Not safe to invoke `newTarget` with virtual invoke from the current context.
+      return target;
+    }
+    return newTarget.method;
+  }
+
+  private boolean canInvokeTargetWithInvokeVirtual(DexEncodedMethod target) {
+    return target.isNonPrivateVirtualMethod()
+        && appView.isInterface(target.method.holder).isFalse();
+  }
+
+  private boolean hasAccessToInvokeTargetFromContext(DexEncodedMethod target, DexType context) {
+    assert !target.accessFlags.isPrivate();
+    DexType holder = target.method.holder;
+    if (holder == context) {
+      // It is always safe to invoke a method from the same enclosing class.
+      return true;
+    }
+    DexClass clazz = appView.definitionFor(holder);
+    if (clazz == null) {
+      // Conservatively report an illegal access.
+      return false;
+    }
+    if (holder.isSamePackage(context)) {
+      // The class must be accessible (note that we have already established that the method is not
+      // private).
+      return !clazz.accessFlags.isPrivate();
+    }
+    // If the method is in another package, then the method and its holder must be public.
+    return clazz.accessFlags.isPublic() && target.accessFlags.isPublic();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 96819d0..70a9795 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -442,7 +442,9 @@
 
     // For each of the actual potential targets, derive constraints based on the accessibility
     // of the method itself.
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+    LookupResult lookupResult =
+        resolutionResult.lookupVirtualDispatchTargets(
+            appView.definitionForProgramType(invocationContext), appView);
     if (lookupResult.isLookupResultFailure()) {
       return ConstraintWithTarget.NEVER;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
index aeedc02..6e53d40 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
@@ -4,8 +4,8 @@
 package com.android.tools.r8.ir.optimize;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.Descriptor;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -28,13 +28,13 @@
 import java.util.function.Predicate;
 
 // Per-class collection of member signatures.
-public abstract class MemberPoolCollection<T extends Descriptor> {
+public abstract class MemberPoolCollection<R extends DexMember<?, R>> {
 
-  final Equivalence<T> equivalence;
+  final Equivalence<R> equivalence;
   final AppView<AppInfoWithLiveness> appView;
-  final Map<DexClass, MemberPool<T>> memberPools = new ConcurrentHashMap<>();
+  final Map<DexClass, MemberPool<R>> memberPools = new ConcurrentHashMap<>();
 
-  MemberPoolCollection(AppView<AppInfoWithLiveness> appView, Equivalence<T> equivalence) {
+  MemberPoolCollection(AppView<AppInfoWithLiveness> appView, Equivalence<R> equivalence) {
     this.appView = appView;
     this.equivalence = equivalence;
   }
@@ -56,7 +56,7 @@
     }
   }
 
-  public MemberPool<T> buildForHierarchy(
+  public MemberPool<R> buildForHierarchy(
       DexClass clazz, ExecutorService executorService, Timing timing) throws ExecutionException {
     timing.begin("Building member pool collection");
     try {
@@ -75,14 +75,14 @@
     return memberPools.containsKey(clazz);
   }
 
-  public MemberPool<T> get(DexClass clazz) {
+  public MemberPool<R> get(DexClass clazz) {
     assert hasPool(clazz);
     return memberPools.get(clazz);
   }
 
-  public boolean markIfNotSeen(DexClass clazz, T reference) {
-    MemberPool<T> memberPool = get(clazz);
-    Wrapper<T> key = equivalence.wrap(reference);
+  public boolean markIfNotSeen(DexClass clazz, R reference) {
+    MemberPool<R> memberPool = get(clazz);
+    Wrapper<R> key = equivalence.wrap(reference);
     if (memberPool.hasSeen(key)) {
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index 5577a8c..f362ef7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -186,7 +186,8 @@
           if (newInstance.clazz.classInitializationMayHaveSideEffects(
               appView,
               // Types that are a super type of `context` are guaranteed to be initialized already.
-              type -> appView.isSubtype(context, type).isTrue())) {
+              type -> appView.isSubtype(context, type).isTrue(),
+              Sets.newIdentityHashSet())) {
             killAllActiveFields();
           }
         } else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchUtils.java
deleted file mode 100644
index 0dd0bcf..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchUtils.java
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.optimize;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.ArrayGet;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.StaticGet;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
-import java.util.Map;
-
-public class SwitchUtils {
-
-  public static final class EnumSwitchInfo {
-    public final DexType enumClass;
-    public final Instruction ordinalInvoke;
-    public final Instruction arrayGet;
-    public final Instruction staticGet;
-    public final Int2ReferenceMap<DexField> indexMap;
-    public final Map<DexField, EnumValueInfo> valueInfoMap;
-
-    private EnumSwitchInfo(DexType enumClass,
-        Instruction ordinalInvoke,
-        Instruction arrayGet, Instruction staticGet,
-        Int2ReferenceMap<DexField> indexMap,
-        Map<DexField, EnumValueInfo> valueInfoMap) {
-      this.enumClass = enumClass;
-      this.ordinalInvoke = ordinalInvoke;
-      this.arrayGet = arrayGet;
-      this.staticGet = staticGet;
-      this.indexMap = indexMap;
-      this.valueInfoMap = valueInfoMap;
-    }
-  }
-
-  /**
-   * Looks for a switch statement over the enum companion class of the form
-   *
-   * <blockquote>
-   *
-   * <pre>
-   * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) {
-   *   ...
-   * }
-   * </pre>
-   *
-   * </blockquote>
-   *
-   * and extracts the components and the index and ordinal maps. See {@link EnumInfoMapCollector}
-   * and {@link SwitchMapCollector} for details.
-   */
-  public static EnumSwitchInfo analyzeSwitchOverEnum(
-      Instruction switchInsn, AppView<AppInfoWithLiveness> appView) {
-    AppInfoWithLiveness appInfo = appView.appInfo();
-    Instruction input = switchInsn.inValues().get(0).definition;
-    if (input == null || !input.isArrayGet()) {
-      return null;
-    }
-    ArrayGet arrayGet = input.asArrayGet();
-    Instruction index = arrayGet.index().definition;
-    if (index == null || !index.isInvokeVirtual()) {
-      return null;
-    }
-    InvokeVirtual ordinalInvoke = index.asInvokeVirtual();
-    DexMethod ordinalMethod = ordinalInvoke.getInvokedMethod();
-    DexClass enumClass = appInfo.definitionFor(ordinalMethod.holder);
-    DexItemFactory dexItemFactory = appInfo.dexItemFactory();
-    // After member rebinding, enumClass will be the actual java.lang.Enum class.
-    if (enumClass == null
-        || (!enumClass.accessFlags.isEnum() && enumClass.type != dexItemFactory.enumType)
-        || ordinalMethod.name != dexItemFactory.ordinalMethodName
-        || ordinalMethod.proto.returnType != dexItemFactory.intType
-        || !ordinalMethod.proto.parameters.isEmpty()) {
-      return null;
-    }
-    Instruction array = arrayGet.array().definition;
-    if (array == null || !array.isStaticGet()) {
-      return null;
-    }
-    StaticGet staticGet = array.asStaticGet();
-    Int2ReferenceMap<DexField> indexMap = appInfo.getSwitchMapFor(staticGet.getField());
-    if (indexMap == null || indexMap.isEmpty()) {
-      return null;
-    }
-    // Due to member rebinding, only the fields are certain to provide the actual enums
-    // class.
-    DexType enumType = indexMap.values().iterator().next().holder;
-    Map<DexField, EnumValueInfo> valueInfoMap = appInfo.getEnumValueInfoMapFor(enumType);
-    if (valueInfoMap == null) {
-      return null;
-    }
-    return new EnumSwitchInfo(enumType, ordinalInvoke, arrayGet, staticGet, indexMap,
-        valueInfoMap);
-  }
-
-
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index c234bc3..a04e7c5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentsInfo;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection;
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
 import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.TypeChecker;
@@ -43,7 +43,6 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
-import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -63,11 +62,11 @@
 
   public static class UninstantiatedTypeOptimizationGraphLense extends NestedGraphLense {
 
-    private final Map<DexMethod, RemovedArgumentsInfo> removedArgumentsInfoPerMethod;
+    private final Map<DexMethod, RemovedArgumentInfoCollection> removedArgumentsInfoPerMethod;
 
     UninstantiatedTypeOptimizationGraphLense(
         BiMap<DexMethod, DexMethod> methodMap,
-        Map<DexMethod, RemovedArgumentsInfo> removedArgumentsInfoPerMethod,
+        Map<DexMethod, RemovedArgumentInfoCollection> removedArgumentsInfoPerMethod,
         AppView<?> appView) {
       super(
           ImmutableMap.of(),
@@ -88,7 +87,8 @@
         if (method.proto.returnType.isVoidType() && !originalMethod.proto.returnType.isVoidType()) {
           result = result.withConstantReturn();
         }
-        RemovedArgumentsInfo removedArgumentsInfo = removedArgumentsInfoPerMethod.get(method);
+        RemovedArgumentInfoCollection removedArgumentsInfo =
+            removedArgumentsInfoPerMethod.get(method);
         if (removedArgumentsInfo != null) {
           result = result.withRemovedArguments(removedArgumentsInfo);
         }
@@ -125,7 +125,8 @@
 
     Map<Wrapper<DexMethod>, Set<DexType>> changedVirtualMethods = new HashMap<>();
     BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
-    Map<DexMethod, RemovedArgumentsInfo> removedArgumentsInfoPerMethod = new IdentityHashMap<>();
+    Map<DexMethod, RemovedArgumentInfoCollection> removedArgumentsInfoPerMethod =
+        new IdentityHashMap<>();
 
     TopDownClassHierarchyTraversal.forProgramClasses(appView)
         .visit(
@@ -150,7 +151,7 @@
       Map<Wrapper<DexMethod>, Set<DexType>> changedVirtualMethods,
       BiMap<DexMethod, DexMethod> methodMapping,
       MethodPoolCollection methodPoolCollection,
-      Map<DexMethod, RemovedArgumentsInfo> removedArgumentsInfoPerMethod) {
+      Map<DexMethod, RemovedArgumentInfoCollection> removedArgumentsInfoPerMethod) {
     MemberPool<DexMethod> methodPool = methodPoolCollection.get(clazz);
 
     if (clazz.isInterface()) {
@@ -198,7 +199,8 @@
       RewrittenPrototypeDescription prototypeChanges =
           prototypeChangesPerMethod.getOrDefault(
               encodedMethod, RewrittenPrototypeDescription.none());
-      RemovedArgumentsInfo removedArgumentsInfo = prototypeChanges.getRemovedArgumentsInfo();
+      RemovedArgumentInfoCollection removedArgumentsInfo =
+          prototypeChanges.getRemovedArgumentInfoCollection();
       DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
       if (newMethod != method) {
         Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
@@ -231,7 +233,8 @@
       DexMethod method = encodedMethod.method;
       RewrittenPrototypeDescription prototypeChanges =
           getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
-      RemovedArgumentsInfo removedArgumentsInfo = prototypeChanges.getRemovedArgumentsInfo();
+      RemovedArgumentInfoCollection removedArgumentsInfo =
+          prototypeChanges.getRemovedArgumentInfoCollection();
       DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
       if (newMethod != method) {
         Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
@@ -259,7 +262,8 @@
       DexMethod method = encodedMethod.method;
       RewrittenPrototypeDescription prototypeChanges =
           getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
-      RemovedArgumentsInfo removedArgumentsInfo = prototypeChanges.getRemovedArgumentsInfo();
+      RemovedArgumentInfoCollection removedArgumentsInfo =
+          prototypeChanges.getRemovedArgumentInfoCollection();
       DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
       if (newMethod != method) {
         Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
@@ -298,32 +302,24 @@
         getRemovedArgumentsInfo(encodedMethod, strategy));
   }
 
-  private RemovedArgumentsInfo getRemovedArgumentsInfo(
+  private RemovedArgumentInfoCollection getRemovedArgumentsInfo(
       DexEncodedMethod encodedMethod, Strategy strategy) {
     if (strategy == DISALLOW_ARGUMENT_REMOVAL) {
-      return RemovedArgumentsInfo.empty();
+      return RemovedArgumentInfoCollection.empty();
     }
 
-    List<RemovedArgumentInfo> removedArgumentsInfo = null;
+    RemovedArgumentInfoCollection.Builder argInfosBuilder = RemovedArgumentInfoCollection.builder();
     DexProto proto = encodedMethod.method.proto;
     int offset = encodedMethod.isStatic() ? 0 : 1;
     for (int i = 0; i < proto.parameters.size(); ++i) {
       DexType type = proto.parameters.values[i];
       if (type.isAlwaysNull(appView)) {
-        if (removedArgumentsInfo == null) {
-          removedArgumentsInfo = new ArrayList<>();
-        }
-        removedArgumentsInfo.add(
-            RemovedArgumentInfo.builder()
-                .setArgumentIndex(i + offset)
-                .setIsAlwaysNull()
-                .setType(type)
-                .build());
+        RemovedArgumentInfo removedArg =
+            RemovedArgumentInfo.builder().setIsAlwaysNull().setType(type).build();
+        argInfosBuilder.addRemovedArgument(i + offset, removedArg);
       }
     }
-    return removedArgumentsInfo != null
-        ? new RemovedArgumentsInfo(removedArgumentsInfo)
-        : RemovedArgumentsInfo.empty();
+    return argInfosBuilder.build();
   }
 
   private DexMethod getNewMethodSignature(
@@ -336,6 +332,7 @@
   }
 
   public void rewrite(IRCode code) {
+    AssumeDynamicTypeRemover assumeDynamicTypeRemover = new AssumeDynamicTypeRemover(appView, code);
     Set<BasicBlock> blocksToBeRemoved = Sets.newIdentityHashSet();
     ListIterator<BasicBlock> blockIterator = code.listIterator();
     Set<Value> valuesToNarrow = Sets.newIdentityHashSet();
@@ -375,6 +372,7 @@
               blockIterator,
               instructionIterator,
               code,
+              assumeDynamicTypeRemover,
               valuesToNarrow);
         } else if (instruction.isInvokeMethod()) {
           rewriteInvoke(
@@ -382,11 +380,13 @@
               blockIterator,
               instructionIterator,
               code,
+              assumeDynamicTypeRemover,
               blocksToBeRemoved,
               valuesToNarrow);
         }
       }
     }
+    assumeDynamicTypeRemover.removeMarkedInstructions(blocksToBeRemoved).finish();
     code.removeBlocks(blocksToBeRemoved);
     code.removeAllTrivialPhis(valuesToNarrow);
     code.removeUnreachableBlocks();
@@ -444,6 +444,7 @@
       ListIterator<BasicBlock> blockIterator,
       InstructionListIterator instructionIterator,
       IRCode code,
+      AssumeDynamicTypeRemover assumeDynamicTypeRemover,
       Set<Value> affectedValues) {
     DexType context = code.method.method.holder;
     DexField field = instruction.getField();
@@ -473,10 +474,12 @@
       } else {
         if (instructionCanBeRemoved) {
           // Replace the field read by the constant null.
+          assumeDynamicTypeRemover.markUsersForRemoval(instruction.outValue());
           affectedValues.addAll(instruction.outValue().affectedValues());
           instructionIterator.replaceCurrentInstruction(code.createConstNull());
         } else {
-          replaceOutValueByNull(instruction, instructionIterator, code, affectedValues);
+          replaceOutValueByNull(
+              instruction, instructionIterator, code, assumeDynamicTypeRemover, affectedValues);
         }
       }
 
@@ -496,6 +499,7 @@
       ListIterator<BasicBlock> blockIterator,
       InstructionListIterator instructionIterator,
       IRCode code,
+      AssumeDynamicTypeRemover assumeDynamicTypeRemover,
       Set<BasicBlock> blocksToBeRemoved,
       Set<Value> affectedValues) {
     DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.method.method.holder);
@@ -518,7 +522,8 @@
 
     DexType returnType = target.method.proto.returnType;
     if (returnType.isAlwaysNull(appView)) {
-      replaceOutValueByNull(invoke, instructionIterator, code, affectedValues);
+      replaceOutValueByNull(
+          invoke, instructionIterator, code, assumeDynamicTypeRemover, affectedValues);
     }
   }
 
@@ -526,11 +531,13 @@
       Instruction instruction,
       InstructionListIterator instructionIterator,
       IRCode code,
+      AssumeDynamicTypeRemover assumeDynamicTypeRemover,
       Set<Value> affectedValues) {
     assert instructionIterator.peekPrevious() == instruction;
     if (instruction.hasOutValue()) {
       Value outValue = instruction.outValue();
       if (outValue.numberOfAllUsers() > 0) {
+        assumeDynamicTypeRemover.markUsersForRemoval(outValue);
         instructionIterator.previous();
         affectedValues.addAll(outValue.affectedValues());
         outValue.replaceUsers(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index d8568d5..e99ba9a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -18,7 +18,7 @@
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentsInfo;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection;
 import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
@@ -32,7 +32,6 @@
 import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Streams;
-import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
@@ -51,11 +50,12 @@
   private final MethodPoolCollection methodPoolCollection;
 
   private final BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
-  private final Map<DexMethod, RemovedArgumentsInfo> removedArguments = new IdentityHashMap<>();
+  private final Map<DexMethod, RemovedArgumentInfoCollection> removedArguments =
+      new IdentityHashMap<>();
 
   public static class UnusedArgumentsGraphLense extends NestedGraphLense {
 
-    private final Map<DexMethod, RemovedArgumentsInfo> removedArguments;
+    private final Map<DexMethod, RemovedArgumentInfoCollection> removedArguments;
 
     UnusedArgumentsGraphLense(
         Map<DexType, DexType> typeMap,
@@ -65,7 +65,7 @@
         BiMap<DexMethod, DexMethod> originalMethodSignatures,
         GraphLense previousLense,
         DexItemFactory dexItemFactory,
-        Map<DexMethod, RemovedArgumentsInfo> removedArguments) {
+        Map<DexMethod, RemovedArgumentInfoCollection> removedArguments) {
       super(
           typeMap,
           methodMap,
@@ -84,7 +84,7 @@
               ? originalMethodSignatures.getOrDefault(method, method)
               : method;
       RewrittenPrototypeDescription result = previousLense.lookupPrototypeChanges(originalMethod);
-      RemovedArgumentsInfo removedArguments = this.removedArguments.get(method);
+      RemovedArgumentInfoCollection removedArguments = this.removedArguments.get(method);
       return removedArguments != null ? result.withRemovedArguments(removedArguments) : result;
     }
   }
@@ -167,7 +167,7 @@
     }
 
     DexEncodedMethod removeArguments(
-        DexEncodedMethod method, DexMethod newSignature, RemovedArgumentsInfo unused) {
+        DexEncodedMethod method, DexMethod newSignature, RemovedArgumentInfoCollection unused) {
       boolean removed = usedSignatures.remove(equivalence.wrap(method.method));
       assert removed;
 
@@ -208,7 +208,7 @@
     }
 
     DexEncodedMethod removeArguments(
-        DexEncodedMethod method, DexMethod newSignature, RemovedArgumentsInfo unused) {
+        DexEncodedMethod method, DexMethod newSignature, RemovedArgumentInfoCollection unused) {
       methodPool.seen(equivalence.wrap(newSignature));
       return method.toTypeSubstitutedMethod(
           newSignature, unused.createParameterAnnotationsRemover(method));
@@ -234,7 +234,7 @@
         continue;
       }
 
-      RemovedArgumentsInfo unused = collectUnusedArguments(method);
+      RemovedArgumentInfoCollection unused = collectUnusedArguments(method);
       if (unused != null && unused.hasRemovedArguments()) {
         DexProto newProto = createProtoWithRemovedArguments(method, unused);
         DexMethod newSignature = signatures.getNewSignature(method, newProto);
@@ -259,7 +259,7 @@
     List<DexEncodedMethod> virtualMethods = clazz.virtualMethods();
     for (int i = 0; i < virtualMethods.size(); i++) {
       DexEncodedMethod method = virtualMethods.get(i);
-      RemovedArgumentsInfo unused = collectUnusedArguments(method, methodPool);
+      RemovedArgumentInfoCollection unused = collectUnusedArguments(method, methodPool);
       if (unused != null && unused.hasRemovedArguments()) {
         DexProto newProto = createProtoWithRemovedArguments(method, unused);
         DexMethod newSignature = signatures.getNewSignature(method, newProto);
@@ -279,11 +279,11 @@
     }
   }
 
-  private RemovedArgumentsInfo collectUnusedArguments(DexEncodedMethod method) {
+  private RemovedArgumentInfoCollection collectUnusedArguments(DexEncodedMethod method) {
     return collectUnusedArguments(method, null);
   }
 
-  private RemovedArgumentsInfo collectUnusedArguments(
+  private RemovedArgumentInfoCollection collectUnusedArguments(
       DexEncodedMethod method, MemberPool<DexMethod> methodPool) {
     if (ArgumentRemovalUtils.isPinned(method, appView)
         || appView.appInfo().keepUnusedArguments.contains(method.method)) {
@@ -314,23 +314,24 @@
     method.getCode().registerArgumentReferences(method, collector);
     BitSet used = collector.getUsedArguments();
     if (used.cardinality() < argumentCount) {
-      List<RemovedArgumentInfo> unused = new ArrayList<>();
+      RemovedArgumentInfoCollection.Builder argInfosBuilder =
+          RemovedArgumentInfoCollection.builder();
       for (int argumentIndex = 0; argumentIndex < argumentCount; argumentIndex++) {
         if (!used.get(argumentIndex)) {
-          unused.add(
+          RemovedArgumentInfo removedArg =
               RemovedArgumentInfo.builder()
-                  .setArgumentIndex(argumentIndex)
                   .setType(method.method.proto.parameters.values[argumentIndex - offset])
-                  .build());
+                  .build();
+          argInfosBuilder.addRemovedArgument(argumentIndex, removedArg);
         }
       }
-      return new RemovedArgumentsInfo(unused);
+      return argInfosBuilder.build();
     }
     return null;
   }
 
   private DexProto createProtoWithRemovedArguments(
-      DexEncodedMethod encodedMethod, RemovedArgumentsInfo unused) {
+      DexEncodedMethod encodedMethod, RemovedArgumentInfoCollection unused) {
     DexType[] parameters = unused.rewriteParameters(encodedMethod);
     return appView.dexItemFactory().createProto(encodedMethod.method.proto.returnType, parameters);
   }
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 c4a761b..b20eba1 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
@@ -22,6 +22,7 @@
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.InliningOracle;
 import com.android.tools.r8.ir.optimize.classinliner.InlineCandidateProcessor.IllegalClassInlinerStateException;
+import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
 import com.android.tools.r8.ir.optimize.string.StringOptimizer;
@@ -164,6 +165,7 @@
       AppView<AppInfoWithLiveness> appView,
       CodeRewriter codeRewriter,
       StringOptimizer stringOptimizer,
+      EnumValueOptimizer enumValueOptimizer,
       DexEncodedMethod method,
       IRCode code,
       OptimizationFeedback feedback,
@@ -282,7 +284,7 @@
       appView.withGeneratedMessageLiteBuilderShrinker(
           shrinker ->
               shrinker.inlineCallsToDynamicMethod(
-                  method, code, codeRewriter, feedback, methodProcessor, inliner));
+                  method, code, enumValueOptimizer, feedback, methodProcessor, inliner));
     }
 
     if (anyInlinedMethods) {
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 9231d05..f076b41 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
@@ -160,7 +160,8 @@
       if (eligibleClass.classInitializationMayHaveSideEffects(
           appView,
           // Types that are a super type of the current context are guaranteed to be initialized.
-          type -> appView.isSubtype(method.method.holder, type).isTrue())) {
+          type -> appView.isSubtype(method.method.holder, type).isTrue(),
+          Sets.newIdentityHashSet())) {
         return EligibilityStatus.HAS_CLINIT;
       }
       return EligibilityStatus.ELIGIBLE;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumInfoMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInfoMapCollector.java
similarity index 96%
rename from src/main/java/com/android/tools/r8/ir/optimize/EnumInfoMapCollector.java
rename to src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInfoMapCollector.java
index 2472589..e0991e0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumInfoMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInfoMapCollector.java
@@ -1,7 +1,7 @@
-// Copyright (c) 2017, 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.
-package com.android.tools.r8.ir.optimize;
+package com.android.tools.r8.ir.optimize.enums;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
similarity index 96%
rename from src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
rename to src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index c38b7b0..b3a25d0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.ir.optimize;
+package com.android.tools.r8.ir.optimize.enums;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -143,7 +143,13 @@
       assert enumClass != null;
 
       DexEncodedMethod initializer = enumClass.lookupDirectMethod(factory.enumMethods.constructor);
-      assert initializer != null;
+      if (initializer == null) {
+        // This case typically happens when a programmer uses EnumSet/EnumMap without using the
+        // enum keep rules. The code is incorrect in this case (EnumSet/EnumMap won't work).
+        // We bail out.
+        markEnumAsUnboxable(Reason.NO_INIT, enumClass);
+        continue;
+      }
       if (initializer.getOptimizationInfo().mayHaveSideEffects()) {
         markEnumAsUnboxable(Reason.INVALID_INIT, enumClass);
         continue;
@@ -325,6 +331,7 @@
     VIRTUAL_METHOD,
     UNEXPECTED_DIRECT_METHOD,
     INVALID_PHI,
+    NO_INIT,
     INVALID_INIT,
     INVALID_CLINIT,
     INVALID_INVOKE,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
similarity index 97%
rename from src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java
rename to src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index 3fb84e1..7b207c6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.ir.optimize;
+package com.android.tools.r8.ir.optimize.enums;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -10,7 +10,7 @@
 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.optimize.EnumUnboxer.Reason;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxer.Reason;
 import com.google.common.collect.Sets;
 import java.util.Set;
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
new file mode 100644
index 0000000..c8122470
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -0,0 +1,268 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.enums;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ArrayGet;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.IntSwitch;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.JumpInstruction;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.SwitchMapCollector;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
+import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import java.util.Arrays;
+import java.util.Map;
+
+public class EnumValueOptimizer {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final DexItemFactory factory;
+
+  public EnumValueOptimizer(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+    this.factory = appView.dexItemFactory();
+  }
+
+  @SuppressWarnings("ConstantConditions")
+  public void rewriteConstantEnumMethodCalls(IRCode code) {
+    if (!code.metadata().mayHaveInvokeMethodWithReceiver()) {
+      return;
+    }
+
+    InstructionListIterator iterator = code.instructionListIterator();
+    while (iterator.hasNext()) {
+      Instruction current = iterator.next();
+
+      if (!current.isInvokeMethodWithReceiver()) {
+        continue;
+      }
+      InvokeMethodWithReceiver methodWithReceiver = current.asInvokeMethodWithReceiver();
+      DexMethod invokedMethod = methodWithReceiver.getInvokedMethod();
+      boolean isOrdinalInvoke = invokedMethod == factory.enumMethods.ordinal;
+      boolean isNameInvoke = invokedMethod == factory.enumMethods.name;
+      boolean isToStringInvoke = invokedMethod == factory.enumMethods.toString;
+      if (!isOrdinalInvoke && !isNameInvoke && !isToStringInvoke) {
+        continue;
+      }
+
+      Value receiver = methodWithReceiver.getReceiver().getAliasedValue();
+      if (receiver.isPhi()) {
+        continue;
+      }
+      Instruction definition = receiver.getDefinition();
+      if (!definition.isStaticGet()) {
+        continue;
+      }
+      DexField enumField = definition.asStaticGet().getField();
+
+      Map<DexField, EnumValueInfo> valueInfoMap =
+          appView.appInfo().withLiveness().getEnumValueInfoMapFor(enumField.type);
+      if (valueInfoMap == null) {
+        continue;
+      }
+
+      // The receiver value is identified as being from a constant enum field lookup by the fact
+      // that it is a static-get to a field whose type is the same as the enclosing class (which
+      // is known to be an enum type). An enum may still define a static field using the enum type
+      // so ensure the field is present in the ordinal map for final validation.
+      EnumValueInfo valueInfo = valueInfoMap.get(enumField);
+      if (valueInfo == null) {
+        continue;
+      }
+
+      Value outValue = methodWithReceiver.outValue();
+      if (isOrdinalInvoke) {
+        iterator.replaceCurrentInstruction(new ConstNumber(outValue, valueInfo.ordinal));
+      } else if (isNameInvoke) {
+        iterator.replaceCurrentInstruction(
+            new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
+      } else {
+        assert isToStringInvoke;
+        DexClass enumClazz = appView.appInfo().definitionFor(enumField.type);
+        if (!enumClazz.accessFlags.isFinal()) {
+          continue;
+        }
+        DexEncodedMethod singleTarget =
+            appView
+                .appInfo()
+                .resolveMethodOnClass(valueInfo.type, factory.objectMethods.toString)
+                .getSingleTarget();
+        if (singleTarget != null && singleTarget.method != factory.enumMethods.toString) {
+          continue;
+        }
+        iterator.replaceCurrentInstruction(
+            new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
+      }
+    }
+    assert code.isConsistentSSA();
+  }
+
+  /**
+   * Inline the indirection of switch maps into the switch statement.
+   *
+   * <p>To ensure binary compatibility, javac generated code does not use ordinal values of enums
+   * directly in switch statements but instead generates a companion class that computes a mapping
+   * from switch branches to ordinals at runtime. As we have whole-program knowledge, we can analyze
+   * these maps and inline the indirection into the switch map again.
+   *
+   * <p>In particular, we look for code of the form
+   *
+   * <blockquote>
+   *
+   * <pre>
+   * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) {
+   *   ...
+   * }
+   * </pre>
+   *
+   * </blockquote>
+   */
+  public void removeSwitchMaps(IRCode code) {
+    for (BasicBlock block : code.blocks) {
+      JumpInstruction exit = block.exit();
+      // Pattern match a switch on a switch map as input.
+      if (!exit.isIntSwitch()) {
+        continue;
+      }
+      IntSwitch switchInsn = exit.asIntSwitch();
+      EnumSwitchInfo info = analyzeSwitchOverEnum(switchInsn);
+      if (info == null) {
+        continue;
+      }
+      Int2IntMap targetMap = new Int2IntArrayMap();
+      for (int i = 0; i < switchInsn.numberOfKeys(); i++) {
+        assert switchInsn.targetBlockIndices()[i] != switchInsn.getFallthroughBlockIndex();
+        EnumValueInfo valueInfo = info.valueInfoMap.get(info.indexMap.get(switchInsn.getKey(i)));
+        targetMap.put(valueInfo.ordinal, switchInsn.targetBlockIndices()[i]);
+      }
+      int[] keys = targetMap.keySet().toIntArray();
+      Arrays.sort(keys);
+      int[] targets = new int[keys.length];
+      for (int i = 0; i < keys.length; i++) {
+        targets[i] = targetMap.get(keys[i]);
+      }
+
+      IntSwitch newSwitch =
+          new IntSwitch(
+              info.ordinalInvoke.outValue(), keys, targets, switchInsn.getFallthroughBlockIndex());
+      // Replace the switch itself.
+      exit.replace(newSwitch, code);
+      // If the original input to the switch is now unused, remove it too. It is not dead
+      // as it might have side-effects but we ignore these here.
+      Instruction arrayGet = info.arrayGet;
+      if (!arrayGet.outValue().hasUsers()) {
+        arrayGet.inValues().forEach(v -> v.removeUser(arrayGet));
+        arrayGet.getBlock().removeInstruction(arrayGet);
+      }
+      Instruction staticGet = info.staticGet;
+      if (!staticGet.outValue().hasUsers()) {
+        assert staticGet.inValues().isEmpty();
+        staticGet.getBlock().removeInstruction(staticGet);
+      }
+    }
+  }
+
+  private static final class EnumSwitchInfo {
+
+    final DexType enumClass;
+    final Instruction ordinalInvoke;
+    final Instruction arrayGet;
+    public final Instruction staticGet;
+    final Int2ReferenceMap<DexField> indexMap;
+    final Map<DexField, EnumValueInfo> valueInfoMap;
+
+    private EnumSwitchInfo(
+        DexType enumClass,
+        Instruction ordinalInvoke,
+        Instruction arrayGet,
+        Instruction staticGet,
+        Int2ReferenceMap<DexField> indexMap,
+        Map<DexField, EnumValueInfo> valueInfoMap) {
+      this.enumClass = enumClass;
+      this.ordinalInvoke = ordinalInvoke;
+      this.arrayGet = arrayGet;
+      this.staticGet = staticGet;
+      this.indexMap = indexMap;
+      this.valueInfoMap = valueInfoMap;
+    }
+  }
+
+  /**
+   * Looks for a switch statement over the enum companion class of the form
+   *
+   * <blockquote>
+   *
+   * <pre>
+   * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) {
+   *   ...
+   * }
+   * </pre>
+   *
+   * </blockquote>
+   *
+   * and extracts the components and the index and ordinal maps. See {@link EnumInfoMapCollector}
+   * and {@link SwitchMapCollector} for details.
+   */
+  private EnumSwitchInfo analyzeSwitchOverEnum(Instruction switchInsn) {
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    Instruction input = switchInsn.inValues().get(0).definition;
+    if (input == null || !input.isArrayGet()) {
+      return null;
+    }
+    ArrayGet arrayGet = input.asArrayGet();
+    Instruction index = arrayGet.index().definition;
+    if (index == null || !index.isInvokeVirtual()) {
+      return null;
+    }
+    InvokeVirtual ordinalInvoke = index.asInvokeVirtual();
+    DexMethod ordinalMethod = ordinalInvoke.getInvokedMethod();
+    DexClass enumClass = appInfo.definitionFor(ordinalMethod.holder);
+    DexItemFactory dexItemFactory = appInfo.dexItemFactory();
+    // After member rebinding, enumClass will be the actual java.lang.Enum class.
+    if (enumClass == null
+        || (!enumClass.accessFlags.isEnum() && enumClass.type != dexItemFactory.enumType)
+        || ordinalMethod.name != dexItemFactory.ordinalMethodName
+        || ordinalMethod.proto.returnType != dexItemFactory.intType
+        || !ordinalMethod.proto.parameters.isEmpty()) {
+      return null;
+    }
+    Instruction array = arrayGet.array().definition;
+    if (array == null || !array.isStaticGet()) {
+      return null;
+    }
+    StaticGet staticGet = array.asStaticGet();
+    Int2ReferenceMap<DexField> indexMap = appInfo.getSwitchMapFor(staticGet.getField());
+    if (indexMap == null || indexMap.isEmpty()) {
+      return null;
+    }
+    // Due to member rebinding, only the fields are certain to provide the actual enums
+    // class.
+    DexType enumType = indexMap.values().iterator().next().holder;
+    Map<DexField, EnumValueInfo> valueInfoMap = appInfo.getEnumValueInfoMapFor(enumType);
+    if (valueInfoMap == null) {
+      return null;
+    }
+    return new EnumSwitchInfo(enumType, ordinalInvoke, arrayGet, staticGet, indexMap, valueInfoMap);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index d1a6c3a..b5215f6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -84,6 +84,7 @@
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerReceiverAnalysis;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsageBuilder;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.NonTrivialInstanceInitializerInfo;
@@ -120,7 +121,8 @@
       DexEncodedMethod method,
       IRCode code,
       OptimizationFeedback feedback,
-      DynamicTypeOptimization dynamicTypeOptimization) {
+      DynamicTypeOptimization dynamicTypeOptimization,
+      InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos) {
     identifyClassInlinerEligibility(method, code, feedback);
     identifyParameterUsages(method, code, feedback);
     identifyReturnsArgument(method, code, feedback);
@@ -129,7 +131,7 @@
     }
     computeDynamicReturnType(dynamicTypeOptimization, feedback, method, code);
     computeInitializedClassesOnNormalExit(feedback, method, code);
-    computeInstanceInitializerInfo(method, code, feedback);
+    computeInstanceInitializerInfo(method, code, feedback, instanceFieldInitializationInfos);
     computeMayHaveSideEffects(feedback, method, code);
     computeReturnValueOnlyDependsOnArguments(feedback, method, code);
     computeNonNullParamOrThrow(feedback, method, code);
@@ -336,10 +338,7 @@
       if (!aliasedValue.isPhi()) {
         Instruction definition = aliasedValue.definition;
         if (definition.isArgument()) {
-          // Find the argument number.
-          int index = aliasedValue.computeArgumentPosition(code);
-          assert index >= 0;
-          feedback.methodReturnsArgument(method, index);
+          feedback.methodReturnsArgument(method, definition.asArgument().getIndex());
         }
         DexType context = method.method.holder;
         AbstractValue abstractReturnValue = definition.getAbstractValue(appView, context);
@@ -354,13 +353,18 @@
   }
 
   private void computeInstanceInitializerInfo(
-      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+      DexEncodedMethod method,
+      IRCode code,
+      OptimizationFeedback feedback,
+      InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos) {
     assert !appView.appInfo().isPinned(method.method);
 
     if (!method.isInstanceInitializer()) {
       return;
     }
 
+    assert instanceFieldInitializationInfos != null;
+
     if (method.accessFlags.isNative()) {
       return;
     }
@@ -375,7 +379,10 @@
       return;
     }
 
-    InstanceInitializerInfo instanceInitializerInfo = analyzeInstanceInitializer(code, clazz);
+    NonTrivialInstanceInitializerInfo.Builder builder =
+        NonTrivialInstanceInitializerInfo.builder(instanceFieldInitializationInfos);
+    InstanceInitializerInfo instanceInitializerInfo =
+        analyzeInstanceInitializer(code, clazz, builder);
     feedback.setInstanceInitializerInfo(
         method,
         instanceInitializerInfo != null
@@ -398,13 +405,13 @@
   // ** Assigns arguments or non-throwing constants to fields of this class.
   //
   // (Note that this initializer does not have to have zero arguments.)
-  private InstanceInitializerInfo analyzeInstanceInitializer(IRCode code, DexClass clazz) {
+  private InstanceInitializerInfo analyzeInstanceInitializer(
+      IRCode code, DexClass clazz, NonTrivialInstanceInitializerInfo.Builder builder) {
     if (clazz.definesFinalizer(options.itemFactory)) {
       // Defining a finalize method can observe the side-effect of Object.<init> GC registration.
       return null;
     }
 
-    NonTrivialInstanceInitializerInfo.Builder builder = NonTrivialInstanceInitializerInfo.builder();
     Value receiver = code.getThis();
     boolean hasCatchHandler = false;
     for (BasicBlock block : code.blocks) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
index b65a579..46cff7b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
@@ -9,9 +9,11 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.conversion.FieldOptimizationFeedback;
 import com.android.tools.r8.ir.conversion.MethodOptimizationFeedback;
+import com.android.tools.r8.shaking.AppInfoWithLivenessModifier;
 import com.android.tools.r8.utils.ThreadUtils;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
 
 public abstract class OptimizationFeedback
     implements FieldOptimizationFeedback, MethodOptimizationFeedback {
@@ -34,4 +36,8 @@
         },
         executorService);
   }
+
+  public void modifyAppInfoWithLiveness(Consumer<AppInfoWithLivenessModifier> consumer) {
+    // Intentionally empty.
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 0fec1f7..c241c0a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.AppInfoWithLivenessModifier;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.BitSet;
@@ -23,10 +24,13 @@
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
 
 public class OptimizationFeedbackDelayed extends OptimizationFeedback {
 
   // Caching of updated optimization info and processed status.
+  private final AppInfoWithLivenessModifier appInfoWithLivenessModifier =
+      AppInfoWithLiveness.modifier();
   private final Map<DexEncodedField, MutableFieldOptimizationInfo> fieldOptimizationInfos =
       new IdentityHashMap<>();
   private final Map<DexEncodedMethod, UpdatableMethodOptimizationInfo> methodOptimizationInfos =
@@ -63,6 +67,15 @@
     super.fixupOptimizationInfos(appView, executorService, fixer);
   }
 
+  @Override
+  public void modifyAppInfoWithLiveness(Consumer<AppInfoWithLivenessModifier> consumer) {
+    consumer.accept(appInfoWithLivenessModifier);
+  }
+
+  public void refineAppInfoWithLiveness(AppInfoWithLiveness appInfo) {
+    appInfoWithLivenessModifier.modify(appInfo);
+  }
+
   public void updateVisibleOptimizationInfo() {
     // Remove methods that have become obsolete. A method may become obsolete, for example, as a
     // result of the class staticizer, which aims to transform virtual methods on companion classes
@@ -85,6 +98,7 @@
   }
 
   public boolean noUpdatesLeft() {
+    assert appInfoWithLivenessModifier.isEmpty();
     assert fieldOptimizationInfos.isEmpty()
         : StringUtils.join(fieldOptimizationInfos.keySet(), ", ");
     assert methodOptimizationInfos.isEmpty()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/EmptyInstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/EmptyInstanceFieldInitializationInfoCollection.java
new file mode 100644
index 0000000..54a50fe
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/EmptyInstanceFieldInitializationInfoCollection.java
@@ -0,0 +1,34 @@
+// 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.graph.DexEncodedField;
+
+/**
+ * Represents that no information is known about the way a constructor initializes the instance
+ * fields of the newly created instance.
+ */
+public class EmptyInstanceFieldInitializationInfoCollection
+    extends InstanceFieldInitializationInfoCollection {
+
+  private static final EmptyInstanceFieldInitializationInfoCollection INSTANCE =
+      new EmptyInstanceFieldInitializationInfoCollection();
+
+  private EmptyInstanceFieldInitializationInfoCollection() {}
+
+  public static EmptyInstanceFieldInitializationInfoCollection getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public InstanceFieldInitializationInfo get(DexEncodedField field) {
+    return UnknownInstanceFieldInitializationInfo.getInstance();
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return true;
+  }
+}
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
new file mode 100644
index 0000000..c56c562
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
@@ -0,0 +1,33 @@
+// 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;
+
+/**
+ * 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 {
+
+  private final int argumentIndex;
+
+  /** Intentionally package private, use {@link InstanceFieldInitializationInfoFactory} instead. */
+  InstanceFieldArgumentInitializationInfo(int argumentIndex) {
+    this.argumentIndex = argumentIndex;
+  }
+
+  public int getArgumentIndex() {
+    return argumentIndex;
+  }
+
+  @Override
+  public boolean isArgumentInitializationInfo() {
+    return true;
+  }
+
+  @Override
+  public InstanceFieldArgumentInitializationInfo asArgumentInitializationInfo() {
+    return this;
+  }
+}
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
new file mode 100644
index 0000000..312d7a0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java
@@ -0,0 +1,27 @@
+// 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;
+
+/**
+ * Information about the way a constructor initializes an instance field on the newly created
+ * instance.
+ *
+ * <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 boolean isArgumentInitializationInfo() {
+    return false;
+  }
+
+  public InstanceFieldArgumentInitializationInfo asArgumentInitializationInfo() {
+    return null;
+  }
+
+  public boolean isUnknown() {
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
new file mode 100644
index 0000000..4912d46
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.info.field;
+
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+/**
+ * A mapping from instance fields of a class to information about how a particular constructor
+ * initializes these instance fields.
+ *
+ * <p>Returns {@link UnknownInstanceFieldInitializationInfo} if no information is known about the
+ * initialization of a given instance field.
+ */
+public abstract class InstanceFieldInitializationInfoCollection {
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public abstract InstanceFieldInitializationInfo get(DexEncodedField field);
+
+  public abstract boolean isEmpty();
+
+  public static class Builder {
+
+    Map<DexField, InstanceFieldInitializationInfo> infos = new IdentityHashMap<>();
+
+    public void recordInitializationInfo(
+        DexEncodedField field, InstanceFieldInitializationInfo info) {
+      assert !infos.containsKey(field.field);
+      infos.put(field.field, info);
+    }
+
+    public InstanceFieldInitializationInfoCollection build() {
+      if (infos.isEmpty()) {
+        return EmptyInstanceFieldInitializationInfoCollection.getInstance();
+      }
+      return new NonTrivialInstanceFieldInitializationInfoCollection(infos);
+    }
+  }
+}
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
new file mode 100644
index 0000000..42209b4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoFactory.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.info.field;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+public class InstanceFieldInitializationInfoFactory {
+
+  private ConcurrentHashMap<Integer, InstanceFieldArgumentInitializationInfo>
+      argumentInitializationInfos = new ConcurrentHashMap<>();
+
+  public InstanceFieldArgumentInitializationInfo createArgumentInitializationInfo(
+      int argumentIndex) {
+    return argumentInitializationInfos.computeIfAbsent(
+        argumentIndex, InstanceFieldArgumentInitializationInfo::new);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
new file mode 100644
index 0000000..bdaaf78
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
@@ -0,0 +1,33 @@
+// 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.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import java.util.Map;
+
+/** See {@link InstanceFieldArgumentInitializationInfo}. */
+public class NonTrivialInstanceFieldInitializationInfoCollection
+    extends InstanceFieldInitializationInfoCollection {
+
+  private final Map<DexField, InstanceFieldInitializationInfo> infos;
+
+  NonTrivialInstanceFieldInitializationInfoCollection(
+      Map<DexField, InstanceFieldInitializationInfo> infos) {
+    assert !infos.isEmpty();
+    assert infos.values().stream().noneMatch(InstanceFieldInitializationInfo::isUnknown);
+    this.infos = infos;
+  }
+
+  @Override
+  public InstanceFieldInitializationInfo get(DexEncodedField field) {
+    return infos.getOrDefault(field.field, UnknownInstanceFieldInitializationInfo.getInstance());
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return false;
+  }
+}
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
new file mode 100644
index 0000000..66e882e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
@@ -0,0 +1,26 @@
+// 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;
+
+/**
+ * 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 {
+
+  private static final UnknownInstanceFieldInitializationInfo INSTANCE =
+      new UnknownInstanceFieldInitializationInfo();
+
+  private UnknownInstanceFieldInitializationInfo() {}
+
+  public static UnknownInstanceFieldInitializationInfo getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public boolean isUnknown() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
index ef3ca08..20aff53 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
@@ -7,6 +7,8 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
+import com.android.tools.r8.ir.optimize.info.field.EmptyInstanceFieldInitializationInfoCollection;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 
 public class DefaultInstanceInitializerInfo extends InstanceInitializerInfo {
 
@@ -30,6 +32,11 @@
   }
 
   @Override
+  public InstanceFieldInitializationInfoCollection fieldInitializationInfos() {
+    return EmptyInstanceFieldInitializationInfoCollection.getInstance();
+  }
+
+  @Override
   public AbstractFieldSet readSet() {
     return UnknownFieldSet.getInstance();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
index 5452bce..b27888e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
@@ -6,11 +6,14 @@
 
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 
 public abstract class InstanceInitializerInfo {
 
   public abstract DexMethod getParent();
 
+  public abstract InstanceFieldInitializationInfoCollection fieldInitializationInfos();
+
   /**
    * Returns an abstraction of the set of fields that may be as a result of executing this
    * initializer.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
index 70017cc..4e19cef 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.ConcreteMutableFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 
 public final class NonTrivialInstanceInitializerInfo extends InstanceInitializerInfo {
 
@@ -18,12 +19,18 @@
   private static final int RECEIVER_NEVER_ESCAPE_OUTSIDE_CONSTRUCTOR_CHAIN = 1 << 2;
 
   private final int data;
+  private final InstanceFieldInitializationInfoCollection fieldInitializationInfos;
   private final AbstractFieldSet readSet;
   private final DexMethod parent;
 
-  private NonTrivialInstanceInitializerInfo(int data, AbstractFieldSet readSet, DexMethod parent) {
+  private NonTrivialInstanceInitializerInfo(
+      int data,
+      InstanceFieldInitializationInfoCollection fieldInitializationInfos,
+      AbstractFieldSet readSet,
+      DexMethod parent) {
     assert verifyNoUnknownBits(data);
     this.data = data;
+    this.fieldInitializationInfos = fieldInitializationInfos;
     this.readSet = readSet;
     this.parent = parent;
   }
@@ -37,8 +44,9 @@
     return true;
   }
 
-  public static Builder builder() {
-    return new Builder();
+  public static Builder builder(
+      InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos) {
+    return new Builder(instanceFieldInitializationInfos);
   }
 
   @Override
@@ -47,6 +55,11 @@
   }
 
   @Override
+  public InstanceFieldInitializationInfoCollection fieldInitializationInfos() {
+    return fieldInitializationInfos;
+  }
+
+  @Override
   public AbstractFieldSet readSet() {
     return readSet;
   }
@@ -68,6 +81,8 @@
 
   public static class Builder {
 
+    private final InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos;
+
     private int data =
         INSTANCE_FIELD_INITIALIZATION_INDEPENDENT_OF_ENVIRONMENT
             | NO_OTHER_SIDE_EFFECTS_THAN_INSTANCE_FIELD_ASSIGNMENTS
@@ -75,8 +90,15 @@
     private AbstractFieldSet readSet = EmptyFieldSet.getInstance();
     private DexMethod parent;
 
+    public Builder(InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos) {
+      this.instanceFieldInitializationInfos = instanceFieldInitializationInfos;
+    }
+
     private boolean isTrivial() {
-      return data == 0 && readSet.isTop() && parent == null;
+      return instanceFieldInitializationInfos.isEmpty()
+          && data == 0
+          && readSet.isTop()
+          && parent == null;
     }
 
     public Builder markFieldAsRead(DexEncodedField field) {
@@ -158,7 +180,8 @@
     public InstanceInitializerInfo build() {
       return isTrivial()
           ? DefaultInstanceInitializerInfo.getInstance()
-          : new NonTrivialInstanceInitializerInfo(data, readSet, parent);
+          : new NonTrivialInstanceInitializerInfo(
+              data, instanceFieldInitializationInfos, readSet, parent);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index 98e83c4..ed47e54 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -156,11 +156,8 @@
 
   @Override
   public final void buildPrelude(IRBuilder builder) {
-    DexSourceCode.buildArgumentsWithUnusedArgumentStubs(
-        builder,
-        0,
-        builder.getMethod(),
-        DexSourceCode::doNothingWriteConsumer);
+    builder.buildArgumentsWithUnusedArgumentStubs(
+        0, builder.getMethod(), DexSourceCode::doNothingWriteConsumer);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index 48f29c2..6085350 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -42,9 +42,7 @@
   }
 
   @Override
-  void processMetadata() {
-    assert !isProcessed;
-    isProcessed = true;
+  void processMetadata(KotlinClassMetadata.Class metadata) {
     kmClass = metadata.toKmClass();
   }
 
@@ -142,4 +140,8 @@
     return this;
   }
 
+  @Override
+  public String toString() {
+    return clazz.toString() + ": " + kmClass.toString();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
index 07ec533..4433017 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -4,15 +4,26 @@
 
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedBinaryName;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 public final class KotlinClassFacade extends KotlinInfo<KotlinClassMetadata.MultiFileClassFacade> {
 
+  // TODO(b/70169921): is it better to maintain List<DexType>?
+  List<String> partClassNames;
+
   static KotlinClassFacade fromKotlinClassMetadata(
       KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
     assert kotlinClassMetadata instanceof KotlinClassMetadata.MultiFileClassFacade;
@@ -26,24 +37,32 @@
   }
 
   @Override
-  void processMetadata() {
-    assert !isProcessed;
-    isProcessed = true;
-    // No API to explore metadata details, hence nothing to do further.
+  void processMetadata(KotlinClassMetadata.MultiFileClassFacade metadata) {
+    // Part Class names are stored in `d1`, which is immutable. Make a copy instead.
+    partClassNames = new ArrayList<>(metadata.getPartClassNames());
+    // No API to explore metadata details, hence nothing further to do.
   }
 
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    // TODO(b/70169921): no idea yet!
-    assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
-            || appView.options().enableKotlinMetadataRewritingForRenamedClasses
-        : toString();
+    ListIterator<String> partClassIterator = partClassNames.listIterator();
+    while (partClassIterator.hasNext()) {
+      String partClassName = partClassIterator.next();
+      partClassIterator.remove();
+      DexType partClassType = appView.dexItemFactory().createType(
+          DescriptorUtils.getDescriptorFromClassBinaryName(partClassName));
+      String renamedPartClassName = toRenamedBinaryName(partClassType, appView, lens);
+      if (renamedPartClassName != null) {
+        partClassIterator.add(renamedPartClassName);
+      }
+    }
   }
 
   @Override
   KotlinClassHeader createHeader() {
-    // TODO(b/70169921): may need to update if `rewrite` is implemented.
-    return metadata.getHeader();
+    KotlinClassMetadata.MultiFileClassFacade.Writer writer =
+        new KotlinClassMetadata.MultiFileClassFacade.Writer();
+    return writer.write(partClassNames).getHeader();
   }
 
   @Override
@@ -61,4 +80,9 @@
     return this;
   }
 
+  @Override
+  public String toString() {
+    return clazz.toString()
+        + ": MultiFileClassFacade(" + StringUtils.join(partClassNames, ", ") + ")";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
index 58c5aff..f108252 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -4,10 +4,14 @@
 
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedBinaryName;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
 import kotlinx.metadata.KmPackage;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -15,6 +19,8 @@
 public final class KotlinClassPart extends KotlinInfo<KotlinClassMetadata.MultiFileClassPart> {
 
   KmPackage kmPackage;
+  // TODO(b/70169921): is it better to maintain DexType?
+  String facadeClassName;
 
   static KotlinClassPart fromKotlinClassMetadata(
       KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
@@ -29,14 +35,16 @@
   }
 
   @Override
-  void processMetadata() {
-    assert !isProcessed;
-    isProcessed = true;
+  void processMetadata(KotlinClassMetadata.MultiFileClassPart metadata) {
     kmPackage = metadata.toKmPackage();
+    facadeClassName = metadata.getFacadeClassName();
   }
 
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    DexType facadeClassType = appView.dexItemFactory().createType(
+        DescriptorUtils.getDescriptorFromClassBinaryName(facadeClassName));
+    facadeClassName = toRenamedBinaryName(facadeClassType, appView, lens);
     if (!appView.options().enableKotlinMetadataRewritingForMembers) {
       return;
     }
@@ -45,10 +53,17 @@
 
   @Override
   KotlinClassHeader createHeader() {
-    KotlinClassMetadata.MultiFileClassPart.Writer writer =
-        new KotlinClassMetadata.MultiFileClassPart.Writer();
-    kmPackage.accept(writer);
-    return writer.write(metadata.getFacadeClassName()).getHeader();
+    if (facadeClassName != null) {
+      KotlinClassMetadata.MultiFileClassPart.Writer writer =
+          new KotlinClassMetadata.MultiFileClassPart.Writer();
+      kmPackage.accept(writer);
+      return writer.write(facadeClassName).getHeader();
+    } else {
+      // It's no longer part of multi-file class.
+      KotlinClassMetadata.FileFacade.Writer writer = new KotlinClassMetadata.FileFacade.Writer();
+      kmPackage.accept(writer);
+      return writer.write().getHeader();
+    }
   }
 
   @Override
@@ -66,4 +81,9 @@
     return this;
   }
 
+  @Override
+  public String toString() {
+    return clazz.toString() + ": " + kmPackage.toString()
+        + ": MultiFileClassPart(" + facadeClassName + ")";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
index 5eac335..bb4dc47 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -29,9 +29,7 @@
   }
 
   @Override
-  void processMetadata() {
-    assert !isProcessed;
-    isProcessed = true;
+  void processMetadata(KotlinClassMetadata.FileFacade metadata) {
     kmPackage = metadata.toKmPackage();
   }
 
@@ -65,4 +63,8 @@
     return this;
   }
 
+  @Override
+  public String toString() {
+    return clazz.toString() + ": " + kmPackage.toString();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
index 56d9359..e9c1eb6 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -25,19 +25,16 @@
 
 // Provides access to package/class-level Kotlin information.
 public abstract class KotlinInfo<MetadataKind extends KotlinClassMetadata> {
-  final MetadataKind metadata;
   final DexClass clazz;
-  boolean isProcessed;
 
   KotlinInfo(MetadataKind metadata, DexClass clazz) {
     assert clazz != null;
-    this.metadata = metadata;
     this.clazz = clazz;
-    processMetadata();
+    processMetadata(metadata);
   }
 
   // Subtypes will define how to process the given metadata.
-  abstract void processMetadata();
+  abstract void processMetadata(MetadataKind metadata);
 
   // Subtypes will define how to rewrite metadata after shrinking and minification.
   // Subtypes that represent subtypes of {@link KmDeclarationContainer} can use
@@ -96,12 +93,6 @@
     return isClass() || isFile() || isClassPart();
   }
 
-  @Override
-  public String toString() {
-    return (clazz != null ? clazz.toSourceString() : "<null class?!>")
-        + ": " + metadata.toString();
-  }
-
   // {@link KmClass} and {@link KmPackage} are inherited from {@link KmDeclarationContainer} that
   // abstract functions and properties. Rewriting of those portions can be unified here.
   void rewriteDeclarationContainer(
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
index 6e959b6..b16d90a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmFieldSignature;
 import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmMethodSignature;
+import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromDescriptor;
 import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKmType;
 import static com.android.tools.r8.kotlin.Kotlin.NAME;
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToKotlinClassifier;
@@ -44,6 +45,30 @@
     return kmType;
   }
 
+  static DexType toRenamedType(
+      DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    // For library or classpath class, synthesize @Metadata always.
+    // For a program class, make sure it is live.
+    if (!appView.appInfo().isNonProgramTypeOrLiveProgramType(type)) {
+      return null;
+    }
+    DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
+    // For library or classpath class, we should not have renamed it.
+    DexClass clazz = appView.definitionFor(type);
+    assert clazz == null || clazz.isProgramClass() || renamedType == type
+        : type.toSourceString() + " -> " + renamedType.toSourceString();
+    return renamedType;
+  }
+
+  static String toRenamedBinaryName(
+      DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    DexType renamedType = toRenamedType(type, appView, lens);
+    if (renamedType == null) {
+      return null;
+    }
+    return getBinaryNameFromDescriptor(renamedType.toDescriptorString());
+  }
+
   static String toRenamedClassifier(
       DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
     // E.g., V -> kotlin/Unit, J -> kotlin/Long, [J -> kotlin/LongArray
@@ -56,16 +81,10 @@
     if (type.isArrayType()) {
       return NAME + "/Array";
     }
-    // For library or classpath class, synthesize @Metadata always.
-    // For a program class, make sure it is live.
-    if (!appView.appInfo().isNonProgramTypeOrLiveProgramType(type)) {
+    DexType renamedType = toRenamedType(type, appView, lens);
+    if (renamedType == null) {
       return null;
     }
-    DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
-    // For library or classpath class, we should not have renamed it.
-    DexClass clazz = appView.definitionFor(type);
-    assert clazz == null || clazz.isProgramClass() || renamedType == type
-        : type.toSourceString() + " -> " + renamedType.toSourceString();
     return descriptorToKotlinClassifier(renamedType.toDescriptorString());
   }
 
@@ -125,7 +144,7 @@
             ? method.getKotlinMemberInfo().flag
             : method.accessFlags.getAsKotlinFlags();
     KmFunction kmFunction = new KmFunction(flag, renamedMethod.name.toString());
-    JvmExtensionsKt.setSignature(kmFunction, toJvmMethodSignature(method.method));
+    JvmExtensionsKt.setSignature(kmFunction, toJvmMethodSignature(renamedMethod));
     KmType kmReturnType = toRenamedKmType(method.method.proto.returnType, appView, lens);
     if (kmReturnType == null) {
       return null;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
index 28a6eb1..5cc7d2e 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -12,6 +12,9 @@
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 public final class KotlinSyntheticClass extends KotlinInfo<KotlinClassMetadata.SyntheticClass> {
+  // TODO(b/70169921): Once converted to internal data structure, this can be gone.
+  private KotlinClassMetadata.SyntheticClass metadata;
+
   public enum Flavour {
     KotlinStyleLambda,
     JavaStyleLambda,
@@ -41,9 +44,8 @@
   }
 
   @Override
-  void processMetadata() {
-    assert !isProcessed;
-    isProcessed = true;
+  void processMetadata(KotlinClassMetadata.SyntheticClass metadata) {
+    this.metadata = metadata;
     if (metadata.isLambda()) {
       // TODO(b/70169921): Use #toKmLambda to store a mutable model if needed.
     }
@@ -116,4 +118,8 @@
         && clazz.interfaces.size() == 1;
   }
 
+  @Override
+  public String toString() {
+    return clazz.toString() + ": " + metadata.toString();
+  }
 }
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 1c75720..96cd284 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -32,10 +31,11 @@
 import com.google.common.collect.Sets;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.function.Predicate;
 
 class ClassNameMinifier {
@@ -91,11 +91,14 @@
     }
   }
 
-  ClassRenaming computeRenaming(Timing timing) {
-    return computeRenaming(timing, Collections.emptyMap());
+  ClassRenaming computeRenaming(Timing timing, ExecutorService executorService)
+      throws ExecutionException {
+    return computeRenaming(timing, executorService, Collections.emptyMap());
   }
 
-  ClassRenaming computeRenaming(Timing timing, Map<DexType, DexString> syntheticClasses) {
+  ClassRenaming computeRenaming(
+      Timing timing, ExecutorService executorService, Map<DexType, DexString> syntheticClasses)
+      throws ExecutionException {
     // Externally defined synthetic classes populate an initial renaming.
     renaming.putAll(syntheticClasses);
 
@@ -139,13 +142,8 @@
     timing.begin("rename-generic");
     new GenericSignatureRewriter(appView, renaming)
         .run(
-            new Iterable<DexProgramClass>() {
-              @Override
-              public Iterator<DexProgramClass> iterator() {
-                return IteratorUtils.<DexClass, DexProgramClass>filter(
-                    classes.iterator(), DexClass::isProgramClass);
-              }
-            });
+            () -> IteratorUtils.filter(classes.iterator(), DexClass::isProgramClass),
+            executorService);
     timing.end();
 
     timing.begin("rename-arrays");
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 359de49..f2d7c1a 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -57,7 +57,7 @@
             new MinificationPackageNamingStrategy(appView),
             // Use deterministic class order to make sure renaming is deterministic.
             appView.appInfo().classesWithDeterministicOrder());
-    ClassRenaming classRenaming = classNameMinifier.computeRenaming(timing);
+    ClassRenaming classRenaming = classNameMinifier.computeRenaming(timing, executorService);
     timing.end();
 
     assert new MinifiedRenaming(
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 7b6d191..471d66f 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -137,7 +137,7 @@
             new MinificationPackageNamingStrategy(appView),
             mappedClasses);
     ClassRenaming classRenaming =
-        classNameMinifier.computeRenaming(timing, syntheticCompanionClasses);
+        classNameMinifier.computeRenaming(timing, executorService, syntheticCompanionClasses);
     timing.end();
 
     ApplyMappingMemberNamingStrategy nameStrategy =
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 e891f95..b4ddb8e 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
@@ -19,9 +19,12 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.Maps;
 import java.lang.reflect.GenericSignatureFormatError;
 import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
@@ -44,39 +47,44 @@
     this.reporter = appView.options().reporter;
   }
 
-  public void run(Iterable<? extends DexProgramClass> classes) {
-    final GenericSignatureCollector genericSignatureCollector = new GenericSignatureCollector();
-    final GenericSignatureParser<DexType> genericSignatureParser =
-        new GenericSignatureParser<>(genericSignatureCollector);
+  public void run(Iterable<? extends DexProgramClass> classes, ExecutorService executorService)
+      throws ExecutionException {
     // Classes may not be the same as appInfo().classes() if applymapping is used on classpath
     // arguments. If that is the case, the ProguardMapMinifier will pass in all classes that is
     // either ProgramClass or has a mapping. This is then transitively called inside the
     // ClassNameMinifier.
-    for (DexProgramClass clazz : classes) {
-      genericSignatureCollector.setCurrentClassContext(clazz);
-      clazz.setAnnotations(
-          rewriteGenericSignatures(
-              clazz.annotations(),
-              genericSignatureParser::parseClassSignature,
-              genericSignatureCollector::getRenamedSignature,
-              (signature, e) -> parseError(clazz, clazz.getOrigin(), signature, e)));
-      clazz.forEachField(
-          field ->
-              field.setAnnotations(
-                  rewriteGenericSignatures(
-                      field.annotations(),
-                      genericSignatureParser::parseFieldSignature,
-                      genericSignatureCollector::getRenamedSignature,
-                      (signature, e) -> parseError(field, clazz.getOrigin(), signature, e))));
-      clazz.forEachMethod(
-          method ->
-              method.setAnnotations(
-                  rewriteGenericSignatures(
-                      method.annotations(),
-                      genericSignatureParser::parseMethodSignature,
-                      genericSignatureCollector::getRenamedSignature,
-                      (signature, e) -> parseError(method, clazz.getOrigin(), signature, e))));
-    }
+    ThreadUtils.processItems(
+        classes,
+        clazz -> {
+          GenericSignatureCollector genericSignatureCollector =
+              new GenericSignatureCollector(clazz);
+          GenericSignatureParser<DexType> genericSignatureParser =
+              new GenericSignatureParser<>(genericSignatureCollector);
+          clazz.setAnnotations(
+              rewriteGenericSignatures(
+                  clazz.annotations(),
+                  genericSignatureParser::parseClassSignature,
+                  genericSignatureCollector::getRenamedSignature,
+                  (signature, e) -> parseError(clazz, clazz.getOrigin(), signature, e)));
+          clazz.forEachField(
+              field ->
+                  field.setAnnotations(
+                      rewriteGenericSignatures(
+                          field.annotations(),
+                          genericSignatureParser::parseFieldSignature,
+                          genericSignatureCollector::getRenamedSignature,
+                          (signature, e) -> parseError(field, clazz.getOrigin(), signature, e))));
+          clazz.forEachMethod(
+              method ->
+                  method.setAnnotations(
+                      rewriteGenericSignatures(
+                          method.annotations(),
+                          genericSignatureParser::parseMethodSignature,
+                          genericSignatureCollector::getRenamedSignature,
+                          (signature, e) -> parseError(method, clazz.getOrigin(), signature, e))));
+        },
+        executorService
+    );
   }
 
   private DexAnnotationSet rewriteGenericSignatures(
@@ -154,17 +162,17 @@
 
   private class GenericSignatureCollector implements GenericSignatureAction<DexType> {
     private StringBuilder renamedSignature;
-    private DexProgramClass currentClassContext;
+    private final DexProgramClass currentClassContext;
     private DexType lastWrittenType = null;
 
+    GenericSignatureCollector(DexProgramClass clazz) {
+      this.currentClassContext = clazz;
+    }
+
     String getRenamedSignature() {
       return renamedSignature.toString();
     }
 
-    void setCurrentClassContext(DexProgramClass clazz) {
-      currentClassContext = clazz;
-    }
-
     @Override
     public void parsedSymbol(char symbol) {
       if (symbol == ';' && lastWrittenType == null) {
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 69ed3b0..509a832 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -504,6 +504,10 @@
 
   private boolean dontAssertDefinitionFor = true;
 
+  public static AppInfoWithLivenessModifier modifier() {
+    return new AppInfoWithLivenessModifier();
+  }
+
   @Override
   public void enableDefinitionForAssert() {
     dontAssertDefinitionFor = false;
@@ -715,11 +719,19 @@
     return fieldAccessInfoCollection;
   }
 
+  FieldAccessInfoCollectionImpl getMutableFieldAccessInfoCollection() {
+    return fieldAccessInfoCollection;
+  }
+
   /** This method provides immutable access to `objectAllocationInfoCollection`. */
   public ObjectAllocationInfoCollection getObjectAllocationInfoCollection() {
     return objectAllocationInfoCollection;
   }
 
+  ObjectAllocationInfoCollectionImpl getMutableObjectAllocationInfoCollection() {
+    return objectAllocationInfoCollection;
+  }
+
   private boolean assertNoItemRemoved(Collection<DexReference> items, Collection<DexType> types) {
     Set<DexType> typeSet = ImmutableSet.copyOf(types);
     for (DexReference item : items) {
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java
new file mode 100644
index 0000000..75c27fd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
+import com.android.tools.r8.graph.FieldAccessInfoImpl;
+import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+/** Used to mutate AppInfoWithLiveness between waves. */
+public class AppInfoWithLivenessModifier {
+
+  private final Set<DexProgramClass> noLongerInstantiatedClasses = Sets.newConcurrentHashSet();
+  private final Set<DexField> noLongerWrittenFields = Sets.newConcurrentHashSet();
+
+  AppInfoWithLivenessModifier() {}
+
+  public boolean isEmpty() {
+    return noLongerInstantiatedClasses.isEmpty();
+  }
+
+  public void removeInstantiatedType(DexProgramClass clazz) {
+    noLongerInstantiatedClasses.add(clazz);
+  }
+
+  public void removeWrittenField(DexField field) {
+    noLongerWrittenFields.add(field);
+  }
+
+  public void modify(AppInfoWithLiveness appInfo) {
+    // Instantiated classes.
+    ObjectAllocationInfoCollectionImpl objectAllocationInfoCollection =
+        appInfo.getMutableObjectAllocationInfoCollection();
+    noLongerInstantiatedClasses.forEach(objectAllocationInfoCollection::markNoLongerInstantiated);
+
+    // Written fields.
+    FieldAccessInfoCollectionImpl fieldAccessInfoCollection =
+        appInfo.getMutableFieldAccessInfoCollection();
+    noLongerWrittenFields.forEach(
+        field -> {
+          FieldAccessInfoImpl fieldAccessInfo = fieldAccessInfoCollection.get(field);
+          if (fieldAccessInfo != null) {
+            fieldAccessInfo.clearWrites();
+          }
+        });
+
+    clear();
+  }
+
+  private void clear() {
+    noLongerInstantiatedClasses.clear();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index f05d068..dfcee69 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -32,11 +32,11 @@
   }
 
   public DexProgramClass getContextHolder() {
-    return context.holder;
+    return context.getHolder();
   }
 
   public DexEncodedMethod getContextMethod() {
-    return context.method;
+    return context.getMethod();
   }
 
   @Override
@@ -66,12 +66,12 @@
 
   @Override
   public boolean registerInstanceFieldWrite(DexField field) {
-    return enqueuer.traceInstanceFieldWrite(field, context.method);
+    return enqueuer.traceInstanceFieldWrite(field, context.getMethod());
   }
 
   @Override
   public boolean registerInstanceFieldRead(DexField field) {
-    return enqueuer.traceInstanceFieldRead(field, context.method);
+    return enqueuer.traceInstanceFieldRead(field, context.getMethod());
   }
 
   @Override
@@ -81,33 +81,33 @@
 
   @Override
   public boolean registerStaticFieldRead(DexField field) {
-    return enqueuer.traceStaticFieldRead(field, context.method);
+    return enqueuer.traceStaticFieldRead(field, context.getMethod());
   }
 
   @Override
   public boolean registerStaticFieldWrite(DexField field) {
-    return enqueuer.traceStaticFieldWrite(field, context.method);
+    return enqueuer.traceStaticFieldWrite(field, context.getMethod());
   }
 
   @Override
   public boolean registerConstClass(DexType type) {
-    return enqueuer.traceConstClass(type, context.method);
+    return enqueuer.traceConstClass(type, context.getMethod());
   }
 
   @Override
   public boolean registerCheckCast(DexType type) {
-    return enqueuer.traceCheckCast(type, context.method);
+    return enqueuer.traceCheckCast(type, context.getMethod());
   }
 
   @Override
   public boolean registerTypeReference(DexType type) {
-    return enqueuer.traceTypeReference(type, context.method);
+    return enqueuer.traceTypeReference(type, context.getMethod());
   }
 
   @Override
   public void registerMethodHandle(DexMethodHandle methodHandle, MethodHandleUse use) {
     super.registerMethodHandle(methodHandle, use);
-    enqueuer.traceMethodHandle(methodHandle, use, context.method);
+    enqueuer.traceMethodHandle(methodHandle, use, context.getMethod());
   }
 
   @Override
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 d0e971e..a7bb64b 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -27,7 +27,6 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.Descriptor;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexCallSite;
@@ -41,6 +40,7 @@
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexLibraryClass;
+import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -106,6 +106,7 @@
 import java.lang.reflect.InvocationHandler;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Deque;
 import java.util.HashMap;
@@ -171,6 +172,9 @@
   private ProguardClassFilter dontWarnPatterns;
   private final EnqueuerUseRegistryFactory useRegistryFactory;
 
+  private final Map<DexProgramClass, Set<DexProgramClass>> immediateSubtypesOfLiveTypes =
+      new IdentityHashMap<>();
+
   private final Map<DexMethod, Set<DexEncodedMethod>> virtualInvokes = new IdentityHashMap<>();
   private final Map<DexMethod, Set<DexEncodedMethod>> interfaceInvokes = new IdentityHashMap<>();
   private final Map<DexMethod, Set<DexEncodedMethod>> superInvokes = new IdentityHashMap<>();
@@ -365,9 +369,8 @@
     instantiatedInterfaceTypes = Sets.newIdentityHashSet();
     lambdaRewriter = options.desugarState == DesugarState.ON ? new LambdaRewriter(appView) : null;
 
-    // TODO(b/147799448): Enable allocation site tracking during the initial round of tree shaking.
     objectAllocationInfoCollection =
-        ObjectAllocationInfoCollectionImpl.builder(false, graphReporter);
+        ObjectAllocationInfoCollectionImpl.builder(mode.isInitialTreeShaking(), graphReporter);
 
     if (appView.rewritePrefix.isRewriting() && mode.isInitialTreeShaking()) {
       desugaredLibraryWrapperAnalysis = new DesugaredLibraryConversionWrapperAnalysis(appView);
@@ -636,13 +639,14 @@
       bootstrapMethods.add(callSite.bootstrapMethod.asMethod());
     }
 
-    LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo, context.holder);
+    DexProgramClass contextHolder = context.getHolder();
+    LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo, contextHolder);
     if (descriptor == null) {
       if (!appInfo.isStringConcat(callSite.bootstrapMethod)) {
         if (options.reporter != null) {
           Diagnostic message =
               new StringDiagnostic(
-                  "Unknown bootstrap method " + callSite.bootstrapMethod, context.holder.origin);
+                  "Unknown bootstrap method " + callSite.bootstrapMethod, contextHolder.origin);
           options.reporter.warning(message);
         }
       }
@@ -651,22 +655,23 @@
       return;
     }
 
+    DexEncodedMethod contextMethod = context.getMethod();
     for (DexType lambdaInstantiatedInterface : descriptor.interfaces) {
-      markLambdaInstantiated(lambdaInstantiatedInterface, context.method);
+      markLambdaInstantiated(lambdaInstantiatedInterface, contextMethod);
     }
 
     if (lambdaRewriter != null) {
-      assert context.method.getCode().isCfCode() : "Unexpected input type with lambdas";
-      CfCode code = context.method.getCode().asCfCode();
+      assert contextMethod.getCode().isCfCode() : "Unexpected input type with lambdas";
+      CfCode code = contextMethod.getCode().asCfCode();
       if (code != null) {
         LambdaClass lambdaClass =
-            lambdaRewriter.getOrCreateLambdaClass(descriptor, context.method.method.holder);
+            lambdaRewriter.getOrCreateLambdaClass(descriptor, contextMethod.method.holder);
         lambdaClasses.put(lambdaClass.type, lambdaClass);
         lambdaCallSites
-            .computeIfAbsent(context.method, k -> new IdentityHashMap<>())
+            .computeIfAbsent(contextMethod, k -> new IdentityHashMap<>())
             .put(callSite, lambdaClass);
         if (lambdaClass.descriptor.interfaces.contains(appView.dexItemFactory().serializableType)) {
-          classesWithSerializableLambdas.add(context.holder);
+          classesWithSerializableLambdas.add(contextHolder);
         }
       }
       if (descriptor.delegatesToLambdaImplMethod()) {
@@ -794,8 +799,8 @@
   }
 
   boolean traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context) {
-    DexProgramClass currentHolder = context.holder;
-    DexEncodedMethod currentMethod = context.method;
+    DexProgramClass currentHolder = context.getHolder();
+    DexEncodedMethod currentMethod = context.getMethod();
     boolean skipTracing =
         registerDeferredActionForDeadProtoBuilder(
             invokedMethod.holder,
@@ -827,12 +832,12 @@
 
   boolean traceInvokeDirectFromLambda(DexMethod invokedMethod, ProgramMethod context) {
     return traceInvokeDirect(
-        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.method));
+        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getMethod()));
   }
 
   private boolean traceInvokeDirect(
       DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
-    DexEncodedMethod currentMethod = context.method;
+    DexEncodedMethod currentMethod = context.getMethod();
     if (!registerMethodWithTargetAndContext(directInvokes, invokedMethod, currentMethod)) {
       return false;
     }
@@ -845,18 +850,17 @@
   }
 
   boolean traceInvokeInterface(DexMethod invokedMethod, ProgramMethod context) {
-    return traceInvokeInterface(
-        invokedMethod, context, KeepReason.invokedFrom(context.holder, context.method));
+    return traceInvokeInterface(invokedMethod, context, KeepReason.invokedFrom(context));
   }
 
   boolean traceInvokeInterfaceFromLambda(DexMethod invokedMethod, ProgramMethod context) {
     return traceInvokeInterface(
-        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.method));
+        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getMethod()));
   }
 
   private boolean traceInvokeInterface(
       DexMethod method, ProgramMethod context, KeepReason keepReason) {
-    DexEncodedMethod currentMethod = context.method;
+    DexEncodedMethod currentMethod = context.getMethod();
     if (!registerMethodWithTargetAndContext(interfaceInvokes, method, currentMethod)) {
       return false;
     }
@@ -869,18 +873,17 @@
   }
 
   boolean traceInvokeStatic(DexMethod invokedMethod, ProgramMethod context) {
-    return traceInvokeStatic(
-        invokedMethod, context, KeepReason.invokedFrom(context.holder, context.method));
+    return traceInvokeStatic(invokedMethod, context, KeepReason.invokedFrom(context));
   }
 
   boolean traceInvokeStaticFromLambda(DexMethod invokedMethod, ProgramMethod context) {
     return traceInvokeStatic(
-        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.method));
+        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getMethod()));
   }
 
   private boolean traceInvokeStatic(
       DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
-    DexEncodedMethod currentMethod = context.method;
+    DexEncodedMethod currentMethod = context.getMethod();
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     if (dexItemFactory.classMethods.isReflectiveClassLookup(invokedMethod)
         || dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(invokedMethod)) {
@@ -912,8 +915,7 @@
   }
 
   boolean traceInvokeSuper(DexMethod invokedMethod, ProgramMethod context) {
-    DexProgramClass currentHolder = context.holder;
-    DexEncodedMethod currentMethod = context.method;
+    DexEncodedMethod currentMethod = context.getMethod();
     // We have to revisit super invokes based on the context they are found in. The same
     // method descriptor will hit different targets, depending on the context it is used in.
     DexMethod actualTarget = getInvokeSuperTarget(invokedMethod, currentMethod);
@@ -929,27 +931,26 @@
   }
 
   boolean traceInvokeVirtual(DexMethod invokedMethod, ProgramMethod context) {
-    return traceInvokeVirtual(
-        invokedMethod, context, KeepReason.invokedFrom(context.holder, context.method));
+    return traceInvokeVirtual(invokedMethod, context, KeepReason.invokedFrom(context));
   }
 
   boolean traceInvokeVirtualFromLambda(DexMethod invokedMethod, ProgramMethod context) {
     return traceInvokeVirtual(
-        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.method));
+        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getMethod()));
   }
 
   private boolean traceInvokeVirtual(
       DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
     if (invokedMethod == appView.dexItemFactory().classMethods.newInstance
         || invokedMethod == appView.dexItemFactory().constructorMethods.newInstance) {
-      pendingReflectiveUses.add(context.method);
+      pendingReflectiveUses.add(context.getMethod());
     } else if (appView.dexItemFactory().classMethods.isReflectiveMemberLookup(invokedMethod)) {
       // Implicitly add -identifiernamestring rule for the Java reflection in use.
       identifierNameStrings.add(invokedMethod);
       // Revisit the current method to implicitly add -keep rule for items with reflective access.
-      pendingReflectiveUses.add(context.method);
+      pendingReflectiveUses.add(context.getMethod());
     }
-    if (!registerMethodWithTargetAndContext(virtualInvokes, invokedMethod, context.method)) {
+    if (!registerMethodWithTargetAndContext(virtualInvokes, invokedMethod, context.getMethod())) {
       return false;
     }
     if (Log.ENABLED) {
@@ -961,7 +962,7 @@
   }
 
   boolean traceNewInstance(DexType type, ProgramMethod context) {
-    DexEncodedMethod currentMethod = context.method;
+    DexEncodedMethod currentMethod = context.getMethod();
     boolean skipTracing =
         registerDeferredActionForDeadProtoBuilder(
             type, currentMethod, () -> workList.enqueueTraceNewInstanceAction(type, context));
@@ -981,7 +982,7 @@
         type,
         context,
         InstantiationReason.LAMBDA,
-        KeepReason.invokedFromLambdaCreatedIn(context.method));
+        KeepReason.invokedFromLambdaCreatedIn(context.getMethod()));
   }
 
   private boolean traceNewInstance(
@@ -989,7 +990,7 @@
       ProgramMethod context,
       InstantiationReason instantiationReason,
       KeepReason keepReason) {
-    DexEncodedMethod currentMethod = context.method;
+    DexEncodedMethod currentMethod = context.getMethod();
     DexProgramClass clazz = getProgramClassOrNull(type);
     if (clazz != null) {
       if (clazz.isAnnotation() || clazz.isInterface()) {
@@ -1248,6 +1249,19 @@
         witness);
   }
 
+  private void addImmediateSubtype(DexProgramClass superType, DexProgramClass subType) {
+    assert liveTypes.contains(subType);
+    assert subType.superType == superType.type
+        || Arrays.asList(subType.interfaces.values).contains(superType.type);
+    immediateSubtypesOfLiveTypes
+        .computeIfAbsent(superType, k -> Sets.newIdentityHashSet())
+        .add(subType);
+  }
+
+  private Set<DexProgramClass> getImmediateLiveSubtypes(DexProgramClass clazz) {
+    return immediateSubtypesOfLiveTypes.getOrDefault(clazz, Collections.emptySet());
+  }
+
   private void markTypeAsLive(
       DexProgramClass holder, ScopedDexMethodSet seen, KeepReasonWitness witness) {
     if (!liveTypes.add(holder, witness)) {
@@ -1277,6 +1291,10 @@
               holder.superType, ignore -> new ScopedDexMethodSet());
       seen.setParent(seenForSuper);
       markTypeAsLive(holder.superType, reason);
+      DexProgramClass superClass = getProgramClassOrNull(holder.superType);
+      if (superClass != null) {
+        addImmediateSubtype(superClass, holder);
+      }
     }
 
     // If this is an interface that has just become live, then report previously seen but unreported
@@ -1345,6 +1363,8 @@
       return;
     }
 
+    addImmediateSubtype(clazz, implementer);
+
     if (!appView.options().enableUnusedInterfaceRemoval || mode.isTracingMainDex()) {
       markTypeAsLive(clazz, graphReporter.reportClassReferencedFrom(clazz, implementer));
     } else {
@@ -1388,10 +1408,7 @@
   private void processAnnotation(
       DexProgramClass holder, DexDefinition annotatedItem, DexAnnotation annotation) {
     assert annotatedItem == holder
-        || (annotatedItem.isDexEncodedField()
-            && annotatedItem.asDexEncodedField().field.holder == holder.type)
-        || (annotatedItem.isDexEncodedMethod()
-            && annotatedItem.asDexEncodedMethod().method.holder == holder.type);
+        || annotatedItem.asDexEncodedMember().toReference().holder == holder.type;
     assert !holder.isDexClass() || holder.asDexClass().isProgramClass();
     DexType type = annotation.annotation.type;
     recordTypeReference(type);
@@ -2135,7 +2152,7 @@
     if (contextOrNull != null
         && !resolution.isUnresolved()
         && !AccessControl.isMethodAccessible(
-            resolution.method, holder, contextOrNull.holder, appInfo)) {
+            resolution.method, holder, contextOrNull.getHolder(), appInfo)) {
       // Not accessible from this context, so this call will cause a runtime exception.
       // Note that the resolution is not cached, as another call context may be valid.
       return;
@@ -2155,10 +2172,11 @@
     assert resolution.holder.isProgramClass();
 
     assert interfaceInvoke == holder.isInterface();
+    DexProgramClass context = contextOrNull == null ? null : contextOrNull.getHolder();
     LookupResult lookupResult =
         // TODO(b/140214802): Call on the resolution once proper resolution and lookup is resolved.
         new SingleResolutionResult(holder, resolution.holder, resolution.method)
-            .lookupVirtualDispatchTargets(appView, appInfo);
+            .lookupVirtualDispatchTargets(context, appView, appInfo);
     if (!lookupResult.isLookupResultSuccess()) {
       return;
     }
@@ -2412,10 +2430,11 @@
     assert fieldAccessInfoCollection.verifyMappingIsOneToOne();
 
     for (ProgramMethod bridge : syntheticInterfaceMethodBridges.values()) {
-      appView.appInfo().invalidateTypeCacheFor(bridge.holder.type);
-      bridge.holder.appendVirtualMethod(bridge.method);
-      targetedMethods.add(bridge.method, graphReporter.fakeReportShouldNotBeUsed());
-      liveMethods.add(bridge.holder, bridge.method, graphReporter.fakeReportShouldNotBeUsed());
+      appView.appInfo().invalidateTypeCacheFor(bridge.getHolder().type);
+      bridge.getHolder().appendVirtualMethod(bridge.getMethod());
+      targetedMethods.add(bridge.getMethod(), graphReporter.fakeReportShouldNotBeUsed());
+      liveMethods.add(
+          bridge.getHolder(), bridge.getMethod(), graphReporter.fakeReportShouldNotBeUsed());
     }
 
     // Ensure references from various root set collections.
@@ -2693,12 +2712,12 @@
     assert replaced == callSites.size();
   }
 
-  private static <T extends PresortedComparable<T>> SortedSet<T> toSortedDescriptorSet(
-      Set<? extends DexEncodedMember<T>> set) {
-    ImmutableSortedSet.Builder<T> builder =
+  private static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+      SortedSet<R> toSortedDescriptorSet(Set<D> set) {
+    ImmutableSortedSet.Builder<R> builder =
         new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompareTo);
-    for (DexEncodedMember<T> item : set) {
-      builder.add(item.getKey());
+    for (D item : set) {
+      builder.add(item.toReference());
     }
     return builder.build();
   }
@@ -2846,25 +2865,27 @@
       InterfaceMethodSyntheticBridgeAction action, RootSetBuilder builder) {
     ProgramMethod methodToKeep = action.getMethodToKeep();
     ProgramMethod singleTarget = action.getSingleTarget();
-    if (rootSet.noShrinking.containsKey(singleTarget.method.method)) {
+    DexEncodedMethod singleTargetMethod = singleTarget.getMethod();
+    if (rootSet.noShrinking.containsKey(singleTargetMethod.method)) {
       return;
     }
     if (methodToKeep != singleTarget) {
-      assert null == methodToKeep.holder.lookupMethod(methodToKeep.method.method);
+      assert null == methodToKeep.getHolder().lookupMethod(methodToKeep.getMethod().method);
       ProgramMethod old =
-          syntheticInterfaceMethodBridges.put(methodToKeep.method.method, methodToKeep);
+          syntheticInterfaceMethodBridges.put(methodToKeep.getMethod().method, methodToKeep);
       if (old == null) {
-        if (singleTarget.method.isLibraryMethodOverride().isTrue()) {
-          methodToKeep.method.setLibraryMethodOverride(OptionalBool.TRUE);
+        if (singleTargetMethod.isLibraryMethodOverride().isTrue()) {
+          methodToKeep.getMethod().setLibraryMethodOverride(OptionalBool.TRUE);
         }
-        assert singleTarget.holder.isInterface();
+        DexProgramClass singleTargetHolder = singleTarget.getHolder();
+        assert singleTargetHolder.isInterface();
         markVirtualMethodAsReachable(
-            singleTarget.method.method,
-            singleTarget.holder.isInterface(),
+            singleTargetMethod.method,
+            singleTargetHolder.isInterface(),
             null,
             graphReporter.fakeReportShouldNotBeUsed());
         enqueueMarkMethodLiveAction(
-            singleTarget.holder, singleTarget.method, graphReporter.fakeReportShouldNotBeUsed());
+            singleTargetHolder, singleTargetMethod, graphReporter.fakeReportShouldNotBeUsed());
       }
     }
     action.getAction().accept(builder);
@@ -2938,18 +2959,16 @@
 
     // If there is a subtype of `clazz` that escapes into the library and does not override `method`
     // then we need to mark the method as being reachable.
-    Deque<DexType> worklist = new ArrayDeque<>(appView.appInfo().allImmediateSubtypes(clazz.type));
-
-    Set<DexType> visited = Sets.newIdentityHashSet();
-    visited.addAll(worklist);
+    Set<DexProgramClass> immediateSubtypes = getImmediateLiveSubtypes(clazz);
+    if (immediateSubtypes.isEmpty()) {
+      return false;
+    }
+    Deque<DexProgramClass> worklist = new ArrayDeque<>(immediateSubtypes);
+    Set<DexProgramClass> visited = SetUtils.newIdentityHashSet(immediateSubtypes);
 
     while (!worklist.isEmpty()) {
-      DexClass current = appView.definitionFor(worklist.removeFirst());
-      if (current == null) {
-        continue;
-      }
-
-      assert visited.contains(current.type);
+      DexProgramClass current = worklist.removeFirst();
+      assert visited.contains(current);
 
       if (current.lookupVirtualMethod(method.method) != null) {
         continue;
@@ -2959,7 +2978,7 @@
         return true;
       }
 
-      for (DexType subtype : appView.appInfo().allImmediateSubtypes(current.type)) {
+      for (DexProgramClass subtype : getImmediateLiveSubtypes(current)) {
         if (visited.add(subtype)) {
           worklist.add(subtype);
         }
@@ -3543,17 +3562,17 @@
     }
   }
 
-  private static final class TargetWithContext<T extends Descriptor<?, T>> {
+  private static final class TargetWithContext<R extends DexMember<?, R>> {
 
-    private final T target;
+    private final R target;
     private final DexEncodedMethod context;
 
-    private TargetWithContext(T target, DexEncodedMethod context) {
+    private TargetWithContext(R target, DexEncodedMethod context) {
       this.target = target;
       this.context = context;
     }
 
-    public T getTarget() {
+    public R getTarget() {
       return target;
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index 680c0b5..cc86522 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -12,6 +12,7 @@
 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.ProgramMethod;
 
 // TODO(herhut): Canonicalize reason objects.
 public abstract class KeepReason {
@@ -40,6 +41,10 @@
     return new InvokedFrom(holder, method);
   }
 
+  public static KeepReason invokedFrom(ProgramMethod context) {
+    return invokedFrom(context.getHolder(), context.getMethod());
+  }
+
   public static KeepReason invokedFromLambdaCreatedIn(DexEncodedMethod method) {
     return new InvokedFromLambdaCreatedIn(method);
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 7495c6b..7d93686 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -557,9 +557,10 @@
               resolutionResult.getResolvedHolder().asProgramClass(),
               resolutionResult.getResolvedMethod());
       ProgramMethod methodToKeep =
-          canInsertForwardingMethod(originalClazz, resolutionMethod.method)
+          canInsertForwardingMethod(originalClazz, resolutionMethod.getMethod())
               ? new ProgramMethod(
-                  originalClazz, resolutionMethod.method.toForwardingMethod(originalClazz, appView))
+                  originalClazz,
+                  resolutionMethod.getMethod().toForwardingMethod(originalClazz, appView))
               : resolutionMethod;
 
       delayedRootSetActionItems.add(
@@ -576,9 +577,9 @@
                       rule);
                 }
                 DexDefinition precondition =
-                    testAndGetPrecondition(methodToKeep.method, preconditionSupplier);
+                    testAndGetPrecondition(methodToKeep.getMethod(), preconditionSupplier);
                 rootSetBuilder.addItemToSets(
-                    methodToKeep.method, context, rule, precondition, ifRule);
+                    methodToKeep.getMethod(), context, rule, precondition, ifRule);
               }));
     }
   }
@@ -1539,7 +1540,7 @@
               // Create a Set of the fields to avoid quadratic behavior.
               fields =
                   Streams.stream(clazz.fields())
-                      .map(DexEncodedField::getKey)
+                      .map(DexEncodedField::toReference)
                       .collect(Collectors.toSet());
             }
             assert fields.contains(requiredField)
@@ -1552,7 +1553,7 @@
               // Create a Set of the methods to avoid quadratic behavior.
               methods =
                   Streams.stream(clazz.methods())
-                      .map(DexEncodedMethod::getKey)
+                      .map(DexEncodedMethod::toReference)
                       .collect(Collectors.toSet());
             }
             assert methods.contains(requiredMethod)
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 3ded35c..05d281b 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
@@ -17,7 +18,6 @@
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
-import com.android.tools.r8.graph.PresortedComparable;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -243,8 +243,8 @@
     return context == null || !isTypeLive(context);
   }
 
-  private <S extends PresortedComparable<S>, T extends DexEncodedMember<S>>
-      int firstUnreachableIndex(List<T> items, Predicate<T> live) {
+  private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> int firstUnreachableIndex(
+      List<D> items, Predicate<D> live) {
     for (int i = 0; i < items.size(); i++) {
       if (!live.test(items.get(i))) {
         return i;
@@ -268,7 +268,7 @@
     }
     for (int i = firstUnreachable; i < methods.size(); i++) {
       DexEncodedMethod method = methods.get(i);
-      if (appInfo.liveMethods.contains(method.getKey())) {
+      if (appInfo.liveMethods.contains(method.toReference())) {
         reachableMethods.add(method);
       } else if (options.configurationDebugging) {
         // Keep the method but rewrite its body, if it has one.
@@ -277,7 +277,7 @@
                 ? method
                 : method.toMethodThatLogsError(appView));
         methodsToKeepForConfigurationDebugging.add(method.method);
-      } else if (appInfo.targetedMethods.contains(method.getKey())) {
+      } else if (appInfo.targetedMethods.contains(method.toReference())) {
         // If the method is already abstract, and doesn't have code, let it be.
         if (method.shouldNotHaveCode() && !method.hasCode()) {
           reachableMethods.add(method);
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 76db9fe..3ba6b3a 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
@@ -30,7 +31,6 @@
 import com.android.tools.r8.graph.LookupResult;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.graph.PresortedComparable;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
@@ -335,7 +335,7 @@
     }
 
     assert Streams.stream(Iterables.concat(clazz.fields(), clazz.methods()))
-        .map(DexEncodedMember::getKey)
+        .map(DexEncodedMember::toReference)
         .noneMatch(appInfo::isPinned);
 
     if (appView.options().featureSplitConfiguration != null &&
@@ -420,7 +420,8 @@
     // We basically can't move the clinit, since it is not called when implementing classes have
     // their clinit called - except when the interface has a default method.
     if ((clazz.hasClassInitializer() && targetClass.hasClassInitializer())
-        || targetClass.classInitializationMayHaveSideEffects(appView, type -> type == clazz.type)
+        || targetClass.classInitializationMayHaveSideEffects(
+            appView, type -> type == clazz.type, Sets.newIdentityHashSet())
         || (clazz.isInterface() && clazz.classInitializationMayHaveSideEffects(appView))) {
       // TODO(herhut): Handle class initializers.
       if (Log.ENABLED) {
@@ -699,7 +700,7 @@
     return true;
   }
 
-  private boolean methodResolutionMayChange(DexClass source, DexClass target) {
+  private boolean methodResolutionMayChange(DexProgramClass source, DexProgramClass target) {
     for (DexEncodedMethod virtualSourceMethod : source.virtualMethods()) {
       DexEncodedMethod directTargetMethod = target.lookupDirectMethod(virtualSourceMethod.method);
       if (directTargetMethod != null) {
@@ -738,7 +739,7 @@
         LookupResult lookupResult =
             appInfo
                 .resolveMethodOnInterface(method.method.holder, method.method)
-                .lookupVirtualDispatchTargets(appView, appInfo);
+                .lookupVirtualDispatchTargets(target, appView);
         assert lookupResult.isLookupResultSuccess();
         if (lookupResult.isLookupResultFailure()) {
           return true;
@@ -917,10 +918,7 @@
           add(directMethods, resultingDirectMethod, MethodSignatureEquivalence.get());
           deferredRenamings.map(directMethod.method, resultingDirectMethod.method);
           deferredRenamings.recordMove(directMethod.method, resultingDirectMethod.method);
-
-          if (!directMethod.isStatic()) {
-            blockRedirectionOfSuperCalls(resultingDirectMethod.method);
-          }
+          blockRedirectionOfSuperCalls(resultingDirectMethod.method);
         }
       }
 
@@ -1269,15 +1267,15 @@
       return null;
     }
 
-    private <T extends DexEncodedMember<S>, S extends PresortedComparable<S>> void add(
-        Map<Wrapper<S>, T> map, T item, Equivalence<S> equivalence) {
-      map.put(equivalence.wrap(item.getKey()), item);
+    private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> void add(
+        Map<Wrapper<R>, D> map, D item, Equivalence<R> equivalence) {
+      map.put(equivalence.wrap(item.toReference()), item);
     }
 
-    private <T extends DexEncodedMember<S>, S extends PresortedComparable<S>> void addAll(
-        Collection<Wrapper<S>> collection, Iterable<T> items, Equivalence<S> equivalence) {
-      for (T item : items) {
-        collection.add(equivalence.wrap(item.getKey()));
+    private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> void addAll(
+        Collection<Wrapper<R>> collection, Iterable<D> items, Equivalence<R> equivalence) {
+      for (D item : items) {
+        collection.add(equivalence.wrap(item.toReference()));
       }
     }
 
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 78edccb..68f1749 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -308,7 +308,8 @@
           // methods, we either did not rename them, we renamed them according to a supplied map or
           // they may be bridges for interface methods with covariant return types.
           sortMethods(methods);
-          assert verifyMethodsAreKeptDirectlyOrIndirectly(appView, methods);
+          // TODO(b/149360203): Reenable assert.
+          // assert verifyMethodsAreKeptDirectlyOrIndirectly(appView, methods);
         }
 
         boolean identityMapping =
diff --git a/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java b/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java
index 40fa54f..2458a64 100644
--- a/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java
+++ b/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java
@@ -5,25 +5,25 @@
 
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.graph.DexEncodedMember;
-import com.android.tools.r8.graph.PresortedComparable;
+import com.android.tools.r8.graph.DexMember;
 import java.util.Iterator;
 import java.util.List;
 import java.util.NoSuchElementException;
 
-public class OrderedMergingIterator<T extends DexEncodedMember<S>, S extends PresortedComparable<S>>
-    implements Iterator<T> {
+public class OrderedMergingIterator<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+    implements Iterator<D> {
 
-  private final List<T> one;
-  private final List<T> other;
+  private final List<D> one;
+  private final List<D> other;
   private int oneIndex = 0;
   private int otherIndex = 0;
 
-  public OrderedMergingIterator(List<T> one, List<T> other) {
+  public OrderedMergingIterator(List<D> one, List<D> other) {
     this.one = one;
     this.other = other;
   }
 
-  private static <T> T getNextChecked(List<T> list, int position) {
+  private D getNextChecked(List<D> list, int position) {
     if (position >= list.size()) {
       throw new NoSuchElementException();
     }
@@ -36,14 +36,14 @@
   }
 
   @Override
-  public T next() {
+  public D next() {
     if (oneIndex >= one.size()) {
       return getNextChecked(other, otherIndex++);
     }
     if (otherIndex >= other.size()) {
       return getNextChecked(one, oneIndex++);
     }
-    int comparison = one.get(oneIndex).getKey().compareTo(other.get(otherIndex).getKey());
+    int comparison = one.get(oneIndex).toReference().compareTo(other.get(otherIndex).toReference());
     if (comparison < 0) {
       return one.get(oneIndex++);
     }
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
new file mode 100644
index 0000000..b631262
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.Set;
+
+public class WorkList<T> {
+
+  private final Deque<T> workingList = new ArrayDeque<>();
+  private final Set<T> seen;
+
+  public WorkList(EqualityTest equalityTest) {
+    if (equalityTest == EqualityTest.HASH) {
+      seen = new HashSet<>();
+    } else {
+      seen = Sets.newIdentityHashSet();
+    }
+  }
+
+  public void addIfNotSeen(Iterable<T> items) {
+    items.forEach(this::addIfNotSeen);
+  }
+
+  public void addIfNotSeen(T item) {
+    if (seen.add(item)) {
+      workingList.addLast(item);
+    }
+  }
+
+  public boolean hasNext() {
+    return !workingList.isEmpty();
+  }
+
+  public T next() {
+    assert hasNext();
+    return workingList.removeFirst();
+  }
+
+  public enum EqualityTest {
+    HASH,
+    IDENTITY
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index fbacef6..14d460e 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -142,6 +142,24 @@
                 handler).build()));
   }
 
+  @Test(expected = CompilationFailedException.class)
+  public void recursiveFlagsFile() throws Throwable {
+    Path working = temp.getRoot().toPath();
+    Path flagsFile = working.resolve("flags.txt");
+    Path recursiveFlagsFile = working.resolve("recursive_flags.txt");
+    Path input = Paths.get(EXAMPLES_BUILD_DIR + "/arithmetic.jar").toAbsolutePath();
+    FileUtils.writeTextFile(recursiveFlagsFile, "--output", "output.zip");
+    FileUtils.writeTextFile(
+        flagsFile, "--min-api", "24", input.toString(), "@" + recursiveFlagsFile);
+    DiagnosticsChecker.checkErrorsContains(
+        "Recursive @argfiles are not supported",
+        handler ->
+            D8.run(
+                D8Command.parse(
+                        new String[] {"@" + flagsFile.toString()}, EmbeddedOrigin.INSTANCE, handler)
+                    .build()));
+  }
+
   @Test
   public void printsHelpOnNoInput() throws Throwable {
     ProcessResult result = ToolHelper.forkD8(temp.getRoot().toPath());
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index cc92bf3..4a703d7 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -143,7 +143,6 @@
     assertEquals(Tool.R8, marker.getTool());
   }
 
-
   @Test(expected=CompilationFailedException.class)
   public void nonExistingFlagsFile() throws Throwable {
     Path working = temp.getRoot().toPath();
@@ -157,6 +156,24 @@
                 handler).build()));
   }
 
+  @Test(expected = CompilationFailedException.class)
+  public void recursiveFlagsFile() throws Throwable {
+    Path working = temp.getRoot().toPath();
+    Path flagsFile = working.resolve("flags.txt");
+    Path recursiveFlagsFile = working.resolve("recursive_flags.txt");
+    Path input = Paths.get(EXAMPLES_BUILD_DIR + "/arithmetic.jar").toAbsolutePath();
+    FileUtils.writeTextFile(recursiveFlagsFile, "--output", "output.zip");
+    FileUtils.writeTextFile(
+        flagsFile, "--min-api", "24", input.toString(), "@" + recursiveFlagsFile);
+    DiagnosticsChecker.checkErrorsContains(
+        "Recursive @argfiles are not supported",
+        handler ->
+            R8.run(
+                R8Command.parse(
+                        new String[] {"@" + flagsFile.toString()}, EmbeddedOrigin.INSTANCE, handler)
+                    .build()));
+  }
+
   @Test
   public void printsHelpOnNoInput() throws Throwable {
     ProcessResult result = ToolHelper.forkR8(temp.getRoot().toPath());
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java
new file mode 100644
index 0000000..8dcad81
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.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.classmerging;
+
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.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 VerticalClassMergerSuperCallInStaticTest extends TestBase {
+
+  private static final String[] EXPECTED = new String[] {"A.collect()", "Base.collect()"};
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public VerticalClassMergerSuperCallInStaticTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addProgramClasses(Base.class, B.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, B.class, Main.class)
+        .addProgramClassFileData(getAWithRewrittenInvokeSpecialToBase())
+        .addKeepMainRule(Main.class)
+        .addKeepClassRules(Base.class, B.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  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
+    @NeverInline
+    public void collect() {
+      System.out.println("A.collect()");
+    }
+
+    @NeverInline
+    public static void callSuper(A a) {
+      a.collect(); // Will be rewritten from invoke-virtual to invoke-special Base.collect();
+    }
+  }
+
+  public static class B extends A {
+
+    public void bar() {
+      collect();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      B b = new B();
+      b.bar();
+      A.callSuper(b);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java b/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java
index 90993ee..6a1e059 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java
@@ -48,6 +48,8 @@
     Method m;
     m = A.class.getMethod("method0");
     m.invoke(a);
+    // The call with in-exact argument to getMethod is intended and should stay.
+    // The warning cannot be suppressed according to b/117198454.
     m = A.class.getMethod("method0", null);
     m.invoke(a);
     m = A.class.getMethod("method0", (Class<?>[]) null);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
index af4088e..21074c9 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
@@ -6,7 +6,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.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java
index ddcb725..a15dc08 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -19,16 +19,22 @@
 @RunWith(Parameterized.class)
 public class ComparisonEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
 
-  private final TestParameters parameters;
-  private final Class<?>[] INPUTS = new Class<?>[] {NullCheck.class, EnumComparison.class};
+  private static final Class<?>[] INPUTS = new Class<?>[] {NullCheck.class, EnumComparison.class};
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final boolean enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
     return enumUnboxingTestParameters();
   }
 
-  public ComparisonEnumUnboxingAnalysisTest(TestParameters parameters) {
+  public ComparisonEnumUnboxingAnalysisTest(
+      TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
     this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
   }
 
   @Test
@@ -37,9 +43,9 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(ComparisonEnumUnboxingAnalysisTest.class)
             .addKeepMainRules(INPUTS)
-            .addKeepRules(KEEP_ENUM)
             .enableInliningAnnotations()
-            .addOptionsModification(this::enableEnumOptions)
+            .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
+            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
             .compile()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
index 332ad2e..b7ebc83 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
@@ -10,9 +10,9 @@
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessages;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.List;
@@ -34,8 +34,9 @@
     }
   }
 
-  void enableEnumOptions(InternalOptions options) {
+  void enableEnumOptions(InternalOptions options, boolean enumValueOptimization) {
     options.enableEnumUnboxing = true;
+    options.enableEnumValueOptimization = enumValueOptimization;
     options.testing.enableEnumUnboxingDebugLogs = true;
   }
 
@@ -55,12 +56,15 @@
         diagnostic.getDiagnosticMessage().contains(enumClass.getSimpleName()));
   }
 
-  static TestParametersCollection enumUnboxingTestParameters() {
-    return getTestParameters()
-        .withCfRuntime(CfVm.JDK9)
-        .withDexRuntime(DexVm.Version.first())
-        .withDexRuntime(DexVm.Version.last())
-        .withAllApiLevels()
-        .build();
+  static List<Object[]> enumUnboxingTestParameters() {
+    return buildParameters(
+        getTestParameters()
+            .withCfRuntime(CfVm.JDK9)
+            .withDexRuntime(DexVm.Version.first())
+            .withDexRuntime(DexVm.Version.last())
+            .withAllApiLevels()
+            .build(),
+        BooleanUtils.values(),
+        BooleanUtils.values());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java
index 9c4872f..c0eeaf4 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java
@@ -12,13 +12,13 @@
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 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.utils.codeinspector.CodeInspector;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -36,14 +36,19 @@
   };
 
   private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final boolean enumKeepRules;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
     return enumUnboxingTestParameters();
   }
 
-  public FailingEnumUnboxingAnalysisTest(TestParameters parameters) {
+  public FailingEnumUnboxingAnalysisTest(
+      TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
     this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
   }
 
   @Test
@@ -57,8 +62,8 @@
         r8FullTestBuilder
             .noTreeShaking() // Disabled to avoid merging Itf into EnumInterface.
             .enableInliningAnnotations()
-            .addKeepRules(KEEP_ENUM)
-            .addOptionsModification(this::enableEnumOptions)
+            .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
+            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
             .compile()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
index c7ad117..944c9c3 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
@@ -11,9 +11,9 @@
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.EnumSet;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -31,14 +31,19 @@
   };
 
   private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final boolean enumKeepRules;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
     return enumUnboxingTestParameters();
   }
 
-  public FailingMethodEnumUnboxingAnalysisTest(TestParameters parameters) {
+  public FailingMethodEnumUnboxingAnalysisTest(
+      TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
     this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
   }
 
   @Test
@@ -47,13 +52,10 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(FailingMethodEnumUnboxingAnalysisTest.class)
             .addKeepMainRules(FAILURES)
-            .addKeepRules(KEEP_ENUM)
-            .addOptionsModification(this::enableEnumOptions)
+            .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
+            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .enableInliningAnnotations()
-            .addOptionsModification(
-                // Disabled to avoid toString() being removed.
-                opt -> opt.enableEnumValueOptimization = false)
             .setMinApi(parameters.getApiLevel())
             .compile()
             .inspect(this::assertEnumsAsExpected);
@@ -64,9 +66,14 @@
                   m ->
                       assertEnumIsBoxed(
                           failure.getDeclaredClasses()[0], failure.getSimpleName(), m))
-              .run(parameters.getRuntime(), failure)
-              .assertSuccess();
-      assertLines2By2Correct(run.getStdOut());
+              .run(parameters.getRuntime(), failure);
+      if (failure == EnumSetTest.class && !enumKeepRules) {
+        // EnumSet and EnumMap cannot be used without the enumKeepRules.
+        run.assertFailure();
+      } else {
+        run.assertSuccess();
+        assertLines2By2Correct(run.getStdOut());
+      }
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java
index 8811ee5..daaf504 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -19,17 +19,23 @@
 @RunWith(Parameterized.class)
 public class FieldPutEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
 
-  private final TestParameters parameters;
   private static final Class<?>[] INPUTS =
       new Class<?>[] {InstanceFieldPut.class, StaticFieldPut.class};
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final boolean enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
     return enumUnboxingTestParameters();
   }
 
-  public FieldPutEnumUnboxingAnalysisTest(TestParameters parameters) {
+  public FieldPutEnumUnboxingAnalysisTest(
+      TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
     this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
   }
 
   @Test
@@ -38,8 +44,8 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(FieldPutEnumUnboxingAnalysisTest.class)
             .addKeepMainRules(INPUTS)
-            .addKeepRules(KEEP_ENUM)
-            .addOptionsModification(this::enableEnumOptions)
+            .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
+            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .enableInliningAnnotations()
             .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java
index 2570a12..afd2e6a 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -15,19 +15,24 @@
 @RunWith(Parameterized.class)
 public class OrdinalEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
 
-  private final TestParameters parameters;
+  private static final Class<?> ENUM_CLASS = MyEnum.class;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final boolean enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
     return enumUnboxingTestParameters();
   }
 
-  public OrdinalEnumUnboxingAnalysisTest(TestParameters parameters) {
+  public OrdinalEnumUnboxingAnalysisTest(
+      TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
     this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
   }
 
-  private static final Class<?> ENUM_CLASS = MyEnum.class;
-
   @Test
   public void testEnumUnboxing() throws Exception {
     Class<Ordinal> classToTest = Ordinal.class;
@@ -35,8 +40,8 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(classToTest, ENUM_CLASS)
             .addKeepMainRule(classToTest)
-            .addKeepRules(KEEP_ENUM)
-            .addOptionsModification(this::enableEnumOptions)
+            .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
+            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
             .compile()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java
index 2abd358..b231d32 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -16,19 +16,24 @@
 @RunWith(Parameterized.class)
 public class PhiEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
 
-  private final TestParameters parameters;
+  private static final Class<?> ENUM_CLASS = MyEnum.class;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final boolean enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
     return enumUnboxingTestParameters();
   }
 
-  public PhiEnumUnboxingAnalysisTest(TestParameters parameters) {
+  public PhiEnumUnboxingAnalysisTest(
+      TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
     this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
   }
 
-  private static final Class<?> ENUM_CLASS = MyEnum.class;
-
   @Test
   public void testEnumUnboxing() throws Exception {
     Class<?> classToTest = Phi.class;
@@ -36,9 +41,9 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(classToTest, ENUM_CLASS)
             .addKeepMainRule(classToTest)
-            .addKeepRules(KEEP_ENUM)
+            .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
             .enableInliningAnnotations()
-            .addOptionsModification(this::enableEnumOptions)
+            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
             .compile()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java
index 53cddc6..a40dec0 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -16,19 +16,24 @@
 @RunWith(Parameterized.class)
 public class SwitchEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
 
-  private final TestParameters parameters;
+  private static final Class<?> ENUM_CLASS = MyEnum.class;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final boolean enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
     return enumUnboxingTestParameters();
   }
 
-  public SwitchEnumUnboxingAnalysisTest(TestParameters parameters) {
+  public SwitchEnumUnboxingAnalysisTest(
+      TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
     this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
   }
 
-  private static final Class<?> ENUM_CLASS = MyEnum.class;
-
   @Test
   public void testEnumUnboxing() throws Exception {
     Class<Switch> classToTest = Switch.class;
@@ -36,9 +41,9 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(SwitchEnumUnboxingAnalysisTest.class)
             .addKeepMainRule(classToTest)
-            .addKeepRules(KEEP_ENUM)
+            .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
             .enableInliningAnnotations()
-            .addOptionsModification(this::enableEnumOptions)
+            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
             .compile()
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 fc6e7d3..fc8f3bf 100644
--- a/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.internal;
 
 import static com.android.tools.r8.ToolHelper.isLocalDevelopment;
+import static org.hamcrest.CoreMatchers.*;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
@@ -42,8 +43,13 @@
             .addKeepRuleFiles(
                 Paths.get(base).resolve(BASE_PG_CONF),
                 Paths.get(ToolHelper.PROGUARD_SETTINGS_FOR_INTERNAL_APPS, PG_CONF))
+            .allowDiagnosticInfoMessages()
             .allowUnusedProguardConfigurationRules()
-            .compile();
+            .compile()
+            .assertAllInfoMessagesMatch(
+                anyOf(
+                    equalTo("Ignoring option: -optimizations"),
+                    containsString("Proguard configuration rule does not match anything")));
 
     int appSize = compileResult.app.applicationSize();
     assertTrue("Expected max size of " + MAX_SIZE+ ", got " + appSize, appSize < MAX_SIZE);
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 4484311..4c3085e 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -10,6 +10,7 @@
 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.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -36,7 +37,7 @@
   private AndroidApp app;
   private DirectMappedDexApplication program;
   private AppInfoWithSubtyping appInfo;
-  private AppView<?> appView;
+  private AppView<? extends AppInfoWithClassHierarchy> appView;
 
   @Before
   public void readGMSCore() throws Exception {
@@ -64,7 +65,8 @@
 
     // Check lookup targets with include method.
     ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(clazz, method.method);
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView, appInfo);
+    LookupResult lookupResult =
+        resolutionResult.lookupVirtualDispatchTargets(clazz, appView, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<DexEncodedMethod> targets = lookupResult.asLookupResultSuccess().getMethodTargets();
     assertTrue(targets.contains(method));
@@ -74,7 +76,7 @@
     LookupResult lookupResult =
         appInfo
             .resolveMethodOnInterface(clazz, method.method)
-            .lookupVirtualDispatchTargets(appView, appInfo);
+            .lookupVirtualDispatchTargets(clazz, appView, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<DexEncodedMethod> targets = lookupResult.asLookupResultSuccess().getMethodTargets();
     if (appInfo.subtypes(method.method.holder).stream()
@@ -103,6 +105,6 @@
 
   @Test
   public void testLookup() {
-    program.classes().forEach(this::testLookup);
+    program.classesWithDeterministicOrder().forEach(this::testLookup);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 5d48fa6..6104883 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -137,7 +137,7 @@
             TypeLatticeElement.fromDexType(
                 app.dexItemFactory.throwableType, Nullability.definitelyNotNull(), appView),
             null);
-    instruction = new Argument(value, false);
+    instruction = new Argument(value, 0, false);
     instruction.setPosition(position);
     block0.add(instruction, metadata);
     instruction = new If(Type.EQ, value);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
index 9ccdb7f..9ebb411 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
@@ -70,12 +70,7 @@
     // inlined into main(), which makes A.foo() eligible for inlining into main().
     ClassSubject aClassSubject = inspector.clazz(A.class);
     assertThat(aClassSubject, isPresent());
-    if (maxInliningDepth == 0) {
-      assertThat(aClassSubject.uniqueMethodWithName("foo"), isPresent());
-    } else {
-      assert maxInliningDepth == 1;
-      assertThat(aClassSubject.uniqueMethodWithName("foo"), not(isPresent()));
-    }
+    assertThat(aClassSubject.uniqueMethodWithName("foo"), not(isPresent()));
 
     // A.bar() should always be inlined because it is marked as @AlwaysInline.
     assertThat(aClassSubject.uniqueMethodWithName("bar"), not(isPresent()));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentTest.java
index beecf65..ca34efd 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentTest.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/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java
new file mode 100644
index 0000000..cf7650f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java
@@ -0,0 +1,99 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.membervaluepropagation.fields;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class FieldInitializedByDifferentConstantsInMultipleConstructorsTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public FieldInitializedByDifferentConstantsInMultipleConstructorsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(FieldInitializedByDifferentConstantsInMultipleConstructorsTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Live!", "Live!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    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());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      A obj1 = new A();
+      if (obj1.x == 42) {
+        live();
+      } else {
+        dead();
+      }
+
+      A obj2 = new A(null);
+      if (obj2.x == 43) {
+        live();
+      } else {
+        dead();
+      }
+    }
+
+    @NeverInline
+    static void live() {
+      System.out.println("Live!");
+    }
+
+    @NeverInline
+    static void dead() {
+      System.out.println("Dead!");
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    int x;
+
+    A() {
+      this.x = 42;
+    }
+
+    A(Object unused) {
+      this.x = 43;
+    }
+  }
+}
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
new file mode 100644
index 0000000..2f0cf08
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedBySameConstantInMultipleConstructorsTest.java
@@ -0,0 +1,99 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.membervaluepropagation.fields;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class FieldInitializedBySameConstantInMultipleConstructorsTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public FieldInitializedBySameConstantInMultipleConstructorsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(FieldInitializedBySameConstantInMultipleConstructorsTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Live!", "Live!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    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());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      A obj1 = new A();
+      if (obj1.x == 42) {
+        live();
+      } else {
+        dead();
+      }
+
+      A obj2 = new A(null);
+      if (obj2.x == 42) {
+        live();
+      } else {
+        dead();
+      }
+    }
+
+    @NeverInline
+    static void live() {
+      System.out.println("Live!");
+    }
+
+    @NeverInline
+    static void dead() {
+      System.out.println("Dead!");
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    int x;
+
+    A() {
+      this.x = 42;
+    }
+
+    A(Object unused) {
+      this.x = 42;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAndUninstantiatedTypesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAndUninstantiatedTypesTest.java
new file mode 100644
index 0000000..af6296b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAndUninstantiatedTypesTest.java
@@ -0,0 +1,172 @@
+// 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.unusedarguments;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+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 UnusedAndUninstantiatedTypesTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public UnusedAndUninstantiatedTypesTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testUnusedAndUninstantiatedTypes() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(UnusedAndUninstantiatedTypesTest.class)
+        .addKeepMainRule(Main.class)
+        .noMinification()
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::assertMethodsAreThere)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(
+            "nothing",
+            "uninstantiatedThenUnused null",
+            "unusedThenUninstantiated null",
+            "doubleUninstantiatedThenUnused null and null",
+            "doubleUnusedThenUninstantiated null and null",
+            "tripleUninstantiatedThenUnused null and null and null",
+            "tripleUnusedThenUninstantiated null and null and null",
+            "withWideParameters null and null and null");
+  }
+
+  private void assertMethodsAreThere(CodeInspector i) {
+    List<FoundMethodSubject> methods = i.clazz(Main.class).allMethods();
+    assertEquals(9, methods.size());
+    for (FoundMethodSubject method : methods) {
+      if (!method.getMethod().method.name.toString().equals("main")) {
+        assertEquals(0, method.getMethod().method.getArity());
+      }
+    }
+  }
+
+  @SuppressWarnings("SameParameterValue")
+  static class Main {
+
+    public static void main(String[] args) {
+      uninstantiatedAndUnused(null);
+      uninstantiatedThenUnused(null, new Unused());
+      unusedThenUninstantiated(new Unused(), null);
+      doubleUninstantiatedThenUnused(null, new Unused(), null, new Unused());
+      doubleUnusedThenUninstantiated(new Unused(), null, new Unused(), null);
+      tripleUninstantiatedThenUnused(null, new Unused(), new Unused(), null, null, new Unused());
+      tripleUnusedThenUninstantiated(new Unused(), null, null, new Unused(), new Unused(), null);
+      withWideParameters(0L, 1L, null, null, 2L, 3L, null);
+    }
+
+    @NeverInline
+    static void uninstantiatedAndUnused(UnInstantiated uninstantiated) {
+      System.out.println("nothing");
+    }
+
+    @NeverInline
+    static void uninstantiatedThenUnused(UnInstantiated uninstantiated, Unused unused) {
+      System.out.println("uninstantiatedThenUnused " + uninstantiated);
+    }
+
+    @NeverInline
+    static void unusedThenUninstantiated(Unused unused, UnInstantiated uninstantiated) {
+      System.out.println("unusedThenUninstantiated " + uninstantiated);
+    }
+
+    @NeverInline
+    static void doubleUninstantiatedThenUnused(
+        UnInstantiated uninstantiated1,
+        Unused unused1,
+        UnInstantiated uninstantiated2,
+        Unused unused2) {
+      System.out.println(
+          "doubleUninstantiatedThenUnused " + uninstantiated1 + " and " + uninstantiated2);
+    }
+
+    @NeverInline
+    static void doubleUnusedThenUninstantiated(
+        Unused unused1,
+        UnInstantiated uninstantiated1,
+        Unused unused2,
+        UnInstantiated uninstantiated2) {
+      System.out.println(
+          "doubleUnusedThenUninstantiated " + uninstantiated1 + " and " + uninstantiated2);
+    }
+
+    @NeverInline
+    static void tripleUninstantiatedThenUnused(
+        UnInstantiated uninstantiated1,
+        Unused unused0,
+        Unused unused1,
+        UnInstantiated uninstantiated0,
+        UnInstantiated uninstantiated2,
+        Unused unused2) {
+      System.out.println(
+          "tripleUninstantiatedThenUnused "
+              + uninstantiated1
+              + " and "
+              + uninstantiated2
+              + " and "
+              + uninstantiated0);
+    }
+
+    @NeverInline
+    static void tripleUnusedThenUninstantiated(
+        Unused unused1,
+        UnInstantiated uninstantiated0,
+        UnInstantiated uninstantiated1,
+        Unused unused0,
+        Unused unused2,
+        UnInstantiated uninstantiated2) {
+      System.out.println(
+          "tripleUnusedThenUninstantiated "
+              + uninstantiated1
+              + " and "
+              + uninstantiated2
+              + " and "
+              + uninstantiated0);
+    }
+
+    @NeverInline
+    static void withWideParameters(
+        long longUnused0,
+        long longUnused1,
+        UnInstantiated uninstantiated0,
+        UnInstantiated uninstantiated1,
+        long longUnused2,
+        long longUnused3,
+        UnInstantiated uninstantiated2) {
+      System.out.println(
+          "withWideParameters "
+              + uninstantiated1
+              + " and "
+              + uninstantiated2
+              + " and "
+              + uninstantiated0);
+    }
+  }
+
+  private static class UnInstantiated {}
+
+  private static class Unused {}
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
index f6649b7..eb98862 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
@@ -9,6 +9,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
@@ -20,6 +21,7 @@
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
 import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.HashMap;
@@ -100,6 +102,7 @@
     ClassSubject impl = inspector.clazz(bClassName);
     assertThat(impl, isPresent());
     assertThat(impl, not(isRenamed()));
+
     // API entry is kept, hence the presence of Metadata.
     KmClassSubject kmClass = impl.getKmClass();
     assertThat(kmClass, isPresent());
@@ -110,6 +113,7 @@
     ClassSubject bKt = inspector.clazz(bKtClassName);
     assertThat(bKt, isPresent());
     assertThat(bKt, not(isRenamed()));
+
     // API entry is kept, hence the presence of Metadata.
     KmPackageSubject kmPackage = bKt.getKmPackage();
     assertThat(kmPackage, isPresent());
@@ -128,7 +132,7 @@
             .addKeepRules("-keep class **.B")
             .addKeepRules("-keep class **.I { <methods>; }")
             // Keep Super, but allow minification.
-            .addKeepRules("-keep,allowobfuscation class **.Super")
+            .addKeepRules("-keep,allowobfuscation class **.Super { <methods>; }")
             // Keep the BKt method, which will be called from other kotlin code.
             .addKeepRules("-keep class **.BKt { <methods>; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
@@ -158,11 +162,24 @@
     ClassSubject sup = inspector.clazz(superClassName);
     assertThat(sup, isRenamed());
 
+    MethodSubject foo = sup.uniqueMethodWithName("foo");
+    assertThat(foo, isRenamed());
+
+    KmClassSubject kmClass = sup.getKmClass();
+    assertThat(kmClass, isPresent());
+
+    // TODO(b/70169921): would be better to look up function with the original name, "foo".
+    KmFunctionSubject kmFunction = kmClass.kmFunctionWithUniqueName(foo.getFinalName());
+    assertThat(kmFunction, isPresent());
+    assertThat(kmFunction, not(isExtensionFunction()));
+    assertEquals(foo.getJvmMethodSignatureAsString(), kmFunction.signature().asString());
+
     ClassSubject impl = inspector.clazz(bClassName);
     assertThat(impl, isPresent());
     assertThat(impl, not(isRenamed()));
+
     // API entry is kept, hence the presence of Metadata.
-    KmClassSubject kmClass = impl.getKmClass();
+    kmClass = impl.getKmClass();
     assertThat(kmClass, isPresent());
     List<ClassSubject> superTypes = kmClass.getSuperTypes();
     assertTrue(superTypes.stream().noneMatch(
@@ -177,7 +194,7 @@
     KmPackageSubject kmPackage = bKt.getKmPackage();
     assertThat(kmPackage, isPresent());
 
-    KmFunctionSubject kmFunction = kmPackage.kmFunctionWithUniqueName("fun");
+    kmFunction = kmPackage.kmFunctionWithUniqueName("fun");
     assertThat(kmFunction, isPresent());
     assertThat(kmFunction, not(isExtensionFunction()));
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
index 2dc57bf..5b9dfed 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
@@ -4,25 +4,34 @@
 package com.android.tools.r8.kotlin.metadata;
 
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
+import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -88,7 +97,6 @@
 
   private void inspectMerged(CodeInspector inspector) {
     String utilClassName = PKG + ".multifileclass_lib.UtilKt";
-    String signedClassName = PKG + ".multifileclass_lib.UtilKt__SignedKt";
 
     ClassSubject util = inspector.clazz(utilClassName);
     assertThat(util, isPresent());
@@ -98,22 +106,10 @@
     assertThat(commaJoinOfInt, not(isRenamed()));
     MethodSubject joinOfInt = util.uniqueMethodWithName("joinOfInt");
     assertThat(joinOfInt, not(isPresent()));
-    // API entry is kept, hence the presence of Metadata.
-    AnnotationSubject annotationSubject = util.annotation(METADATA_TYPE);
-    assertThat(annotationSubject, isPresent());
-    // TODO(b/70169921): need further inspection.
 
-    ClassSubject signed = inspector.clazz(signedClassName);
-    assertThat(signed, isRenamed());
-    commaJoinOfInt = signed.uniqueMethodWithName("commaSeparatedJoinOfInt");
-    assertThat(commaJoinOfInt, isPresent());
-    assertThat(commaJoinOfInt, not(isRenamed()));
-    joinOfInt = signed.uniqueMethodWithName("joinOfInt");
-    assertThat(joinOfInt, isRenamed());
-    // API entry is kept, hence the presence of Metadata.
-    annotationSubject = util.annotation(METADATA_TYPE);
-    assertThat(annotationSubject, isPresent());
-    // TODO(b/70169921): need further inspection.
+    inspectMetadataForFacade(inspector, util);
+
+    inspectSignedKt(inspector);
   }
 
   @Test
@@ -146,7 +142,6 @@
 
   private void inspectRenamed(CodeInspector inspector) {
     String utilClassName = PKG + ".multifileclass_lib.UtilKt";
-    String signedClassName = PKG + ".multifileclass_lib.UtilKt__SignedKt";
 
     ClassSubject util = inspector.clazz(utilClassName);
     assertThat(util, isPresent());
@@ -157,21 +152,48 @@
     MethodSubject joinOfInt = util.uniqueMethodWithName("joinOfInt");
     assertThat(joinOfInt, isPresent());
     assertThat(joinOfInt, isRenamed());
+
+    inspectMetadataForFacade(inspector, util);
+
+    inspectSignedKt(inspector);
+  }
+
+  private void inspectMetadataForFacade(CodeInspector inspector, ClassSubject util) {
     // API entry is kept, hence the presence of Metadata.
     AnnotationSubject annotationSubject = util.annotation(METADATA_TYPE);
     assertThat(annotationSubject, isPresent());
-    // TODO(b/70169921): need further inspection.
+    KotlinClassMetadata metadata = util.getKotlinClassMetadata();
+    assertNotNull(metadata);
+    assertTrue(metadata instanceof KotlinClassMetadata.MultiFileClassFacade);
+    KotlinClassMetadata.MultiFileClassFacade facade =
+        (KotlinClassMetadata.MultiFileClassFacade) metadata;
+    List<String> partClassNames = facade.getPartClassNames();
+    assertEquals(2, partClassNames.size());
+    for (String partClassName : partClassNames) {
+      ClassSubject partClass =
+          inspector.clazz(DescriptorUtils.getJavaTypeFromBinaryName(partClassName));
+      assertThat(partClass, isRenamed());
+    }
+  }
 
+  private void inspectSignedKt(CodeInspector inspector) {
+    String signedClassName = PKG + ".multifileclass_lib.UtilKt__SignedKt";
     ClassSubject signed = inspector.clazz(signedClassName);
     assertThat(signed, isRenamed());
-    commaJoinOfInt = signed.uniqueMethodWithName("commaSeparatedJoinOfInt");
+    MethodSubject commaJoinOfInt = signed.uniqueMethodWithName("commaSeparatedJoinOfInt");
     assertThat(commaJoinOfInt, isPresent());
     assertThat(commaJoinOfInt, not(isRenamed()));
-    joinOfInt = signed.uniqueMethodWithName("joinOfInt");
+    MethodSubject joinOfInt = signed.uniqueMethodWithName("joinOfInt");
     assertThat(joinOfInt, isRenamed());
+
     // API entry is kept, hence the presence of Metadata.
-    annotationSubject = util.annotation(METADATA_TYPE);
-    assertThat(annotationSubject, isPresent());
-    // TODO(b/70169921): need further inspection.
+    KmPackageSubject kmPackage = signed.getKmPackage();
+    assertThat(kmPackage, isPresent());
+    KmFunctionSubject kmFunction =
+        kmPackage.kmFunctionExtensionWithUniqueName("commaSeparatedJoinOfInt");
+    assertThat(kmFunction, isPresent());
+    assertThat(kmFunction, isExtensionFunction());
+    // TODO(b/70169921): Inspect that parameter type has a correct type argument, Int.
+    // TODO(b/70169921): Inspect that the name in KmFunction is still 'join' so that apps can refer.
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/function_lib/B.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/function_lib/B.kt
index d327fda..41d286c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/function_lib/B.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/function_lib/B.kt
@@ -11,6 +11,10 @@
   override fun doStuff() {
     println("do stuff")
   }
+
+  fun foo() {
+    println("Super::foo")
+  }
 }
 
 class B : Super()
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index ba27c33..72ede27 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -223,7 +223,10 @@
         appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     if (resolutionResult.isVirtualTarget()) {
-      LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView, appInfo);
+      LookupResult lookupResult =
+          resolutionResult.lookupVirtualDispatchTargets(
+              appView.definitionForProgramType(buildType(Main.class, appView.dexItemFactory())),
+              appView);
       assertTrue(lookupResult.isLookupResultSuccess());
       Set<DexEncodedMethod> targets = lookupResult.asLookupResultSuccess().getMethodTargets();
       Set<DexType> targetHolders =
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
index 3fcc33d..27da1c4 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
@@ -18,6 +18,7 @@
 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.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -57,7 +58,9 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets =
         lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
index bdd5dd3..c8df559 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
@@ -18,6 +18,7 @@
 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.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -55,7 +56,9 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets =
         lookupResult.asLookupResultSuccess().getMethodTargets().stream()
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 266cb48..9136188 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
@@ -19,6 +19,7 @@
 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.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -60,7 +61,9 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets =
         lookupResult.asLookupResultSuccess().getMethodTargets().stream()
@@ -102,7 +105,9 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets =
         lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java
new file mode 100644
index 0000000..da61d6a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java
@@ -0,0 +1,118 @@
+// 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.interfacetargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.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.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.LookupResult;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** This test is testing visiting direct subtypes and finding the right targets. */
+@RunWith(Parameterized.class)
+public class DuplicateImportsTest extends TestBase {
+
+  private static final String[] EXPECTED = new String[] {"J.foo"};
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public DuplicateImportsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(I.class, J.class, A.class, Main.class).build(), Main.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    assertTrue(lookupResult.isLookupResultSuccess());
+    Set<String> targets =
+        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addInnerClasses(DuplicateImportsTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(DuplicateImportsTest.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @NeverMerge
+  public interface I {
+    void foo();
+  }
+
+  @NeverMerge
+  public interface J extends I {
+
+    @Override
+    @NeverInline
+    default void foo() {
+      System.out.println("J.foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class A implements I, J {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      ((I) new A()).foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
index 2614319..016347b 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ToolHelper.DexVm;
 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.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassTransformer;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -58,9 +59,14 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "<clinit>", appInfo.dexItemFactory());
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     Assert.assertThrows(
         AssertionError.class,
-        () -> appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appView));
+        () ->
+            appInfo
+                .resolveMethod(method.holder, method)
+                .lookupVirtualDispatchTargets(context, appView));
   }
 
   private Matcher<String> getExpected() {
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
index 1ca966b..0ba0226 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.TestRuntime;
 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.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -49,9 +50,14 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     Assert.assertThrows(
         AssertionError.class,
-        () -> appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appView));
+        () ->
+            appInfo
+                .resolveMethod(method.holder, method)
+                .lookupVirtualDispatchTargets(context, appView));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
index 251fa37..eeca9fe 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
@@ -18,6 +18,7 @@
 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.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -56,7 +57,9 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(J.class, "bar", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets =
         lookupResult.asLookupResultSuccess().getMethodTargets().stream()
@@ -84,7 +87,6 @@
         .enableNeverClassInliningAnnotations()
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
-        .compile()
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(EXPECTED);
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
index eff0088..64c81fa 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
@@ -18,6 +18,7 @@
 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.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -56,7 +57,9 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets =
         lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
index 9552212..a21d3e1 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
@@ -18,6 +18,7 @@
 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.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -57,7 +58,9 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets =
         lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
index 41d4879..80caa62 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
@@ -17,6 +17,7 @@
 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.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -56,7 +57,9 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets =
         lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
index 84236e5..713d80e 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
@@ -17,6 +17,7 @@
 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.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -55,7 +56,9 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets =
         lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
index a952bb3..ba468e0 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
@@ -17,6 +17,7 @@
 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.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -55,7 +56,9 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets =
         lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/NonAbstractWidening.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/NonAbstractWidening.java
new file mode 100644
index 0000000..f9d75e4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/NonAbstractWidening.java
@@ -0,0 +1,16 @@
+// 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.packageprivate;
+
+import com.android.tools.r8.resolution.packageprivate.a.AbstractWidening;
+import com.android.tools.r8.resolution.packageprivate.a.I;
+
+public class NonAbstractWidening extends AbstractWidening implements I {
+
+  @Override
+  public void foo() {
+    System.out.println("Method declaration will be removed");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java
new file mode 100644
index 0000000..a714a03
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java
@@ -0,0 +1,121 @@
+// 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.packageprivate;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+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 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.LookupResult;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.resolution.packageprivate.a.Abstract;
+import com.android.tools.r8.resolution.packageprivate.a.I;
+import com.android.tools.r8.resolution.packageprivate.a.NonAbstract;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import org.junit.BeforeClass;
+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 PackagePrivateClasspathWidenTest extends TestBase {
+
+  private static final String[] EXPECTED = new String[] {"C.foo", "C.foo"};
+  private static final Class[] CLASSPATH_CLASSES =
+      new Class[] {Abstract.class, NonAbstract.class, I.class};
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public PackagePrivateClasspathWidenTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private static Path classPathJar = null;
+
+  @BeforeClass
+  public static void createClassPathJar() throws IOException {
+    classPathJar = getStaticTemp().newFile("classpath.jar").toPath();
+    writeClassesToJar(classPathJar, CLASSPATH_CLASSES);
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(C.class, Main.class).addClasspathFiles(classPathJar).build(), Main.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexMethod method = buildNullaryVoidMethod(Abstract.class, "foo", appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Abstract.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    assertTrue(lookupResult.isLookupResultSuccess());
+    Set<String> targets =
+        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    ImmutableSet<String> expected = ImmutableSet.of(C.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addProgramClasses(C.class, Main.class)
+        .addRunClasspathFiles(buildOnDexRuntime(parameters, classPathJar))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(C.class, Main.class)
+        .addClasspathClasses(CLASSPATH_CLASSES)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addRunClasspathFiles(buildOnDexRuntime(parameters, classPathJar))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  public static class C extends NonAbstract {
+
+    @Override
+    public void foo() {
+      System.out.println("C.foo");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      C c = new C();
+      Abstract.run(c);
+      c.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryTest.java
index f465727..eb08197 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryTest.java
@@ -16,6 +16,7 @@
 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.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.resolution.packageprivate.a.A;
@@ -56,18 +57,18 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets =
         lookupResult.asLookupResultSuccess().getMethodTargets().stream()
             .map(DexEncodedMethod::qualifiedName)
             .collect(Collectors.toSet());
-    // TODO(b/149363086): Fix expection, should not include C.bar().
     ImmutableSet<String> expected =
         ImmutableSet.of(
             A.class.getTypeName() + ".bar",
             B.class.getTypeName() + ".bar",
-            C.class.getTypeName() + ".bar",
             D.class.getTypeName() + ".bar");
     assertEquals(expected, targets);
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryWithNarrowingTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryWithNarrowingTest.java
index 35791e7..619ee45 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryWithNarrowingTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryWithNarrowingTest.java
@@ -18,6 +18,7 @@
 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.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.resolution.packageprivate.PackagePrivateReentryTest.C;
@@ -60,18 +61,18 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets =
         lookupResult.asLookupResultSuccess().getMethodTargets().stream()
             .map(DexEncodedMethod::qualifiedName)
             .collect(Collectors.toSet());
-    // TODO(b/149363086): Fix expection, should not include C.bar().
     ImmutableSet<String> expected =
         ImmutableSet.of(
             A.class.getTypeName() + ".bar",
             B.class.getTypeName() + ".bar",
-            C.class.getTypeName() + ".bar",
             D.class.getTypeName() + ".bar");
     assertEquals(expected, targets);
   }
@@ -107,7 +108,7 @@
 
   private byte[] getDWithPackagePrivateFoo() throws NoSuchMethodException, IOException {
     return transformer(D.class)
-        .setAccessFlags(D.class.getDeclaredMethod("bar", null), m -> m.unsetPublic())
+        .setAccessFlags(D.class.getDeclaredMethod("bar"), m -> m.unsetPublic())
         .transform();
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethod2Test.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethod2Test.java
new file mode 100644
index 0000000..cc85b0d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethod2Test.java
@@ -0,0 +1,166 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.packageprivate;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper.DexVm;
+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.LookupResult;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.resolution.packageprivate.a.Abstract;
+import com.android.tools.r8.resolution.packageprivate.a.AbstractWidening;
+import com.android.tools.r8.resolution.packageprivate.a.I;
+import com.android.tools.r8.resolution.packageprivate.a.J;
+import com.android.tools.r8.resolution.packageprivate.a.NonAbstractWideningExtendingA;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.MethodVisitor;
+
+@RunWith(Parameterized.class)
+public class PackagePrivateWithDefaultMethod2Test extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public PackagePrivateWithDefaultMethod2Test(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(
+                    Abstract.class,
+                    AbstractWidening.class,
+                    I.class,
+                    A.class,
+                    NonAbstractWideningExtendingA.class,
+                    J.class,
+                    Main.class)
+                .addClassProgramData(getNonAbstractWithoutDeclaredMethods())
+                .build(),
+            Main.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    assertTrue(lookupResult.isLookupResultSuccess());
+    Set<String> targets =
+        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    // TODO(b/148591377): The set should be empty.
+    ImmutableSet<String> expected = ImmutableSet.of(AbstractWidening.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+  }
+
+  @Test
+  public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
+    TestRunResult<?> runResult =
+        testForRuntime(parameters)
+            .addProgramClasses(
+                Abstract.class,
+                AbstractWidening.class,
+                I.class,
+                A.class,
+                NonAbstractWideningExtendingA.class,
+                J.class,
+                Main.class)
+            .addProgramClassFileData(getNonAbstractWithoutDeclaredMethods())
+            .run(parameters.getRuntime(), Main.class);
+    if (parameters.isDexRuntime()
+        && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
+      runResult.assertFailure();
+    } else {
+      runResult.assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
+    }
+  }
+
+  @Test
+  public void testR8() throws ExecutionException, CompilationFailedException, IOException {
+    R8TestRunResult runResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(
+                Abstract.class,
+                AbstractWidening.class,
+                I.class,
+                A.class,
+                NonAbstractWideningExtendingA.class,
+                J.class,
+                Main.class)
+            .addProgramClassFileData(getNonAbstractWithoutDeclaredMethods())
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(Main.class)
+            .run(parameters.getRuntime(), Main.class)
+            .disassemble();
+    if (parameters.isDexRuntime()
+        && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
+      runResult.assertFailure();
+    } else {
+      runResult.assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
+    }
+  }
+
+  private byte[] getNonAbstractWithoutDeclaredMethods() throws IOException {
+    return transformer(NonAbstractWidening.class)
+        .addClassTransformer(
+            new ClassTransformer() {
+              @Override
+              public MethodVisitor visitMethod(
+                  int access,
+                  String name,
+                  String descriptor,
+                  String signature,
+                  String[] exceptions) {
+                if (!name.equals("foo")) {
+                  return super.visitMethod(access, name, descriptor, signature, exceptions);
+                }
+                return null;
+              }
+            })
+        .transform();
+  }
+
+  public static class A extends NonAbstractWidening {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      NonAbstractWideningExtendingA d = new NonAbstractWideningExtendingA();
+      Abstract.run(d);
+      d.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
index c2d041c..42efe5c 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
@@ -19,6 +19,7 @@
 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.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.resolution.packageprivate.a.Abstract;
@@ -71,7 +72,9 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets =
         lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/WidenAccessOutsidePackageTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/WidenAccessOutsidePackageTest.java
index 28c8c2f..b761626 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/WidenAccessOutsidePackageTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/WidenAccessOutsidePackageTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.resolution.packageprivate;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -17,6 +16,7 @@
 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.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.resolution.packageprivate.a.A;
@@ -57,18 +57,15 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
-    assertTrue(lookupResult.isLookupResultSuccess());
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
     Set<String> targets =
         lookupResult.asLookupResultSuccess().getMethodTargets().stream()
             .map(DexEncodedMethod::qualifiedName)
             .collect(Collectors.toSet());
-    // TODO(b/149363086): Fix expectation.
     ImmutableSet<String> expected =
-        ImmutableSet.of(
-            A.class.getTypeName() + ".bar",
-            B.class.getTypeName() + ".bar",
-            C.class.getTypeName() + ".bar");
+        ImmutableSet.of(A.class.getTypeName() + ".bar", B.class.getTypeName() + ".bar");
     assertEquals(expected, targets);
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/a/AbstractWidening.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/a/AbstractWidening.java
new file mode 100644
index 0000000..472a97b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/a/AbstractWidening.java
@@ -0,0 +1,11 @@
+// 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.packageprivate.a;
+
+public abstract class AbstractWidening extends Abstract {
+
+  @Override
+  public abstract void foo();
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/a/NonAbstractWideningExtendingA.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/a/NonAbstractWideningExtendingA.java
new file mode 100644
index 0000000..ec0bd6a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/a/NonAbstractWideningExtendingA.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.packageprivate.a;
+
+import com.android.tools.r8.resolution.packageprivate.PackagePrivateWithDefaultMethod2Test;
+
+public class NonAbstractWideningExtendingA extends PackagePrivateWithDefaultMethod2Test.A
+    implements J {}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
index 9538dec..88c7b9e 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
@@ -18,6 +18,7 @@
 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.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -56,7 +57,9 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets =
         lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
index 4133fbe..00ef1db 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
@@ -18,6 +18,7 @@
 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.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -56,15 +57,15 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets =
         lookupResult.asLookupResultSuccess().getMethodTargets().stream()
             .map(DexEncodedMethod::qualifiedName)
             .collect(Collectors.toSet());
-    // TODO(b/148591377): I.foo() should ideally not be included in the set.
-    ImmutableSet<String> expected =
-        ImmutableSet.of(I.class.getTypeName() + ".foo", J.class.getTypeName() + ".foo");
+    ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
index 264ddb0..3c30267 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
@@ -18,6 +18,7 @@
 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.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -56,7 +57,9 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets =
         lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
index ff58047..dccf199 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
@@ -18,6 +18,7 @@
 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.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -59,7 +60,9 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets =
         lookupResult.asLookupResultSuccess().getMethodTargets().stream()
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
new file mode 100644
index 0000000..da24bcb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java
@@ -0,0 +1,119 @@
+// 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.virtualtargets;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+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 com.android.tools.r8.graph.AppView;
+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.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 InvalidResolutionToThisTarget extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvalidResolutionToThisTarget(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @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());
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addProgramClasses(A.class)
+        .addProgramClassFileData(getMainWithModifiedReceiverCall())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(containsString("java.lang.VerifyError"));
+  }
+
+  @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();
+            });
+  }
+
+  private byte[] getMainWithModifiedReceiverCall() throws IOException {
+    return transformer(Main.class)
+        .transformMethodInsnInMethod(
+            "main",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              if (name.equals("foo")) {
+                continuation.apply(
+                    opcode,
+                    DescriptorUtils.getBinaryNameFromJavaType(A.class.getTypeName()),
+                    name,
+                    descriptor,
+                    isInterface);
+              } else {
+                continuation.apply(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .transform();
+  }
+
+  public static class A {
+
+    public void foo() {
+      System.out.println("A.foo");
+    }
+  }
+
+  public static class Main {
+
+    public void foo() {
+      System.out.println("Main.foo");
+      new A().foo();
+    }
+
+    public static void main(String[] args) {
+      new Main().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
index 055c113..7d32527 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
@@ -18,6 +18,7 @@
 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.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -55,7 +56,9 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets =
         lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java
index 45c208a..37c071a 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java
@@ -5,6 +5,9 @@
 package com.android.tools.r8.resolution.virtualtargets;
 
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
@@ -12,11 +15,21 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.ToolHelper.DexVm;
+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.LookupResult;
+import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.resolution.virtualtargets.package_a.Middle;
 import com.android.tools.r8.resolution.virtualtargets.package_a.Top;
 import com.android.tools.r8.resolution.virtualtargets.package_a.TopRunner;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -39,6 +52,30 @@
   }
 
   @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(Top.class, Middle.class, Bottom.class, TopRunner.class, Main.class)
+                .build(),
+            Main.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexMethod method = buildNullaryVoidMethod(Top.class, "clear", appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(TopRunner.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    assertTrue(lookupResult.isLookupResultSuccess());
+    Set<String> targets =
+        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    ImmutableSet<String> expected =
+        ImmutableSet.of(Top.class.getTypeName() + ".clear", Middle.class.getTypeName() + ".clear");
+    assertEquals(expected, targets);
+  }
+
+  @Test
   public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
     TestRunResult<?> runResult =
         testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
@@ -60,7 +97,7 @@
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines("Bottom.clear()", "Bottom.clear()");
+        .assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
   }
 
   public static class Bottom extends Middle {
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java
index e786da6..dfc9d43 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java
@@ -5,22 +5,34 @@
 package com.android.tools.r8.resolution.virtualtargets;
 
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.ToolHelper.DexVm;
+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.LookupResult;
+import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModel;
 import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModelRunner;
 import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModelRunnerWithCast;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -32,6 +44,9 @@
   private static final String[] EXPECTED =
       new String[] {"ViewModel.clear()", "MyViewModel.clear()", "ViewModel.clear()"};
 
+  private static final String[] R8_OUTPUT =
+      new String[] {"MyViewModel.clear()", "MyViewModel.clear()", "MyViewModel.clear()"};
+
   private final TestParameters parameters;
 
   @Parameters(name = "{0}")
@@ -44,6 +59,30 @@
   }
 
   @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(MyViewModel.class, ViewModel.class, Main.class, ViewModelRunner.class)
+                .build(),
+            Main.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexMethod method = buildNullaryVoidMethod(ViewModel.class, "clear", appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+    DexProgramClass context =
+        appView.definitionForProgramType(
+            buildType(ViewModelRunner.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    assertTrue(lookupResult.isLookupResultSuccess());
+    Set<String> targets =
+        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    ImmutableSet<String> expected = ImmutableSet.of(ViewModel.class.getTypeName() + ".clear");
+    assertEquals(expected, targets);
+  }
+
+  @Test
   public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
     TestRunResult<?> runResult =
         testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
@@ -60,19 +99,30 @@
 
   @Test
   public void testR8() throws ExecutionException, CompilationFailedException, IOException {
-    R8TestRunResult runResult =
-        testForR8(parameters.getBackend())
-            .addProgramClasses(
-                MyViewModel.class, Main.class, ViewModel.class, ViewModelRunner.class)
-            .addKeepMainRule(Main.class)
-            .setMinApi(parameters.getApiLevel())
-            .run(parameters.getRuntime(), Main.class);
-    if (parameters.isDexRuntime()
-        && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
-      runResult.assertFailureWithErrorThatMatches(containsString("overrides final"));
-    } else {
-      runResult.assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException"));
-    }
+    // TODO(b/148429150): Fix R8 to output expected.
+    testForR8(parameters.getBackend())
+        .addProgramClasses(MyViewModel.class, Main.class, ViewModel.class, ViewModelRunner.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(R8_OUTPUT);
+  }
+
+  @Test
+  public void testResolutionWithInvalidInvoke() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(MyViewModel.class, ViewModel.class, Main.class, ViewModelRunner.class)
+                .build(),
+            Main.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexMethod method = buildNullaryVoidMethod(ViewModel.class, "clear", appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    assertTrue(lookupResult.isLookupResultFailure());
   }
 
   @Test
@@ -94,19 +144,39 @@
   @Test
   public void testR8WithInvalidInvoke()
       throws ExecutionException, CompilationFailedException, IOException {
-    R8TestRunResult runResult =
-        testForR8(parameters.getBackend())
-            .addProgramClasses(MyViewModel.class, ViewModel.class, ViewModelRunner.class)
-            .addProgramClassFileData(getModifiedMainWithIllegalInvokeToViewModelClear())
-            .addKeepMainRule(Main.class)
-            .setMinApi(parameters.getApiLevel())
-            .run(parameters.getRuntime(), Main.class);
-    if (parameters.isDexRuntime()
-        && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
-      runResult.assertFailureWithErrorThatMatches(containsString("overrides final"));
-    } else {
-      runResult.assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException"));
-    }
+    // TODO(b/148429150): Fix R8 to output expected.
+    testForR8(parameters.getBackend())
+        .addProgramClasses(MyViewModel.class, ViewModel.class, ViewModelRunner.class)
+        .addProgramClassFileData(getModifiedMainWithIllegalInvokeToViewModelClear())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException"));
+  }
+
+  @Test
+  public void testResolutionWithAmbiguousInvoke() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(
+                    MyViewModel.class, ViewModel.class, Main.class, ViewModelRunnerWithCast.class)
+                .build(),
+            Main.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexMethod method = buildNullaryVoidMethod(ViewModel.class, "clear", appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+    DexProgramClass context =
+        appView.definitionForProgramType(
+            buildType(ViewModelRunnerWithCast.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    assertTrue(lookupResult.isLookupResultSuccess());
+    Set<String> targets =
+        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    ImmutableSet<String> expected = ImmutableSet.of(ViewModel.class.getTypeName() + ".clear");
+    assertEquals(expected, targets);
   }
 
   @Test
@@ -129,19 +199,14 @@
   @Test
   public void testR8WithAmbiguousInvoke()
       throws ExecutionException, CompilationFailedException, IOException {
-    R8TestRunResult runResult =
-        testForR8(parameters.getBackend())
-            .addProgramClasses(MyViewModel.class, ViewModel.class, Main.class)
-            .addProgramClassFileData(getModifiedViewModelRunnerWithDirectMyViewModelTarget())
-            .addKeepMainRule(Main.class)
-            .setMinApi(parameters.getApiLevel())
-            .run(parameters.getRuntime(), Main.class);
-    if (parameters.isDexRuntime()
-        && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
-      runResult.assertFailureWithErrorThatMatches(containsString("overrides final"));
-    } else {
-      runResult.assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException"));
-    }
+    // TODO(b/148429150): Fix R8 to output expected.
+    testForR8(parameters.getBackend())
+        .addProgramClasses(MyViewModel.class, ViewModel.class, Main.class)
+        .addProgramClassFileData(getModifiedViewModelRunnerWithDirectMyViewModelTarget())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(R8_OUTPUT);
   }
 
   private byte[] getModifiedMainWithIllegalInvokeToViewModelClear() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PrivateOverrideOfVirtualTargetTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PrivateOverrideOfVirtualTargetTest.java
new file mode 100644
index 0000000..915cd21
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PrivateOverrideOfVirtualTargetTest.java
@@ -0,0 +1,149 @@
+// 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.virtualtargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+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 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.LookupResult;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.resolution.virtualtargets.package_a.A;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class PrivateOverrideOfVirtualTargetTest extends TestBase {
+
+  private final TestParameters parameters;
+  private static final String[] EXPECTED =
+      new String[] {"A.foo", "A.bar", "B.foo", "A.bar", "B.bar"};
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public PrivateOverrideOfVirtualTargetTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(A.class, Main.class)
+                .addClassProgramData(getBWithModifiedInvokes())
+                .build(),
+            DefaultWithoutTopTest.Main.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(B.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    assertTrue(lookupResult.isLookupResultSuccess());
+    Set<String> targets =
+        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    ImmutableSet<String> expected = ImmutableSet.of(A.class.getTypeName() + ".bar");
+    assertEquals(expected, targets);
+  }
+
+  @Test
+  public void testRuntime()
+      throws NoSuchMethodException, IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addProgramClasses(A.class, Main.class)
+        .addProgramClassFileData(getBWithModifiedInvokes())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8()
+      throws NoSuchMethodException, IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, Main.class)
+        .addProgramClassFileData(getBWithModifiedInvokes())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  private byte[] getBWithModifiedInvokes() throws NoSuchMethodException, IOException {
+    Box<Boolean> modifyOwner = new Box<>(true);
+    return transformer(B.class)
+        .setPrivate(B.class.getDeclaredMethod("bar"))
+        .transformMethodInsnInMethod(
+            "callOnB",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              if (name.equals("foo")) {
+                continuation.apply(opcode, owner, name, descriptor, isInterface);
+                return;
+              }
+              if (modifyOwner.get()) {
+                continuation.apply(
+                    Opcodes.INVOKEVIRTUAL,
+                    DescriptorUtils.getBinaryNameFromJavaType(A.class.getTypeName()),
+                    name,
+                    descriptor,
+                    isInterface);
+                modifyOwner.set(false);
+              } else {
+                continuation.apply(Opcodes.INVOKEVIRTUAL, owner, name, descriptor, isInterface);
+              }
+            })
+        .transform();
+  }
+
+  public static class B extends A {
+    private void foo() {
+      System.out.println("B.foo");
+    }
+
+    @Override
+    protected /* private */ void bar() {
+      System.out.println("B.bar");
+    }
+
+    void callOnB() {
+      foo();
+      bar(); /* will become A.bar() */
+      bar(); /* will become B.bar() */
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      B b = new B();
+      A.run(b);
+      b.callOnB();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
index 570527c..3afeb68 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
@@ -18,6 +18,7 @@
 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.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -56,7 +57,9 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(appView);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets =
         lookupResult.asLookupResultSuccess().getMethodTargets().stream()
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/A.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/A.java
new file mode 100644
index 0000000..1b311d5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/A.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.virtualtargets.package_a;
+
+public class A {
+
+  void foo() {
+    System.out.println("A.foo");
+  }
+
+  protected void bar() {
+    System.out.println("A.bar");
+  }
+
+  public static void run(A a) {
+    a.foo();
+    a.bar();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java b/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
index af204ec..5f6a618 100644
--- a/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
@@ -31,7 +31,7 @@
   public static void main(String[] args) throws Exception {
     C obj = new C();
     for (Method m : obj.getClass().getDeclaredMethods()) {
-      m.invoke(obj, null);
+      m.invoke(obj);
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index a471b18..7331a22 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexClass;
 import java.util.List;
 import java.util.function.Consumer;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 public class AbsentClassSubject extends ClassSubject {
 
@@ -147,4 +148,9 @@
   public KmPackageSubject getKmPackage() {
     return null;
   }
+
+  @Override
+  public KotlinClassMetadata getKotlinClassMetadata() {
+    return null;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index d72d1a8..b693ad9 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -18,6 +18,7 @@
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 public abstract class ClassSubject extends Subject {
 
@@ -174,4 +175,6 @@
   public abstract KmClassSubject getKmClass();
 
   public abstract KmPackageSubject getKmPackage();
+
+  public abstract KotlinClassMetadata getKotlinClassMetadata();
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index c0dba89..dc65b71 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -355,8 +355,25 @@
     KotlinClassMetadata metadata =
         KotlinClassMetadataReader.toKotlinClassMetadata(
             codeInspector.getFactory().kotlin, annotationSubject.getAnnotation());
-    assertTrue(metadata instanceof KotlinClassMetadata.FileFacade);
-    KotlinClassMetadata.FileFacade kFile = (KotlinClassMetadata.FileFacade) metadata;
-    return new FoundKmPackageSubject(codeInspector, getDexClass(), kFile.toKmPackage());
+    assertTrue(metadata instanceof KotlinClassMetadata.FileFacade
+        || metadata instanceof KotlinClassMetadata.MultiFileClassPart);
+    if (metadata instanceof KotlinClassMetadata.FileFacade) {
+      KotlinClassMetadata.FileFacade kFile = (KotlinClassMetadata.FileFacade) metadata;
+      return new FoundKmPackageSubject(codeInspector, getDexClass(), kFile.toKmPackage());
+    } else {
+      KotlinClassMetadata.MultiFileClassPart kPart =
+          (KotlinClassMetadata.MultiFileClassPart) metadata;
+      return new FoundKmPackageSubject(codeInspector, getDexClass(), kPart.toKmPackage());
+    }
+  }
+
+  @Override
+  public KotlinClassMetadata getKotlinClassMetadata() {
+    AnnotationSubject annotationSubject = annotation(METADATA_TYPE);
+    if (!annotationSubject.isPresent()) {
+      return null;
+    }
+    return KotlinClassMetadataReader.toKotlinClassMetadata(
+        codeInspector.getFactory().kotlin, annotationSubject.getAnnotation());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
index b9fa99a..5b3338b 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
@@ -80,6 +80,7 @@
     }
   }
 
+  // TODO(b/70169921): Search both original and renamed names.
   default KmFunctionSubject kmFunctionOrExtensionWithUniqueName(String name, boolean isExtension) {
     KmFunction foundFunction = null;
     for (KmFunction kmFunction : getKmDeclarationContainer().getFunctions()) {
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index cf26883..abd0f71 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -1224,6 +1224,7 @@
       assert shrinker in SHRINKERS
   else:
     options.shrinker = [shrinker for shrinker in SHRINKERS]
+
   if options.hash or options.version:
     # No need to build R8 if a specific version should be used.
     options.no_build = True
@@ -1266,7 +1267,7 @@
     options.no_logging = False
     # TODO(b/141081520): Remove logging filter once fixed.
     options.app_logging_filter = ['sqldelight']
-    options.shrinker = [shrinker for shrinker in SHRINKERS if shrinker != 'pg']
+    options.shrinker = ['r8', 'r8-full']
     print(options.shrinker)
 
   if options.golem: