Merge commit '1b868a6d67a6fd9b141bead5148cd42fc2c1c738' into dev-release
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 20faecf..cc80809 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -2,6 +2,16 @@
 # 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.
 
+from os import path
+from subprocess import check_output, Popen, PIPE, STDOUT
+
+FMT_CMD = path.join(
+    'third_party',
+    'google-java-format',
+    'google-java-format-google-java-format-1.7',
+    'scripts',
+    'google-java-format-diff.py')
+
 def CheckDoNotMerge(input_api, output_api):
   for l in input_api.change.FullDescriptionText().splitlines():
     if l.lower().startswith('do not merge'):
@@ -9,7 +19,42 @@
       return [output_api.PresubmitPromptWarning(msg, [])]
   return []
 
-def CheckChangeOnUpload(input_api, output_api):
+def CheckFormatting(input_api, output_api):
+  branch = (
+      check_output(['git', 'cl', 'upstream'])
+          .strip()
+          .replace('refs/heads/', ''))
   results = []
+  for f in input_api.AffectedFiles():
+    path = f.LocalPath()
+    if not path.endswith('.java'):
+      continue
+    diff = check_output(
+        ['git', 'diff', '--no-prefix', '-U0', branch, '--', path])
+    proc = Popen(FMT_CMD, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
+    (stdout, stderr) = proc.communicate(input=diff)
+    if len(stdout) > 0:
+      results.append(output_api.PresubmitError(stdout))
+  if len(results) > 0:
+    results.append(output_api.PresubmitError(
+        """Please fix the formatting by running:
+
+  git diff -U0 $(git cl upstream) | %s -p1 -i
+
+or bypass the checks with:
+
+  cl upload --bypass-hooks
+  """ % FMT_CMD))
+  return results
+
+def CheckChange(input_api, output_api):
+  results = []
+  results.extend(CheckFormatting(input_api, output_api))
   results.extend(CheckDoNotMerge(input_api, output_api))
   return results
+
+def CheckChangeOnCommit(input_api, output_api):
+  return CheckChange(input_api, output_api)
+
+def CheckChangeOnUpload(input_api, output_api):
+  return CheckChange(input_api, output_api)
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 85abb47..1fa0bb0 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -21,6 +21,8 @@
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.jar.CfApplicationWriter;
+import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.PrefixRewritingNamingLens;
 import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.utils.AndroidApp;
@@ -218,15 +220,27 @@
         markers.add(marker);
       }
 
-      new ApplicationWriter(
-              app,
-              null,
-              options,
-              marker == null ? null : ImmutableList.copyOf(markers),
-              GraphLense.getIdentityLense(),
-              PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView),
-              null)
-          .write(executor);
+      if (options.isGeneratingClassFiles()) {
+        new CfApplicationWriter(
+                app,
+                appView,
+                options,
+                marker,
+                GraphLense.getIdentityLense(),
+                NamingLens.getIdentityLens(),
+                null)
+            .write(options.getClassFileConsumer(), executor);
+      } else {
+        new ApplicationWriter(
+                app,
+                null,
+                options,
+                marker == null ? null : ImmutableList.copyOf(markers),
+                GraphLense.getIdentityLense(),
+                PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView),
+                null)
+            .write(executor);
+      }
       options.printWarnings();
     } catch (ExecutionException e) {
       throw unwrapExecutionException(e);
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index d421ec3..b7c9347 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -175,7 +175,7 @@
     void validate() {
       Reporter reporter = getReporter();
       if (getProgramConsumer() instanceof ClassFileConsumer) {
-        reporter.error("D8 does not support compiling to Java class files");
+        reporter.warning("Compiling to Java class files with D8 is not officially supported");
       }
       if (getAppBuilder().hasMainDexList()) {
         if (intermediate) {
@@ -332,6 +332,9 @@
     assert !internal.debug;
     internal.debug = getMode() == CompilationMode.DEBUG;
     internal.programConsumer = getProgramConsumer();
+    if (internal.programConsumer instanceof ClassFileConsumer) {
+      internal.enableCfInterfaceMethodDesugaring = true;
+    }
     internal.mainDexListConsumer = getMainDexListConsumer();
     internal.minimalMainDex = internal.debug;
     internal.minApiLevel = getMinApiLevel();
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index f66fff8..a6827d3 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -206,6 +206,8 @@
         compilationMode = CompilationMode.RELEASE;
       } else if (arg.equals("--file-per-class")) {
         outputMode = OutputMode.DexFilePerClass;
+      } else if (arg.equals("--classfile")) {
+        outputMode = OutputMode.ClassFile;
       } else if (arg.equals("--output")) {
         if (outputPath != null) {
           builder.error(
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 2086f5e..8817968 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
@@ -336,6 +337,7 @@
         assert appView.rootSet().verifyKeptFieldsAreAccessedAndLive(appViewWithLiveness.appInfo());
         assert appView.rootSet().verifyKeptMethodsAreTargetedAndLive(appViewWithLiveness.appInfo());
         assert appView.rootSet().verifyKeptTypesAreLive(appViewWithLiveness.appInfo());
+        assert appView.rootSet().verifyKeptItemsAreKept(appView.appInfo().app(), appView.appInfo());
 
         appView.rootSet().checkAllRulesAreUsed(options);
 
@@ -512,7 +514,7 @@
       assert appView.dexItemFactory().verifyNoCachedTypeLatticeElements();
 
       // Collect switch maps and ordinals maps.
-      if (options.enableEnumValueOptimization) {
+      if (options.enableEnumSwitchMapRemoval) {
         appViewWithLiveness.setAppInfo(new SwitchMapCollector(appViewWithLiveness).run());
       }
       if (options.enableEnumValueOptimization || options.enableEnumUnboxing) {
@@ -560,6 +562,10 @@
       // Collect the already pruned types before creating a new app info without liveness.
       Set<DexType> prunedTypes = appView.withLiveness().appInfo().getPrunedTypes();
 
+      // TODO: move to appview.
+      EnumValueInfoMapCollection enumValueInfoMapCollection =
+          appViewWithLiveness.appInfo().getEnumValueInfoMapCollection();
+
       if (!options.mainDexKeepRules.isEmpty()) {
         appView.setAppInfo(new AppInfoWithSubtyping(application));
         // No need to build a new main dex root set
@@ -624,11 +630,18 @@
 
           Enqueuer enqueuer = EnqueuerFactory.createForFinalTreeShaking(appView, keptGraphConsumer);
           appView.setAppInfo(
-              enqueuer.traceApplication(
-                  appView.rootSet(),
-                  options.getProguardConfiguration().getDontWarnPatterns(),
-                  executorService,
-                  timing));
+              enqueuer
+                  .traceApplication(
+                      appView.rootSet(),
+                      options.getProguardConfiguration().getDontWarnPatterns(),
+                      executorService,
+                      timing)
+                  .withEnumValueInfoMaps(enumValueInfoMapCollection));
+
+          appView.withGeneratedMessageLiteBuilderShrinker(
+              shrinker ->
+                  shrinker.removeDeadBuilderReferencesFromDynamicMethods(
+                      appViewWithLiveness, executorService, timing));
 
           if (Log.ENABLED && Log.isLoggingEnabledFor(GeneratedExtensionRegistryShrinker.class)) {
             appView.withGeneratedExtensionRegistryShrinker(
@@ -645,6 +658,7 @@
 
             TreePruner pruner = new TreePruner(appViewWithLiveness, treePrunerConfiguration);
             application = pruner.run(application);
+
             if (options.usageInformationConsumer != null) {
               ExceptionUtils.withFinishedResourceHandler(
                   options.reporter, options.usageInformationConsumer);
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 2992824..32a4bf7 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -250,7 +250,9 @@
       if (appView != null) {
         appView.appInfo().disableDefinitionForAssert();
       }
+      namingLens.setIsSortingBeforeWriting(true);
       application.dexItemFactory.sort(namingLens);
+      namingLens.setIsSortingBeforeWriting(false);
       if (appView != null) {
         appView.appInfo().enableDefinitionForAssert();
       }
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 df91f96..f15e96e 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -47,6 +47,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueKind;
 import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
 import com.android.tools.r8.graph.DexValue.DexValueMethodType;
 import com.android.tools.r8.graph.DexValue.DexValueNull;
@@ -184,93 +185,112 @@
     int header = dexReader.get() & 0xff;
     int valueArg = header >> 5;
     int valueType = header & 0x1f;
-    switch (valueType) {
-      case DexValue.VALUE_BYTE: {
-        assert valueArg == 0;
-        byte value = (byte) parseSigned(dexReader, 1);
-        return DexValue.DexValueByte.create(value);
-      }
-      case DexValue.VALUE_SHORT: {
-        int size = valueArg + 1;
-        short value = (short) parseSigned(dexReader, size);
-        return DexValue.DexValueShort.create(value);
-      }
-      case DexValue.VALUE_CHAR: {
-        int size = valueArg + 1;
-        char value = (char) parseUnsigned(dexReader, size);
-        return DexValue.DexValueChar.create(value);
-      }
-      case DexValue.VALUE_INT: {
-        int size = valueArg + 1;
-        int value = (int) parseSigned(dexReader, size);
-        return DexValue.DexValueInt.create(value);
-      }
-      case DexValue.VALUE_LONG: {
-        int size = valueArg + 1;
-        long value = parseSigned(dexReader, size);
-        return DexValue.DexValueLong.create(value);
-      }
-      case DexValue.VALUE_FLOAT: {
-        int size = valueArg + 1;
-        return DexValue.DexValueFloat.create(parseFloat(dexReader, size));
-      }
-      case DexValue.VALUE_DOUBLE: {
-        int size = valueArg + 1;
-        return DexValue.DexValueDouble.create(parseDouble(dexReader, size));
-      }
-      case DexValue.VALUE_STRING: {
-        int size = valueArg + 1;
-        int index = (int) parseUnsigned(dexReader, size);
-        DexString value = indexedItems.getString(index);
-        return new DexValue.DexValueString(value);
-      }
-      case DexValue.VALUE_TYPE: {
-        int size = valueArg + 1;
-        DexType value = indexedItems.getType((int) parseUnsigned(dexReader, size));
-        return new DexValue.DexValueType(value);
-      }
-      case DexValue.VALUE_FIELD: {
-        int size = valueArg + 1;
-        DexField value = indexedItems.getField((int) parseUnsigned(dexReader, size));
-        checkName(value.name);
-        return new DexValue.DexValueField(value);
-      }
-      case DexValue.VALUE_METHOD: {
-        int size = valueArg + 1;
-        DexMethod value = indexedItems.getMethod((int) parseUnsigned(dexReader, size));
-        checkName(value.name);
-        return new DexValue.DexValueMethod(value);
-      }
-      case DexValue.VALUE_ENUM: {
-        int size = valueArg + 1;
-        DexField value = indexedItems.getField((int) parseUnsigned(dexReader, size));
-        return new DexValue.DexValueEnum(value);
-      }
-      case DexValue.VALUE_ARRAY: {
-        assert valueArg == 0;
-        return new DexValue.DexValueArray(parseEncodedArrayValues());
-      }
-      case DexValue.VALUE_ANNOTATION: {
-        assert valueArg == 0;
-        return new DexValue.DexValueAnnotation(parseEncodedAnnotation());
-      }
-      case DexValue.VALUE_NULL: {
-        assert valueArg == 0;
-        return DexValueNull.NULL;
-      }
-      case DexValue.VALUE_BOOLEAN: {
-        // 0 is false, and 1 is true.
-        return DexValue.DexValueBoolean.create(valueArg != 0);
-      }
-      case DexValue.VALUE_METHOD_TYPE: {
-        int size = valueArg + 1;
-        DexProto value = indexedItems.getProto((int) parseUnsigned(dexReader, size));
-        return new DexValue.DexValueMethodType(value);
-      }
-      case DexValue.VALUE_METHOD_HANDLE: {
-        int size = valueArg + 1;
-        DexMethodHandle value = indexedItems.getMethodHandle((int) parseUnsigned(dexReader, size));
-        return new DexValue.DexValueMethodHandle(value);
+    switch (DexValueKind.fromId(valueType)) {
+      case BYTE:
+        {
+          assert valueArg == 0;
+          byte value = (byte) parseSigned(dexReader, 1);
+          return DexValue.DexValueByte.create(value);
+        }
+      case SHORT:
+        {
+          int size = valueArg + 1;
+          short value = (short) parseSigned(dexReader, size);
+          return DexValue.DexValueShort.create(value);
+        }
+      case CHAR:
+        {
+          int size = valueArg + 1;
+          char value = (char) parseUnsigned(dexReader, size);
+          return DexValue.DexValueChar.create(value);
+        }
+      case INT:
+        {
+          int size = valueArg + 1;
+          int value = (int) parseSigned(dexReader, size);
+          return DexValue.DexValueInt.create(value);
+        }
+      case LONG:
+        {
+          int size = valueArg + 1;
+          long value = parseSigned(dexReader, size);
+          return DexValue.DexValueLong.create(value);
+        }
+      case FLOAT:
+        {
+          int size = valueArg + 1;
+          return DexValue.DexValueFloat.create(parseFloat(dexReader, size));
+        }
+      case DOUBLE:
+        {
+          int size = valueArg + 1;
+          return DexValue.DexValueDouble.create(parseDouble(dexReader, size));
+        }
+      case STRING:
+        {
+          int size = valueArg + 1;
+          int index = (int) parseUnsigned(dexReader, size);
+          DexString value = indexedItems.getString(index);
+          return new DexValue.DexValueString(value);
+        }
+      case TYPE:
+        {
+          int size = valueArg + 1;
+          DexType value = indexedItems.getType((int) parseUnsigned(dexReader, size));
+          return new DexValue.DexValueType(value);
+        }
+      case FIELD:
+        {
+          int size = valueArg + 1;
+          DexField value = indexedItems.getField((int) parseUnsigned(dexReader, size));
+          checkName(value.name);
+          return new DexValue.DexValueField(value);
+        }
+      case METHOD:
+        {
+          int size = valueArg + 1;
+          DexMethod value = indexedItems.getMethod((int) parseUnsigned(dexReader, size));
+          checkName(value.name);
+          return new DexValue.DexValueMethod(value);
+        }
+      case ENUM:
+        {
+          int size = valueArg + 1;
+          DexField value = indexedItems.getField((int) parseUnsigned(dexReader, size));
+          return new DexValue.DexValueEnum(value);
+        }
+      case ARRAY:
+        {
+          assert valueArg == 0;
+          return new DexValue.DexValueArray(parseEncodedArrayValues());
+        }
+      case ANNOTATION:
+        {
+          assert valueArg == 0;
+          return new DexValue.DexValueAnnotation(parseEncodedAnnotation());
+        }
+      case NULL:
+        {
+          assert valueArg == 0;
+          return DexValueNull.NULL;
+        }
+      case BOOLEAN:
+        {
+          // 0 is false, and 1 is true.
+          return DexValue.DexValueBoolean.create(valueArg != 0);
+        }
+      case METHOD_TYPE:
+        {
+          int size = valueArg + 1;
+          DexProto value = indexedItems.getProto((int) parseUnsigned(dexReader, size));
+          return new DexValue.DexValueMethodType(value);
+        }
+      case METHOD_HANDLE:
+        {
+          int size = valueArg + 1;
+          DexMethodHandle value =
+              indexedItems.getMethodHandle((int) parseUnsigned(dexReader, size));
+          return new DexValue.DexValueMethodHandle(value);
       }
       default:
         throw new IndexOutOfBoundsException();
diff --git a/src/main/java/com/android/tools/r8/errors/CompilationError.java b/src/main/java/com/android/tools/r8/errors/CompilationError.java
index d165596..ccefc7b 100644
--- a/src/main/java/com/android/tools/r8/errors/CompilationError.java
+++ b/src/main/java/com/android/tools/r8/errors/CompilationError.java
@@ -40,6 +40,14 @@
     this.position = position;
   }
 
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  public Position getPosition() {
+    return position;
+  }
+
   public CompilationError withAdditionalOriginAndPositionInfo(Origin origin, Position position) {
     if (this.origin == Origin.unknown() || this.position == Position.UNKNOWN) {
       return new CompilationError(
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 a791cfc..640b7a1 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -98,7 +98,7 @@
     assert previous == null || previous == clazz;
   }
 
-  public Collection<DexProgramClass> getSynthesizedClassesForSanityCheck() {
+  public Collection<DexProgramClass> synthesizedClasses() {
     assert checkIfObsolete();
     return Collections.unmodifiableCollection(synthesizedClasses.values());
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 35f989e..55c4ccb 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -9,10 +9,13 @@
 
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Deque;
+import java.util.List;
 import java.util.Set;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
@@ -203,6 +206,65 @@
     return isSubtype(type, dexItemFactory().serializableType);
   }
 
+  public List<DexProgramClass> computeProgramClassRelationChain(
+      DexProgramClass subClass, DexProgramClass superClass) {
+    assert isSubtype(subClass.type, superClass.type);
+    assert !subClass.isInterface();
+    if (!superClass.isInterface()) {
+      return computeChainInClassHierarchy(subClass, superClass.type);
+    }
+    // If the super type is an interface we first compute the program chain upwards, and in a
+    // top-down order check if the interface is a super-type to the class. Computing it this way
+    // guarantees to find the instantiated program-classes of the longest chain.
+    List<DexProgramClass> relationChain =
+        computeChainInClassHierarchy(subClass, dexItemFactory().objectType);
+    WorkList<DexType> interfaceWorklist = WorkList.newIdentityWorkList();
+    for (int i = relationChain.size() - 1; i >= 0; i--) {
+      DexProgramClass clazz = relationChain.get(i);
+      if (isInterfaceInSuperTypes(clazz, superClass.type, interfaceWorklist)) {
+        return relationChain.subList(0, i + 1);
+      }
+    }
+    return Collections.emptyList();
+  }
+
+  private boolean isInterfaceInSuperTypes(
+      DexProgramClass clazz, DexType ifaceToFind, WorkList<DexType> workList) {
+    workList.addIfNotSeen(clazz.allImmediateSupertypes());
+    while (workList.hasNext()) {
+      DexType superType = workList.next();
+      if (superType == ifaceToFind) {
+        return true;
+      }
+      DexClass superClass = definitionFor(superType);
+      if (superClass != null) {
+        workList.addIfNotSeen(superClass.allImmediateSupertypes());
+      }
+    }
+    return false;
+  }
+
+  private List<DexProgramClass> computeChainInClassHierarchy(
+      DexProgramClass subClass, DexType superType) {
+    assert isSubtype(subClass.type, superType);
+    assert !subClass.isInterface();
+    assert superType == dexItemFactory().objectType
+        || definitionFor(superType) == null
+        || !definitionFor(superType).isInterface();
+    List<DexProgramClass> relationChain = new ArrayList<>();
+    DexClass current = subClass;
+    while (current != null) {
+      if (current.isProgramClass()) {
+        relationChain.add(current.asProgramClass());
+      }
+      if (current.type == superType) {
+        return relationChain;
+      }
+      current = definitionFor(current.superType);
+    }
+    return relationChain;
+  }
+
   /**
    * Helper method used for emulated interface resolution (not in JVM specifications). The result
    * may be abstract.
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 105868f..d1e70ed 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_GROUP_CLASS_NAME_PREFIX;
 
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.annotations.VisibleForTesting;
@@ -40,7 +41,7 @@
   public void forEachInstantiatedSubType(
       DexType type,
       Consumer<DexProgramClass> subTypeConsumer,
-      Consumer<DexCallSite> callSiteConsumer) {
+      Consumer<LambdaDescriptor> lambdaConsumer) {
     WorkList<DexType> workList = WorkList.newIdentityWorkList();
     workList.addIfNotSeen(type);
     while (workList.hasNext()) {
@@ -52,7 +53,7 @@
       workList.addIfNotSeen(allImmediateSubtypes(subType));
     }
     // TODO(b/148769279): Change this when we have information about callsites.
-    callSiteConsumer.accept(null);
+    //  This should effectively disappear once AppInfoWithLiveness implements support.
   }
 
   private static class TypeInfo {
@@ -356,7 +357,7 @@
     return true;
   }
 
-  protected boolean hasAnyInstantiatedLambdas(DexProgramClass clazz) {
+  public boolean hasAnyInstantiatedLambdas(DexProgramClass clazz) {
     assert checkIfObsolete();
     return true; // Don't know, there might be.
   }
@@ -480,9 +481,8 @@
   }
 
   public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(ClassTypeLatticeElement type) {
-    Set<DexType> interfaces = type.getInterfaces();
-    if (!interfaces.isEmpty()) {
-      for (DexType interfaceType : interfaces) {
+    if (type.getClassType() == dexItemFactory().objectType && !type.getInterfaces().isEmpty()) {
+      for (DexType interfaceType : type.getInterfaces()) {
         if (computeMayHaveFinalizeMethodDirectlyOrIndirectlyIfAbsent(interfaceType, false)) {
           return true;
         }
@@ -509,13 +509,13 @@
     if (clazz.isProgramClass()) {
       if (lookUpwards) {
         DexEncodedMethod resolutionResult =
-            resolveMethod(type, dexItemFactory().objectMethods.finalize).getSingleTarget();
+            resolveMethod(type, dexItemFactory().objectMembers.finalize).getSingleTarget();
         if (resolutionResult != null && resolutionResult.isProgramMethod(this)) {
           mayHaveFinalizeMethodDirectlyOrIndirectlyCache.put(type, true);
           return true;
         }
       } else {
-        if (clazz.lookupVirtualMethod(dexItemFactory().objectMethods.finalize) != null) {
+        if (clazz.lookupVirtualMethod(dexItemFactory().objectMembers.finalize) != null) {
           mayHaveFinalizeMethodDirectlyOrIndirectlyCache.put(type, true);
           return true;
         }
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 8e0dfc4..9ed88a6 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -19,12 +19,13 @@
 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.LibraryModeledPredicate;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableSet;
-import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
@@ -32,7 +33,7 @@
 import java.util.function.Function;
 import java.util.function.Predicate;
 
-public class AppView<T extends AppInfo> implements DexDefinitionSupplier {
+public class AppView<T extends AppInfo> implements DexDefinitionSupplier, LibraryModeledPredicate {
 
   private enum WholeProgramOptimizations {
     ON,
@@ -65,7 +66,7 @@
   private Set<DexMethod> unneededVisibilityBridgeMethods = ImmutableSet.of();
   private HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses;
   private VerticallyMergedClasses verticallyMergedClasses;
-  private Set<DexType> unboxedEnums = Collections.emptySet();
+  private EnumValueInfoMapCollection unboxedEnums = EnumValueInfoMapCollection.empty();
 
   private Map<DexClass, DexValueString> sourceDebugExtensions = new IdentityHashMap<>();
 
@@ -108,6 +109,11 @@
     }
   }
 
+  @Override
+  public boolean isModeled(DexType type) {
+    return libraryMethodOptimizer.isModeled(type);
+  }
+
   public static <T extends AppInfo> AppView<T> createForD8(T appInfo, InternalOptions options) {
     return new AppView<>(appInfo, WholeProgramOptimizations.OFF, options);
   }
@@ -268,8 +274,8 @@
     }
   }
 
-  public void withGeneratedMessageLiteBuilderShrinker(
-      Consumer<GeneratedMessageLiteBuilderShrinker> consumer) {
+  public <E extends Throwable> void withGeneratedMessageLiteBuilderShrinker(
+      ThrowingConsumer<GeneratedMessageLiteBuilderShrinker, E> consumer) throws E {
     if (protoShrinker != null && protoShrinker.generatedMessageLiteBuilderShrinker != null) {
       consumer.accept(protoShrinker.generatedMessageLiteBuilderShrinker);
     }
@@ -370,12 +376,16 @@
     this.verticallyMergedClasses = verticallyMergedClasses;
   }
 
-  public void setUnboxedEnums(Set<DexType> unboxedEnums) {
+  public EnumValueInfoMapCollection unboxedEnums() {
+    return unboxedEnums;
+  }
+
+  public void setUnboxedEnums(EnumValueInfoMapCollection unboxedEnums) {
     this.unboxedEnums = unboxedEnums;
   }
 
   public boolean validateUnboxedEnumsHaveBeenPruned() {
-    for (DexType unboxedEnum : unboxedEnums) {
+    for (DexType unboxedEnum : unboxedEnums.enumSet()) {
       assert definitionForProgramType(unboxedEnum) == null
           : "Enum " + unboxedEnum + " has been unboxed but is still in the program.";
       assert appInfo().withLiveness().wasPruned(unboxedEnum)
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 2a6154d..74735f4 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -89,7 +89,11 @@
           : FieldSignature.fromDexField(field.field);
       writeAnnotations(field.annotations(), ps);
       ps.print(field.accessFlags + " ");
-      ps.println(fieldSignature);
+      ps.print(fieldSignature);
+      if (field.isStatic() && field.hasExplicitStaticValue()) {
+        ps.print(" = " + field.getStaticValue());
+      }
+      ps.println();
     }
   }
 
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 c474718..a75fd4e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -686,6 +686,10 @@
     return null;
   }
 
+  public boolean isPublic() {
+    return accessFlags.isPublic();
+  }
+
   @Override
   public boolean isStatic() {
     return accessFlags.isStatic();
@@ -859,7 +863,7 @@
   }
 
   public boolean definesFinalizer(DexItemFactory factory) {
-    return lookupVirtualMethod(factory.objectMethods.finalize) != null;
+    return lookupVirtualMethod(factory.objectMembers.finalize) != null;
   }
 
   public boolean defaultValuesForStaticFieldsMayTriggerAllocation() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
index ded00bd..37f8853 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.errors.Unreachable;
 
-public class DexClassAndMethod {
+public class DexClassAndMethod implements LookupTarget {
 
   private final DexClass holder;
   private final DexEncodedMethod method;
@@ -35,6 +35,16 @@
     throw new Unreachable("Unsupported attempt at computing the hashcode of DexClassAndMethod");
   }
 
+  @Override
+  public boolean isMethodTarget() {
+    return true;
+  }
+
+  @Override
+  public DexClassAndMethod asMethodTarget() {
+    return this;
+  }
+
   public DexClass getHolder() {
     return holder;
   }
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 cba6dfc..e8bd9dc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -138,6 +138,10 @@
     return accessFlags.isPrivate();
   }
 
+  public boolean isPublic() {
+    return accessFlags.isPublic();
+  }
+
   @Override
   public boolean isStaticMember() {
     return isStatic();
@@ -158,6 +162,11 @@
     this.staticValue = staticValue;
   }
 
+  public void clearStaticValue() {
+    assert accessFlags.isStatic();
+    this.staticValue = null;
+  }
+
   public DexValue getStaticValue() {
     assert accessFlags.isStatic();
     return staticValue == null ? DexValue.defaultForType(field.type) : staticValue;
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 8cb71bb..58b768d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -406,7 +406,7 @@
       new StringBuildingMethods(stringBufferType);
   public final BooleanMembers booleanMembers = new BooleanMembers();
   public final ObjectsMethods objectsMethods = new ObjectsMethods();
-  public final ObjectMethods objectMethods = new ObjectMethods();
+  public final ObjectMembers objectMembers = new ObjectMembers();
   public final StringMethods stringMethods = new StringMethods();
   public final LongMethods longMethods = new LongMethods();
   public final DoubleMethods doubleMethods = new DoubleMethods();
@@ -595,8 +595,10 @@
   public Map<DexMethod, Predicate<InvokeMethod>> libraryMethodsWithoutSideEffects =
       Streams.<Pair<DexMethod, Predicate<InvokeMethod>>>concat(
               Stream.of(new Pair<>(enumMethods.constructor, alwaysTrue())),
-              Stream.of(new Pair<>(objectMethods.constructor, alwaysTrue())),
-              Stream.of(new Pair<>(objectMethods.getClass, alwaysTrue())),
+              Stream.of(new Pair<>(npeMethods.init, alwaysTrue())),
+              Stream.of(new Pair<>(npeMethods.initWithMessage, alwaysTrue())),
+              Stream.of(new Pair<>(objectMembers.constructor, alwaysTrue())),
+              Stream.of(new Pair<>(objectMembers.getClass, alwaysTrue())),
               mapToPredicate(classMethods.getNames, alwaysTrue()),
               mapToPredicate(
                   stringBufferMethods.constructorMethods,
@@ -625,12 +627,20 @@
 
   public Set<DexType> libraryTypesAssumedToBePresent =
       ImmutableSet.<DexType>builder()
-          .add(objectType, callableType, stringBufferType, stringBuilderType, stringType)
+          .add(
+              callableType,
+              enumType,
+              npeType,
+              objectType,
+              stringBufferType,
+              stringBuilderType,
+              stringType)
           .addAll(primitiveToBoxed.values())
           .build();
 
   public Set<DexType> libraryClassesWithoutStaticInitialization =
-      ImmutableSet.of(boxedBooleanType, enumType, objectType, stringBufferType, stringBuilderType);
+      ImmutableSet.of(
+          boxedBooleanType, enumType, npeType, objectType, stringBufferType, stringBuilderType);
 
   private boolean skipNameValidationForTesting = false;
 
@@ -735,16 +745,26 @@
     }
   }
 
-  public class ObjectMethods {
+  public class ObjectMembers {
 
+    /**
+     * This field is not on {@link Object}, but will be synthesized on program classes as a static
+     * field, for the compiler to have a principled way to trigger the initialization of a given
+     * class.
+     */
+    public final DexField clinitField = createField(objectType, intType, "$r8$clinit");
+
+    public final DexMethod clone;
     public final DexMethod getClass;
     public final DexMethod constructor;
     public final DexMethod finalize;
     public final DexMethod toString;
 
-    private ObjectMethods() {
-      getClass = createMethod(objectDescriptor, getClassMethodName, classDescriptor,
-          DexString.EMPTY_ARRAY);
+    private ObjectMembers() {
+      // The clone method is installed on each array, so one has to use method.match(clone).
+      clone = createMethod(objectType, createProto(objectType), cloneMethodName);
+      getClass = createMethod(objectDescriptor,
+          getClassMethodName, classDescriptor, DexString.EMPTY_ARRAY);
       constructor = createMethod(objectDescriptor,
           constructorMethodName, voidType.descriptor, DexString.EMPTY_ARRAY);
       finalize = createMethod(objectDescriptor,
@@ -941,16 +961,10 @@
 
   public class NullPointerExceptionMethods {
 
-    public final DexMethod init;
-
-    private NullPointerExceptionMethods() {
-      init =
-          createMethod(
-              npeDescriptor,
-              constructorMethodName,
-              voidDescriptor,
-              DexString.EMPTY_ARRAY);
-    }
+    public final DexMethod init =
+        createMethod(npeType, createProto(voidType), constructorMethodName);
+    public final DexMethod initWithMessage =
+        createMethod(npeType, createProto(voidType, stringType), constructorMethodName);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
index 8c622ae..4b0e1fb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
@@ -58,7 +58,7 @@
     // Set all static field values to unknown. We don't want to use the value from the library
     // at compile time, as it can be different at runtime.
     for (DexEncodedField staticField : staticFields) {
-      staticField.setStaticValue(DexValue.UNKNOWN);
+      staticField.clearStaticValue();
     }
     assert kind == Kind.CF : "Invalid kind " + kind + " for library-path class " + type;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexProto.java b/src/main/java/com/android/tools/r8/graph/DexProto.java
index 5c05605..43d6a29 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProto.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProto.java
@@ -20,6 +20,10 @@
     this.parameters = parameters;
   }
 
+  public DexType getParameter(int index) {
+    return parameters.values[index];
+  }
+
   @Override
   public int computeHashCode() {
     return shorty.hashCode()
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index f105f9c..794f7c8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -26,28 +26,89 @@
 
 public abstract class DexValue extends DexItem {
 
+  public enum DexValueKind {
+    BYTE(0x00),
+    SHORT(0x02),
+    CHAR(0x03),
+    INT(0x04),
+    LONG(0x06),
+    FLOAT(0x10),
+    DOUBLE(0x11),
+    METHOD_TYPE(0x15),
+    METHOD_HANDLE(0x16),
+    STRING(0x17),
+    TYPE(0x18),
+    FIELD(0x19),
+    METHOD(0x1a),
+    ENUM(0x1b),
+    ARRAY(0x1c),
+    ANNOTATION(0x1d),
+    NULL(0x1e),
+    BOOLEAN(0x1f);
+
+    public static DexValueKind fromId(int id) {
+      switch (id) {
+        case 0x00:
+          return BYTE;
+        case 0x02:
+          return SHORT;
+        case 0x03:
+          return CHAR;
+        case 0x04:
+          return INT;
+        case 0x06:
+          return LONG;
+        case 0x10:
+          return FLOAT;
+        case 0x11:
+          return DOUBLE;
+        case 0x15:
+          return METHOD_TYPE;
+        case 0x16:
+          return METHOD_HANDLE;
+        case 0x17:
+          return STRING;
+        case 0x18:
+          return TYPE;
+        case 0x19:
+          return FIELD;
+        case 0x1a:
+          return METHOD;
+        case 0x1b:
+          return ENUM;
+        case 0x1c:
+          return ARRAY;
+        case 0x1d:
+          return ANNOTATION;
+        case 0x1e:
+          return NULL;
+        case 0x1f:
+          return BOOLEAN;
+        default:
+          throw new Unreachable();
+      }
+    }
+
+    private final byte b;
+
+    DexValueKind(int b) {
+      this.b = (byte) b;
+    }
+
+    byte toByte() {
+      return b;
+    }
+  }
+
   public static final DexValue[] EMPTY_ARRAY = {};
 
-  public static final UnknownDexValue UNKNOWN = UnknownDexValue.UNKNOWN;
+  public boolean isDexItemBasedValueString() {
+    return false;
+  }
 
-  public static final byte VALUE_BYTE = 0x00;
-  public static final byte VALUE_SHORT = 0x02;
-  public static final byte VALUE_CHAR = 0x03;
-  public static final byte VALUE_INT = 0x04;
-  public static final byte VALUE_LONG = 0x06;
-  public static final byte VALUE_FLOAT = 0x10;
-  public static final byte VALUE_DOUBLE = 0x11;
-  public static final byte VALUE_METHOD_TYPE = 0x15;
-  public static final byte VALUE_METHOD_HANDLE = 0x16;
-  public static final byte VALUE_STRING = 0x17;
-  public static final byte VALUE_TYPE = 0x18;
-  public static final byte VALUE_FIELD = 0x19;
-  public static final byte VALUE_METHOD = 0x1a;
-  public static final byte VALUE_ENUM = 0x1b;
-  public static final byte VALUE_ARRAY = 0x1c;
-  public static final byte VALUE_ANNOTATION = 0x1d;
-  public static final byte VALUE_NULL = 0x1e;
-  public static final byte VALUE_BOOLEAN = 0x1f;
+  public DexItemBasedValueString asDexItemBasedValueString() {
+    return null;
+  }
 
   public DexValueMethodHandle asDexValueMethodHandle() {
     return null;
@@ -57,30 +118,54 @@
     return null;
   }
 
-  public DexValueType asDexValueType() {
-    return null;
+  public boolean isDexValueArray() {
+    return false;
   }
 
   public DexValueArray asDexValueArray() {
     return null;
   }
 
-  public DexValueString asDexValueString() {
+  public boolean isDexValueBoolean() {
+    return false;
+  }
+
+  public DexValueBoolean asDexValueBoolean() {
     return null;
   }
 
-  public boolean isDexValueString() {
+  public boolean isDexValueByte() {
     return false;
   }
 
-  public boolean isDexValueType() {
+  public DexValueByte asDexValueByte() {
+    return null;
+  }
+
+  public boolean isDexValueDouble() {
     return false;
   }
 
-  public boolean isDexValueArray() {
+  public DexValueDouble asDexValueDouble() {
+    return null;
+  }
+
+  public boolean isDexValueChar() {
     return false;
   }
 
+  public DexValueChar asDexValueChar() {
+    return null;
+  }
+
+  public boolean isDexValueFloat() {
+    return false;
+  }
+
+  public DexValueFloat asDexValueFloat() {
+    return null;
+  }
+
   public boolean isDexValueInt() {
     return false;
   }
@@ -89,7 +174,55 @@
     return null;
   }
 
-  public static DexValue fromAsmBootstrapArgument(
+  public boolean isDexValueLong() {
+    return false;
+  }
+
+  public DexValueLong asDexValueLong() {
+    return null;
+  }
+
+  public boolean isDexValueNull() {
+    return false;
+  }
+
+  public DexValueNull asDexValueNull() {
+    return null;
+  }
+
+  public boolean isDexValueNumber() {
+    return false;
+  }
+
+  public DexValueNumber asDexValueNumber() {
+    return null;
+  }
+
+  public boolean isDexValueShort() {
+    return false;
+  }
+
+  public DexValueShort asDexValueShort() {
+    return null;
+  }
+
+  public boolean isDexValueString() {
+    return false;
+  }
+
+  public DexValueString asDexValueString() {
+    return null;
+  }
+
+  public boolean isDexValueType() {
+    return false;
+  }
+
+  public DexValueType asDexValueType() {
+    return null;
+  }
+
+  static DexValue fromAsmBootstrapArgument(
       Object value, JarApplicationReader application, DexType clazz) {
     if (value instanceof Integer) {
       return DexValue.DexValueInt.create((Integer) value);
@@ -123,8 +256,8 @@
     }
   }
 
-  private static void writeHeader(byte type, int arg, DexOutputBuffer dest) {
-    dest.putByte((byte) ((arg << 5) | type));
+  private static void writeHeader(DexValueKind kind, int arg, DexOutputBuffer dest) {
+    dest.putByte((byte) ((arg << 5) | kind.toByte()));
   }
 
   @Override
@@ -195,72 +328,11 @@
 
   public abstract Object asAsmEncodedObject();
 
-  static public class UnknownDexValue extends DexValue {
-
-    // Singleton instance.
-    public static final UnknownDexValue UNKNOWN = new UnknownDexValue();
-
-    private UnknownDexValue() {
-    }
+  private abstract static class SimpleDexValue extends DexValue {
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection indexedItems,
-        DexMethod method, int instructionOffset) {
-      throw new Unreachable();
-    }
-
-    @Override
-    public void sort() {
-      throw new Unreachable();
-    }
-
-    @Override
-    public boolean mayHaveSideEffects() {
-      return true;
-    }
-
-    @Override
-    public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
-      throw new Unreachable();
-    }
-
-    @Override
-    public Object getBoxedValue() {
-      throw new Unreachable();
-    }
-
-    @Override
-    public Object asAsmEncodedObject() {
-      throw new Unreachable();
-    }
-
-    @Override
-    public int hashCode() {
-      return System.identityHashCode(this);
-    }
-
-    @Override
-    public boolean equals(Object other) {
-      return other == this;
-    }
-
-    @Override
-    public String toString() {
-      return "UNKNOWN";
-    }
-
-    @Override
-    public ConstInstruction asConstInstruction(
-        AppView<? extends AppInfoWithSubtyping> appView, IRCode code, DebugLocalInfo local) {
-      return null;
-    }
-  }
-
-  static private abstract class SimpleDexValue extends DexValue {
-
-    @Override
-    public void collectIndexedItems(IndexedItemCollection indexedItems,
-        DexMethod method, int instructionOffset) {
+    public void collectIndexedItems(
+        IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
       // Intentionally left empty
     }
 
@@ -274,18 +346,32 @@
       return false;
     }
 
-    protected static void writeIntegerTo(byte type, long value, int expected,
-        DexOutputBuffer dest) {
+    static void writeIntegerTo(DexValueKind kind, long value, int expected, DexOutputBuffer dest) {
       // Leave space for header.
       dest.forward(1);
       int length = dest.putSignedEncodedValue(value, expected);
       dest.rewind(length + 1);
-      writeHeader(type, length - 1, dest);
+      writeHeader(kind, length - 1, dest);
       dest.forward(length);
     }
   }
 
-  static public class DexValueByte extends SimpleDexValue {
+  public abstract static class DexValueNumber extends SimpleDexValue {
+
+    public abstract long getRawValue();
+
+    @Override
+    public boolean isDexValueNumber() {
+      return true;
+    }
+
+    @Override
+    public DexValueNumber asDexValueNumber() {
+      return this;
+    }
+  }
+
+  public static class DexValueByte extends DexValueNumber {
 
     public static final DexValueByte DEFAULT = new DexValueByte((byte) 0);
 
@@ -304,13 +390,28 @@
     }
 
     @Override
+    public long getRawValue() {
+      return value;
+    }
+
+    @Override
+    public boolean isDexValueByte() {
+      return true;
+    }
+
+    @Override
+    public DexValueByte asDexValueByte() {
+      return this;
+    }
+
+    @Override
     public Object getBoxedValue() {
       return getValue();
     }
 
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
-      writeHeader(VALUE_BYTE, 0, dest);
+      writeHeader(DexValueKind.BYTE, 0, dest);
       dest.putSignedEncodedValue(value, 1);
     }
 
@@ -344,7 +445,7 @@
     }
   }
 
-  static public class DexValueShort extends SimpleDexValue {
+  public static class DexValueShort extends DexValueNumber {
 
     public static final DexValueShort DEFAULT = new DexValueShort((short) 0);
     final short value;
@@ -362,13 +463,28 @@
     }
 
     @Override
+    public long getRawValue() {
+      return value;
+    }
+
+    @Override
+    public boolean isDexValueShort() {
+      return true;
+    }
+
+    @Override
+    public DexValueShort asDexValueShort() {
+      return this;
+    }
+
+    @Override
     public Object getBoxedValue() {
       return getValue();
     }
 
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
-      writeIntegerTo(VALUE_SHORT, value, Short.BYTES, dest);
+      writeIntegerTo(DexValueKind.SHORT, value, Short.BYTES, dest);
     }
 
     @Override
@@ -401,7 +517,7 @@
     }
   }
 
-  static public class DexValueChar extends SimpleDexValue {
+  public static class DexValueChar extends DexValueNumber {
 
     public static final DexValueChar DEFAULT = new DexValueChar((char) 0);
     final char value;
@@ -419,6 +535,21 @@
     }
 
     @Override
+    public long getRawValue() {
+      return value;
+    }
+
+    @Override
+    public boolean isDexValueChar() {
+      return true;
+    }
+
+    @Override
+    public DexValueChar asDexValueChar() {
+      return this;
+    }
+
+    @Override
     public Object getBoxedValue() {
       return getValue();
     }
@@ -428,7 +559,7 @@
       dest.forward(1);
       int length = dest.putUnsignedEncodedValue(value, 2);
       dest.rewind(length + 1);
-      writeHeader(VALUE_CHAR, length - 1, dest);
+      writeHeader(DexValueKind.CHAR, length - 1, dest);
       dest.forward(length);
     }
 
@@ -462,7 +593,7 @@
     }
   }
 
-  static public class DexValueInt extends SimpleDexValue {
+  public static class DexValueInt extends DexValueNumber {
 
     public static final DexValueInt DEFAULT = new DexValueInt(0);
     public final int value;
@@ -480,13 +611,18 @@
     }
 
     @Override
+    public long getRawValue() {
+      return value;
+    }
+
+    @Override
     public Object getBoxedValue() {
       return getValue();
     }
 
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
-      writeIntegerTo(VALUE_INT, value, Integer.BYTES, dest);
+      writeIntegerTo(DexValueKind.INT, value, Integer.BYTES, dest);
     }
 
     @Override
@@ -529,7 +665,7 @@
     }
   }
 
-  static public class DexValueLong extends SimpleDexValue {
+  public static class DexValueLong extends DexValueNumber {
 
     public static final DexValueLong DEFAULT = new DexValueLong(0);
     final long value;
@@ -547,13 +683,28 @@
     }
 
     @Override
+    public long getRawValue() {
+      return value;
+    }
+
+    @Override
+    public boolean isDexValueLong() {
+      return true;
+    }
+
+    @Override
+    public DexValueLong asDexValueLong() {
+      return this;
+    }
+
+    @Override
     public Object getBoxedValue() {
       return getValue();
     }
 
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
-      writeIntegerTo(VALUE_LONG, value, Long.BYTES, dest);
+      writeIntegerTo(DexValueKind.LONG, value, Long.BYTES, dest);
     }
 
     @Override
@@ -586,7 +737,7 @@
     }
   }
 
-  static public class DexValueFloat extends SimpleDexValue {
+  public static class DexValueFloat extends DexValueNumber {
 
     public static final DexValueFloat DEFAULT = new DexValueFloat(0);
     final float value;
@@ -604,6 +755,21 @@
     }
 
     @Override
+    public long getRawValue() {
+      return Float.floatToIntBits(value);
+    }
+
+    @Override
+    public boolean isDexValueFloat() {
+      return true;
+    }
+
+    @Override
+    public DexValueFloat asDexValueFloat() {
+      return this;
+    }
+
+    @Override
     public Object getBoxedValue() {
       return getValue();
     }
@@ -613,7 +779,7 @@
       dest.forward(1);
       int length = EncodedValueUtils.putFloat(dest, value);
       dest.rewind(length + 1);
-      writeHeader(VALUE_FLOAT, length - 1, dest);
+      writeHeader(DexValueKind.FLOAT, length - 1, dest);
       dest.forward(length);
     }
 
@@ -646,10 +812,9 @@
     public String toString() {
       return "Float " + value;
     }
-
   }
 
-  static public class DexValueDouble extends SimpleDexValue {
+  public static class DexValueDouble extends DexValueNumber {
 
     public static final DexValueDouble DEFAULT = new DexValueDouble(0);
 
@@ -668,6 +833,21 @@
     }
 
     @Override
+    public long getRawValue() {
+      return Double.doubleToRawLongBits(value);
+    }
+
+    @Override
+    public boolean isDexValueDouble() {
+      return true;
+    }
+
+    @Override
+    public DexValueDouble asDexValueDouble() {
+      return this;
+    }
+
+    @Override
     public Object getBoxedValue() {
       return getValue();
     }
@@ -677,7 +857,7 @@
       dest.forward(1);
       int length = EncodedValueUtils.putDouble(dest, value);
       dest.rewind(length + 1);
-      writeHeader(VALUE_DOUBLE, length - 1, dest);
+      writeHeader(DexValueKind.DOUBLE, length - 1, dest);
       dest.forward(length);
     }
 
@@ -720,7 +900,7 @@
       this.value = value;
     }
 
-    protected abstract byte getValueKind();
+    protected abstract DexValueKind getValueKind();
 
     public T getValue() {
       return value;
@@ -759,7 +939,7 @@
 
     @Override
     public int hashCode() {
-      return value.hashCode() * 7 + getValueKind();
+      return value.hashCode() * 7 + getValueKind().toByte();
     }
 
     @Override
@@ -802,8 +982,8 @@
     }
 
     @Override
-    protected byte getValueKind() {
-      return VALUE_STRING;
+    protected DexValueKind getValueKind() {
+      return DexValueKind.STRING;
     }
 
     @Override
@@ -840,13 +1020,23 @@
     }
 
     @Override
+    public boolean isDexItemBasedValueString() {
+      return true;
+    }
+
+    @Override
+    public DexItemBasedValueString asDexItemBasedValueString() {
+      return this;
+    }
+
+    @Override
     public Object asAsmEncodedObject() {
       return value.toString();
     }
 
     @Override
-    protected byte getValueKind() {
-      return VALUE_STRING;
+    protected DexValueKind getValueKind() {
+      return DexValueKind.STRING;
     }
 
     @Override
@@ -879,13 +1069,13 @@
     }
 
     @Override
-    protected byte getValueKind() {
-      return VALUE_TYPE;
+    protected DexValueKind getValueKind() {
+      return DexValueKind.TYPE;
     }
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection indexedItems,
-        DexMethod method, int instructionOffset) {
+    public void collectIndexedItems(
+        IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
       value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
 
@@ -907,13 +1097,13 @@
     }
 
     @Override
-    protected byte getValueKind() {
-      return VALUE_FIELD;
+    protected DexValueKind getValueKind() {
+      return DexValueKind.FIELD;
     }
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection indexedItems,
-        DexMethod method, int instructionOffset) {
+    public void collectIndexedItems(
+        IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
       value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
   }
@@ -925,13 +1115,13 @@
     }
 
     @Override
-    protected byte getValueKind() {
-      return VALUE_METHOD;
+    protected DexValueKind getValueKind() {
+      return DexValueKind.METHOD;
     }
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection indexedItems,
-        DexMethod method, int instructionOffset) {
+    public void collectIndexedItems(
+        IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
       value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
   }
@@ -943,13 +1133,13 @@
     }
 
     @Override
-    protected byte getValueKind() {
-      return VALUE_ENUM;
+    protected DexValueKind getValueKind() {
+      return DexValueKind.ENUM;
     }
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection indexedItems,
-        DexMethod method, int instructionOffset) {
+    public void collectIndexedItems(
+        IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
       value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
   }
@@ -966,13 +1156,13 @@
     }
 
     @Override
-    protected byte getValueKind() {
-      return VALUE_METHOD_TYPE;
+    protected DexValueKind getValueKind() {
+      return DexValueKind.METHOD_TYPE;
     }
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection indexedItems,
-        DexMethod method, int instructionOffset) {
+    public void collectIndexedItems(
+        IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
       value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
   }
@@ -997,7 +1187,7 @@
 
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
-      writeHeader(VALUE_ARRAY, 0, dest);
+      writeHeader(DexValueKind.ARRAY, 0, dest);
       dest.putUleb128(values.length);
       for (DexValue value : values) {
         value.writeTo(dest, mapping);
@@ -1070,7 +1260,7 @@
 
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
-      writeHeader(VALUE_ANNOTATION, 0, dest);
+      writeHeader(DexValueKind.ANNOTATION, 0, dest);
       FileWriter.writeEncodedAnnotation(value, dest, mapping);
     }
 
@@ -1112,7 +1302,7 @@
     }
   }
 
-  static public class DexValueNull extends SimpleDexValue {
+  public static class DexValueNull extends DexValueNumber {
 
     public static final DexValue NULL = new DexValueNull();
 
@@ -1125,8 +1315,23 @@
     }
 
     @Override
+    public long getRawValue() {
+      return 0;
+    }
+
+    @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
-      writeHeader(VALUE_NULL, 0, dest);
+      writeHeader(DexValueKind.NULL, 0, dest);
+    }
+
+    @Override
+    public boolean isDexValueNull() {
+      return true;
+    }
+
+    @Override
+    public DexValueNull asDexValueNull() {
+      return this;
     }
 
     @Override
@@ -1164,7 +1369,7 @@
     }
   }
 
-  static public class DexValueBoolean extends SimpleDexValue {
+  public static class DexValueBoolean extends DexValueNumber {
 
     private static final DexValueBoolean TRUE = new DexValueBoolean(true);
     private static final DexValueBoolean FALSE = new DexValueBoolean(false);
@@ -1186,13 +1391,28 @@
     }
 
     @Override
+    public long getRawValue() {
+      return BooleanUtils.longValue(value);
+    }
+
+    @Override
+    public boolean isDexValueBoolean() {
+      return true;
+    }
+
+    @Override
+    public DexValueBoolean asDexValueBoolean() {
+      return this;
+    }
+
+    @Override
     public Object getBoxedValue() {
       return getValue();
     }
 
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
-      writeHeader(VALUE_BOOLEAN, value ? 1 : 0, dest);
+      writeHeader(DexValueKind.BOOLEAN, value ? 1 : 0, dest);
     }
 
     @Override
@@ -1237,13 +1457,13 @@
     }
 
     @Override
-    protected byte getValueKind() {
-      return VALUE_METHOD_HANDLE;
+    protected DexValueKind getValueKind() {
+      return DexValueKind.METHOD_HANDLE;
     }
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection indexedItems,
-        DexMethod method, int instructionOffset) {
+    public void collectIndexedItems(
+        IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
       value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java b/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java
index 470685f..4fd96f7 100644
--- a/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java
@@ -86,6 +86,10 @@
       return map.size();
     }
 
+    public boolean hasEnumValueInfo(DexField field) {
+      return map.containsKey(field);
+    }
+
     public EnumValueInfo getEnumValueInfo(DexField field) {
       return map.get(field);
     }
@@ -110,6 +114,10 @@
       this.ordinal = ordinal;
     }
 
+    public int convertToInt() {
+      return ordinal + 1;
+    }
+
     EnumValueInfo rewrittenWithLens(GraphLense lens) {
       DexType newType = lens.lookupType(type);
       if (type == newType) {
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
index 2bd8acf..38d3a8e 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
@@ -32,12 +32,20 @@
 
   boolean hasReflectiveAccess();
 
+  default boolean isAccessedFromMethodHandle() {
+    return isReadFromMethodHandle() || isWrittenFromMethodHandle();
+  }
+
   boolean isRead();
 
+  boolean isReadFromMethodHandle();
+
   boolean isReadOnlyIn(DexEncodedMethod method);
 
   boolean isWritten();
 
+  boolean isWrittenFromMethodHandle();
+
   boolean isWrittenInMethodSatisfying(Predicate<DexEncodedMethod> predicate);
 
   boolean isWrittenOnlyInMethodSatisfying(Predicate<DexEncodedMethod> predicate);
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
index ddab792..9d19406 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
@@ -12,6 +12,8 @@
 
   void flattenAccessContexts();
 
+  boolean contains(DexField field);
+
   T get(DexField field);
 
   void forEach(Consumer<T> consumer);
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
index 7ef10ac..5634d5b 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
@@ -21,6 +21,11 @@
   }
 
   @Override
+  public boolean contains(DexField field) {
+    return infos.containsKey(field);
+  }
+
+  @Override
   public FieldAccessInfoImpl get(DexField field) {
     return infos.get(field);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index 4715169..b15153b 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -22,11 +22,15 @@
 
   public static final FieldAccessInfoImpl MISSING_FIELD_ACCESS_INFO = new FieldAccessInfoImpl(null);
 
+  public static int FLAG_IS_READ_FROM_METHOD_HANDLE = 1 << 0;
+  public static int FLAG_IS_WRITTEN_FROM_METHOD_HANDLE = 1 << 1;
+  public static int FLAG_HAS_REFLECTIVE_ACCESS = 1 << 2;
+
   // A direct reference to the definition of the field.
   private DexField field;
 
-  // If this field has a reflective access.
-  private boolean hasReflectiveAccess;
+  // If this field is accessed from a method handle or has a reflective access.
+  private int flags;
 
   // Maps every direct and indirect reference in a read-context to the set of methods in which that
   // reference appears.
@@ -185,11 +189,11 @@
 
   @Override
   public boolean hasReflectiveAccess() {
-    return hasReflectiveAccess;
+    return (flags & FLAG_HAS_REFLECTIVE_ACCESS) != 0;
   }
 
   public void setHasReflectiveAccess() {
-    hasReflectiveAccess = true;
+    flags |= FLAG_HAS_REFLECTIVE_ACCESS;
   }
 
   /** Returns true if this field is read by the program. */
@@ -199,6 +203,15 @@
   }
 
   @Override
+  public boolean isReadFromMethodHandle() {
+    return (flags & FLAG_IS_READ_FROM_METHOD_HANDLE) != 0;
+  }
+
+  public void setReadFromMethodHandle() {
+    flags |= FLAG_IS_READ_FROM_METHOD_HANDLE;
+  }
+
+  @Override
   public boolean isReadOnlyIn(DexEncodedMethod method) {
     assert isRead();
     assert method != null;
@@ -212,6 +225,15 @@
     return writesWithContexts != null && !writesWithContexts.isEmpty();
   }
 
+  @Override
+  public boolean isWrittenFromMethodHandle() {
+    return (flags & FLAG_IS_WRITTEN_FROM_METHOD_HANDLE) != 0;
+  }
+
+  public void setWrittenFromMethodHandle() {
+    flags |= FLAG_IS_WRITTEN_FROM_METHOD_HANDLE;
+  }
+
   /**
    * Returns true if this field is written by a method for which {@param predicate} returns true.
    */
@@ -292,9 +314,7 @@
 
   public FieldAccessInfoImpl rewrittenWithLens(DexDefinitionSupplier definitions, GraphLense lens) {
     FieldAccessInfoImpl rewritten = new FieldAccessInfoImpl(lens.lookupField(field));
-    if (hasReflectiveAccess) {
-      rewritten.setHasReflectiveAccess();
-    }
+    rewritten.flags = flags;
     if (readsWithContexts != null) {
       rewritten.readsWithContexts = new IdentityHashMap<>();
       readsWithContexts.forEach(
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignature.java b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
index f948208..049cb7a 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignature.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -133,6 +133,8 @@
     ClassSignature(
         ClassTypeSignature superClassSignature,
         List<ClassTypeSignature> superInterfaceSignatures) {
+      assert superClassSignature != null;
+      assert superInterfaceSignatures != null;
       this.superClassSignature = superClassSignature;
       this.superInterfaceSignatures = superInterfaceSignatures;
     }
@@ -173,9 +175,13 @@
       return null;
     }
 
-    public abstract TypeSignature toArrayTypeSignature(AppView<?> appView);
+    public TypeSignature toArrayTypeSignature(AppView<?> appView) {
+      return null;
+    }
 
-    public abstract TypeSignature toArrayElementTypeSignature(AppView<?> appView);
+    public TypeSignature toArrayElementTypeSignature(AppView<?> appView) {
+      return null;
+    }
   }
 
   // TODO(b/129925954): better structures for a circle of
@@ -200,6 +206,14 @@
       return null;
     }
 
+    public boolean isArrayTypeSignature() {
+      return false;
+    }
+
+    public ArrayTypeSignature asArrayTypeSignature() {
+      return null;
+    }
+
     public boolean isTypeVariableSignature() {
       return false;
     }
@@ -209,12 +223,10 @@
     }
   }
 
-  // TODO(b/129925954): separate ArrayTypeSignature or just reuse ClassTypeSignature?
   public static class ClassTypeSignature extends FieldTypeSignature {
     static final ClassTypeSignature UNKNOWN_CLASS_TYPE_SIGNATURE =
-        new ClassTypeSignature(null, ImmutableList.of());
+        new ClassTypeSignature(DexItemFactory.nullValueType, ImmutableList.of());
 
-    // This covers class type or array type, with or without type arguments.
     final DexType type;
     // E.g., for Map<K, V>, a signature will indicate what types are for K and V.
     // Note that this could be nested, e.g., Map<K, Consumer<V>>.
@@ -227,6 +239,8 @@
     ClassTypeSignature innerTypeSignature;
 
     ClassTypeSignature(DexType type, List<FieldTypeSignature> typeArguments) {
+      assert type != null;
+      assert typeArguments != null;
       this.type = type;
       this.typeArguments = typeArguments;
     }
@@ -250,25 +264,8 @@
     }
 
     @Override
-    public ClassTypeSignature toArrayTypeSignature(AppView<?> appView) {
-      DexType arrayType = type.toArrayType(1, appView.dexItemFactory());
-      ClassTypeSignature result = new ClassTypeSignature(arrayType, typeArguments);
-      copyEnclosingRelations(result);
-      return result;
-    }
-
-    @Override
-    public ClassTypeSignature toArrayElementTypeSignature(AppView<?> appView) {
-      assert type.isArrayType();
-      DexType elementType = type.toArrayElementType( appView.dexItemFactory());
-      ClassTypeSignature result = new ClassTypeSignature(elementType, typeArguments);
-      copyEnclosingRelations(result);
-      return result;
-    }
-
-    private void copyEnclosingRelations(ClassTypeSignature cloned) {
-      cloned.enclosingTypeSignature = this.enclosingTypeSignature;
-      cloned.innerTypeSignature = this.innerTypeSignature;
+    public ArrayTypeSignature toArrayTypeSignature(AppView<?> appView) {
+      return new ArrayTypeSignature(this);
     }
 
     static void link(ClassTypeSignature outer, ClassTypeSignature inner) {
@@ -276,12 +273,66 @@
       outer.innerTypeSignature = inner;
       inner.enclosingTypeSignature = outer;
     }
+
+    // TODO(b/129925954): rewrite GenericSignatureRewriter with this pattern?
+    public interface Converter<R> {
+      R init();
+      R visitType(DexType type, R result);
+      R visitTypeArgument(FieldTypeSignature typeArgument, R result);
+      R visitInnerTypeSignature(ClassTypeSignature innerTypeSignature, R result);
+    }
+
+    public <R> R convert(Converter<R> converter) {
+      R result = converter.init();
+      result = converter.visitType(type, result);
+      for (FieldTypeSignature typeArgument : typeArguments) {
+        result = converter.visitTypeArgument(typeArgument, result);
+      }
+      if (innerTypeSignature != null) {
+        result = converter.visitInnerTypeSignature(innerTypeSignature, result);
+      }
+      return result;
+    }
+  }
+
+  public static class ArrayTypeSignature extends FieldTypeSignature {
+    final TypeSignature elementSignature;
+
+    ArrayTypeSignature(TypeSignature elementSignature) {
+      assert elementSignature != null;
+      this.elementSignature = elementSignature;
+    }
+
+    public TypeSignature elementSignature() {
+      return elementSignature;
+    }
+
+    @Override
+    public boolean isArrayTypeSignature() {
+      return true;
+    }
+
+    @Override
+    public ArrayTypeSignature asArrayTypeSignature() {
+      return this;
+    }
+
+    @Override
+    public TypeSignature toArrayTypeSignature(AppView<?> appView) {
+      return new ArrayTypeSignature(this);
+    }
+
+    @Override
+    public TypeSignature toArrayElementTypeSignature(AppView<?> appView) {
+      return elementSignature;
+    }
   }
 
   public static class TypeVariableSignature extends FieldTypeSignature {
     final String typeVariable;
 
     TypeVariableSignature(String typeVariable) {
+      assert typeVariable != null;
       this.typeVariable = typeVariable;
     }
 
@@ -296,13 +347,8 @@
     }
 
     @Override
-    public TypeSignature toArrayTypeSignature(AppView<?> appView) {
-      throw new Unimplemented("TypeVariableSignature::toArrayTypeSignature");
-    }
-
-    @Override
-    public TypeSignature toArrayElementTypeSignature(AppView<?> appView) {
-      throw new Unimplemented("TypeVariableSignature::toArrayElementTypeSignature");
+    public ArrayTypeSignature toArrayTypeSignature(AppView<?> appView) {
+      return new ArrayTypeSignature(this);
     }
   }
 
@@ -311,9 +357,8 @@
     final DexType type;
 
     BaseTypeSignature(DexType type) {
-      assert type.isPrimitiveType() || type.isPrimitiveArrayType()
-              || type.isVoidType()
-          : type.toDescriptorString();
+      assert type != null;
+      assert type.isPrimitiveType() : type.toDescriptorString();
       this.type = type;
     }
 
@@ -328,36 +373,47 @@
     }
 
     @Override
-    public BaseTypeSignature toArrayTypeSignature(AppView<?> appView) {
+    public ArrayTypeSignature toArrayTypeSignature(AppView<?> appView) {
       assert !type.isVoidType();
-      DexType arrayType = type.toArrayType(1, appView.dexItemFactory());
-      return new BaseTypeSignature(arrayType);
+      return new ArrayTypeSignature(this);
+    }
+  }
+
+  public static class ReturnType {
+    static final ReturnType VOID = new ReturnType(null);
+
+    // `null` indicates that it's `void`.
+    final TypeSignature typeSignature;
+
+    ReturnType(TypeSignature typeSignature) {
+      this.typeSignature = typeSignature;
     }
 
-    @Override
-    public BaseTypeSignature toArrayElementTypeSignature(AppView<?> appView) {
-      assert type.isPrimitiveArrayType();
-      DexType elementType = type.toArrayElementType(appView.dexItemFactory());
-      return new BaseTypeSignature(elementType);
+    public boolean isVoidDescriptor() {
+      return typeSignature == null;
+    }
+
+    public TypeSignature typeSignature() {
+      return typeSignature;
     }
   }
 
   public static class MethodTypeSignature implements DexDefinitionSignature<DexEncodedMethod> {
     static final MethodTypeSignature UNKNOWN_METHOD_TYPE_SIGNATURE =
-        new MethodTypeSignature(
-            ImmutableList.of(),
-            ClassTypeSignature.UNKNOWN_CLASS_TYPE_SIGNATURE,
-            ImmutableList.of());
+        new MethodTypeSignature(ImmutableList.of(), ReturnType.VOID, ImmutableList.of());
 
     // TODO(b/129925954): encoding formal type parameters
     final List<TypeSignature> typeSignatures;
-    final TypeSignature returnType;
+    final ReturnType returnType;
     final List<TypeSignature> throwsSignatures;
 
     MethodTypeSignature(
         List<TypeSignature> typeSignatures,
-        TypeSignature returnType,
+        ReturnType returnType,
         List<TypeSignature> throwsSignatures) {
+      assert typeSignatures != null;
+      assert returnType != null;
+      assert throwsSignatures != null;
       this.typeSignatures = typeSignatures;
       this.returnType = returnType;
       this.throwsSignatures = throwsSignatures;
@@ -370,7 +426,7 @@
       return typeSignatures.get(i);
     }
 
-    public TypeSignature returnType() {
+    public ReturnType returnType() {
       return returnType;
     }
 
@@ -817,7 +873,7 @@
 
       expect(')');
 
-      TypeSignature returnType = updateReturnType();
+      ReturnType returnType = updateReturnType();
 
       ImmutableList.Builder<TypeSignature> throwsSignatureBuilder = ImmutableList.builder();
       if (symbol == '^') {
@@ -837,13 +893,13 @@
           parameterSignatureBuilder.build(), returnType, throwsSignatureBuilder.build());
     }
 
-    private TypeSignature updateReturnType() {
+    private ReturnType updateReturnType() {
       // ReturnType ::= TypeSignature | "V".
       if (symbol != 'V') {
-        return updateTypeSignature(ParserPosition.MEMBER_ANNOTATION);
+        return new ReturnType(updateTypeSignature(ParserPosition.MEMBER_ANNOTATION));
       } else {
         scanSymbol();
-        return new BaseTypeSignature(appView.dexItemFactory().voidType);
+        return ReturnType.VOID;
       }
     }
 
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 8588a9d..7d2d844 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -350,6 +350,12 @@
         continue;
       }
       for (DexEncodedField field : clazz.fields()) {
+        // The field $r8$clinitField may be synthesized by R8 in order to trigger the initialization
+        // of the enclosing class. It is not present in the input, and therefore we do not require
+        // that it can be mapped back to the original program.
+        if (field.field.match(dexItemFactory.objectMembers.clinitField)) {
+          continue;
+        }
         DexField originalField = getOriginalFieldSignature(field.field);
         assert originalFields.contains(originalField)
             : "Unable to map field `" + field.field.toSourceString() + "` back to original program";
diff --git a/src/main/java/com/android/tools/r8/graph/InstantiatedSubTypeInfo.java b/src/main/java/com/android/tools/r8/graph/InstantiatedSubTypeInfo.java
index e4942bb..7887fe9 100644
--- a/src/main/java/com/android/tools/r8/graph/InstantiatedSubTypeInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/InstantiatedSubTypeInfo.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import java.util.function.Consumer;
 
 @FunctionalInterface
@@ -12,5 +13,5 @@
   void forEachInstantiatedSubType(
       DexType type,
       Consumer<DexProgramClass> subTypeConsumer,
-      Consumer<DexCallSite> callSiteConsumer);
+      Consumer<LambdaDescriptor> lambdaConsumer);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java b/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java
index bfc0eb4..64fa1a6 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java
@@ -14,6 +14,7 @@
   private final PinnedPredicate pinnedPredicate;
 
   private Set<DexType> pinnedInstantiations;
+  private Set<DexMethod> pinnedMethods;
 
   LookupCompletenessHelper(PinnedPredicate pinnedPredicate) {
     this.pinnedPredicate = pinnedPredicate;
@@ -28,15 +29,32 @@
     }
   }
 
+  void checkMethod(DexEncodedMethod method) {
+    if (pinnedPredicate.isPinned(method.method)) {
+      if (pinnedMethods == null) {
+        pinnedMethods = Sets.newIdentityHashSet();
+      }
+      pinnedMethods.add(method.method);
+    }
+  }
+
+  void checkDexClassAndMethod(DexClassAndMethod classAndMethod) {
+    checkClass(classAndMethod.getHolder());
+    checkMethod(classAndMethod.getMethod());
+  }
+
   LookupResultCollectionState computeCollectionState(
-      DexMethod method, AppView<? extends AppInfoWithClassHierarchy> appView) {
+      DexMethod method, AppInfoWithClassHierarchy appInfo) {
     assert pinnedInstantiations == null || !pinnedInstantiations.isEmpty();
     if (pinnedInstantiations == null) {
       return LookupResultCollectionState.Complete;
     }
+    if (pinnedMethods != null) {
+      return LookupResultCollectionState.Incomplete;
+    }
     WorkList<DexType> workList = WorkList.newIdentityWorkList(pinnedInstantiations);
     while (workList.hasNext()) {
-      if (isMethodKeptInSuperTypeOrIsLibrary(workList, method, appView)) {
+      if (isMethodKeptInSuperTypeOrIsLibrary(workList, method, appInfo)) {
         return LookupResultCollectionState.Incomplete;
       }
     }
@@ -44,11 +62,9 @@
   }
 
   private boolean isMethodKeptInSuperTypeOrIsLibrary(
-      WorkList<DexType> workList,
-      DexMethod method,
-      AppView<? extends AppInfoWithClassHierarchy> appView) {
+      WorkList<DexType> workList, DexMethod method, AppInfoWithClassHierarchy appInfo) {
     while (workList.hasNext()) {
-      DexClass parent = appView.definitionFor(workList.next());
+      DexClass parent = appInfo.definitionFor(workList.next());
       if (parent == null) {
         continue;
       }
diff --git a/src/main/java/com/android/tools/r8/graph/LookupLambdaTarget.java b/src/main/java/com/android/tools/r8/graph/LookupLambdaTarget.java
new file mode 100644
index 0000000..b41ef8f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/LookupLambdaTarget.java
@@ -0,0 +1,32 @@
+// 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.ir.desugar.LambdaDescriptor;
+
+public class LookupLambdaTarget implements LookupTarget {
+  private final LambdaDescriptor lambda;
+  private final DexClassAndMethod method;
+
+  public LookupLambdaTarget(LambdaDescriptor lambda, DexClassAndMethod method) {
+    assert lambda != null;
+    assert method != null;
+    this.lambda = lambda;
+    this.method = method;
+  }
+
+  @Override
+  public boolean isLambdaTarget() {
+    return true;
+  }
+
+  @Override
+  public LookupLambdaTarget asLambdaTarget() {
+    return this;
+  }
+
+  public DexClassAndMethod getImplementationMethod() {
+    return method;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/LookupResult.java b/src/main/java/com/android/tools/r8/graph/LookupResult.java
index fd71a8d..17d7f55 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupResult.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupResult.java
@@ -6,7 +6,9 @@
 
 import com.android.tools.r8.graph.LookupResult.LookupResultSuccess.LookupResultCollectionState;
 import java.util.Collections;
-import java.util.Set;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
 
 public abstract class LookupResult {
 
@@ -26,9 +28,18 @@
     return null;
   }
 
+  public final void forEach(Consumer<LookupTarget> onTarget) {
+    forEach(onTarget::accept, onTarget::accept);
+  }
+
+  public abstract void forEach(
+      Consumer<DexClassAndMethod> onMethodTarget, Consumer<LookupLambdaTarget> onLambdaTarget);
+
   public static LookupResultSuccess createResult(
-      Set<DexEncodedMethod> methodTargets, LookupResultCollectionState state) {
-    return new LookupResultSuccess(methodTargets, state);
+      Map<DexEncodedMethod, DexClassAndMethod> methodTargets,
+      List<LookupLambdaTarget> lambdaTargets,
+      LookupResultCollectionState state) {
+    return new LookupResultSuccess(methodTargets, lambdaTargets, state);
   }
 
   public static LookupResultFailure createFailedResult() {
@@ -42,23 +53,46 @@
   public static class LookupResultSuccess extends LookupResult {
 
     private static final LookupResultSuccess EMPTY_INSTANCE =
-        new LookupResultSuccess(Collections.emptySet(), LookupResultCollectionState.Incomplete);
+        new LookupResultSuccess(
+            Collections.emptyMap(),
+            Collections.emptyList(),
+            LookupResultCollectionState.Incomplete);
 
-    private final Set<DexEncodedMethod> methodTargets;
-    private final LookupResultCollectionState state;
+    private final Map<DexEncodedMethod, DexClassAndMethod> methodTargets;
+    private final List<LookupLambdaTarget> lambdaTargets;
+    private LookupResultCollectionState state;
 
     private LookupResultSuccess(
-        Set<DexEncodedMethod> methodTargets, LookupResultCollectionState state) {
+        Map<DexEncodedMethod, DexClassAndMethod> methodTargets,
+        List<LookupLambdaTarget> lambdaTargets,
+        LookupResultCollectionState state) {
       this.methodTargets = methodTargets;
+      this.lambdaTargets = lambdaTargets;
       this.state = state;
     }
 
     public boolean isEmpty() {
-      return methodTargets == null || methodTargets.isEmpty();
+      return methodTargets.isEmpty() && lambdaTargets.isEmpty();
     }
 
-    public Set<DexEncodedMethod> getMethodTargets() {
-      return methodTargets;
+    public boolean hasMethodTargets() {
+      return !methodTargets.isEmpty();
+    }
+
+    public boolean hasLambdaTargets() {
+      return !lambdaTargets.isEmpty();
+    }
+
+    @Override
+    public void forEach(
+        Consumer<DexClassAndMethod> onMethodTarget, Consumer<LookupLambdaTarget> onLambdaTarget) {
+      methodTargets.forEach((ignore, method) -> onMethodTarget.accept(method));
+      lambdaTargets.forEach(onLambdaTarget);
+    }
+
+    public boolean contains(DexEncodedMethod method) {
+      // Containment of a method in the lookup results only pertains to the method targets.
+      return methodTargets.containsKey(method);
     }
 
     @Override
@@ -79,6 +113,24 @@
       return state == LookupResultCollectionState.Complete;
     }
 
+    public void setIncomplete() {
+      // TODO(b/148769279): Remove when we have instantiated info.
+      state = LookupResultCollectionState.Incomplete;
+    }
+
+    public LookupTarget getSingleLookupTarget() {
+      if (isIncomplete() || methodTargets.size() + lambdaTargets.size() > 1) {
+        return null;
+      }
+      // TODO(b/150932978): Check lambda targets implementation methods.
+      if (methodTargets.size() == 1) {
+        return methodTargets.values().iterator().next();
+      } else if (lambdaTargets.size() == 1) {
+        return lambdaTargets.get(0);
+      }
+      return null;
+    }
+
     public enum LookupResultCollectionState {
       Complete,
       Incomplete,
@@ -102,5 +154,11 @@
     public boolean isLookupResultFailure() {
       return true;
     }
+
+    @Override
+    public void forEach(
+        Consumer<DexClassAndMethod> onMethodTarget, Consumer<LookupLambdaTarget> onLambdaTarget) {
+      // Nothing to iterate for a failed lookup.
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/LookupTarget.java b/src/main/java/com/android/tools/r8/graph/LookupTarget.java
new file mode 100644
index 0000000..6966a44
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/LookupTarget.java
@@ -0,0 +1,22 @@
+// 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;
+
+public interface LookupTarget {
+  default boolean isMethodTarget() {
+    return false;
+  }
+
+  default boolean isLambdaTarget() {
+    return false;
+  }
+
+  default DexClassAndMethod asMethodTarget() {
+    return null;
+  }
+
+  default LookupLambdaTarget asLambdaTarget() {
+    return null;
+  }
+}
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 53510bd..3869d23 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -6,15 +6,22 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.shaking.GraphReporter;
 import com.android.tools.r8.shaking.InstantiationReason;
 import com.android.tools.r8.shaking.KeepReason;
 import com.android.tools.r8.utils.LensUtils;
+import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 /** Stores the set of instantiated classes along with their allocation sites. */
 public class ObjectAllocationInfoCollectionImpl implements ObjectAllocationInfoCollection {
@@ -68,11 +75,33 @@
 
     private final boolean trackAllocationSites;
 
+    /** Instantiated classes with the contexts of the instantiations. */
     private final Map<DexProgramClass, Set<DexEncodedMethod>> classesWithAllocationSiteTracking =
         new IdentityHashMap<>();
+
+    /** Instantiated classes without contexts. */
     private final Set<DexProgramClass> classesWithoutAllocationSiteTracking =
         Sets.newIdentityHashSet();
 
+    /** Set of types directly implemented by a lambda. */
+    private final Map<DexType, List<LambdaDescriptor>> instantiatedLambdas =
+        new IdentityHashMap<>();
+
+    /**
+     * Hierarchy for instantiated types mapping a type to the set of immediate subtypes for which
+     * some subtype is either instantiated or is implemented by an instantiated lambda.
+     */
+    private final Map<DexType, Set<DexClass>> instantiatedHierarchy = new IdentityHashMap<>();
+
+    /**
+     * Set of interface types for which there may be instantiations, such as lambda expressions or
+     * explicit keep rules.
+     */
+    private final Set<DexProgramClass> instantiatedInterfaceTypes = Sets.newIdentityHashSet();
+
+    /** Subset of the above that are marked instantiated by usages that are not lambdas. */
+    public final Set<DexProgramClass> unknownInstantiatedInterfaceTypes = Sets.newIdentityHashSet();
+
     private GraphReporter reporter;
 
     private Builder(boolean trackAllocationSites, GraphReporter reporter) {
@@ -97,7 +126,15 @@
       return !clazz.instanceFields().isEmpty();
     }
 
+    public boolean isInstantiatedDirectlyOrIsInstantiationLeaf(DexProgramClass clazz) {
+      if (clazz.isInterface()) {
+        return instantiatedInterfaceTypes.contains(clazz);
+      }
+      return isInstantiatedDirectly(clazz);
+    }
+
     public boolean isInstantiatedDirectly(DexProgramClass clazz) {
+      assert !clazz.isInterface();
       if (classesWithAllocationSiteTracking.containsKey(clazz)) {
         assert !classesWithAllocationSiteTracking.get(clazz).isEmpty();
         return true;
@@ -105,6 +142,30 @@
       return classesWithoutAllocationSiteTracking.contains(clazz);
     }
 
+    public boolean isInstantiatedDirectlyOrHasInstantiatedSubtype(DexProgramClass clazz) {
+      return isInstantiatedDirectlyOrIsInstantiationLeaf(clazz)
+          || instantiatedHierarchy.containsKey(clazz.type);
+    }
+
+    public void forEachInstantiatedSubType(
+        DexType type,
+        Consumer<DexProgramClass> onClass,
+        Consumer<LambdaDescriptor> onLambda,
+        AppInfo appInfo) {
+      internalForEachInstantiatedSubType(
+          type,
+          onClass,
+          onLambda,
+          instantiatedHierarchy,
+          instantiatedLambdas,
+          this::isInstantiatedDirectlyOrIsInstantiationLeaf,
+          appInfo);
+    }
+
+    public Set<DexClass> getImmediateSubtypesInInstantiatedHierarchy(DexType type) {
+      return instantiatedHierarchy.get(type);
+    }
+
     /**
      * Records that {@param clazz} is instantiated in {@param context}.
      *
@@ -114,11 +175,13 @@
         DexProgramClass clazz,
         DexEncodedMethod context,
         InstantiationReason instantiationReason,
-        KeepReason keepReason) {
+        KeepReason keepReason,
+        AppInfo appInfo) {
       assert !clazz.isInterface();
       if (reporter != null) {
         reporter.registerClass(clazz, keepReason);
       }
+      populateInstantiatedHierarchy(appInfo, clazz);
       if (shouldTrackAllocationSitesForClass(clazz, instantiationReason)) {
         assert context != null;
         Set<DexEncodedMethod> allocationSitesForClass =
@@ -135,6 +198,51 @@
       return false;
     }
 
+    public boolean recordInstantiatedInterface(DexProgramClass iface) {
+      assert iface.isInterface();
+      assert !iface.isAnnotation();
+      unknownInstantiatedInterfaceTypes.add(iface);
+      return instantiatedInterfaceTypes.add(iface);
+    }
+
+    public void recordInstantiatedLambdaInterface(
+        DexType iface, LambdaDescriptor lambda, AppInfo appInfo) {
+      instantiatedLambdas.computeIfAbsent(iface, key -> new ArrayList<>()).add(lambda);
+      populateInstantiatedHierarchy(appInfo, iface);
+    }
+
+    private void populateInstantiatedHierarchy(AppInfo appInfo, DexType type) {
+      DexClass clazz = appInfo.definitionFor(type);
+      if (clazz != null) {
+        populateInstantiatedHierarchy(appInfo, clazz);
+      }
+    }
+
+    private void populateInstantiatedHierarchy(AppInfo appInfo, DexClass clazz) {
+      if (clazz.superType != null) {
+        populateInstantiatedHierarchy(appInfo, clazz.superType, clazz);
+      }
+      for (DexType iface : clazz.interfaces.values) {
+        populateInstantiatedHierarchy(appInfo, iface, clazz);
+      }
+    }
+
+    private void populateInstantiatedHierarchy(AppInfo appInfo, DexType type, DexClass subtype) {
+      if (type == appInfo.dexItemFactory().objectType) {
+        return;
+      }
+      Set<DexClass> subtypes = instantiatedHierarchy.get(type);
+      if (subtypes != null) {
+        subtypes.add(subtype);
+        return;
+      }
+      // This is the first time an instantiation appears below 'type', recursively populate.
+      subtypes = Sets.newIdentityHashSet();
+      subtypes.add(subtype);
+      instantiatedHierarchy.put(type, subtypes);
+      populateInstantiatedHierarchy(appInfo, type);
+    }
+
     Builder rewrittenWithLens(
         ObjectAllocationInfoCollectionImpl objectAllocationInfos,
         DexDefinitionSupplier definitions,
@@ -173,4 +281,48 @@
           classesWithAllocationSiteTracking, classesWithoutAllocationSiteTracking);
     }
   }
+
+  private static void internalForEachInstantiatedSubType(
+      DexType type,
+      Consumer<DexProgramClass> subTypeConsumer,
+      Consumer<LambdaDescriptor> lambdaConsumer,
+      Map<DexType, Set<DexClass>> instantiatedHierarchy,
+      Map<DexType, List<LambdaDescriptor>> instantiatedLambdas,
+      Predicate<DexProgramClass> isInstantiatedDirectly,
+      AppInfo appInfo) {
+    WorkList<DexClass> worklist = WorkList.newIdentityWorkList();
+    if (type == appInfo.dexItemFactory().objectType) {
+      // All types are below java.lang.Object, but we don't maintain an entry for it.
+      instantiatedHierarchy.forEach(
+          (key, subtypes) -> {
+            DexClass clazz = appInfo.definitionFor(key);
+            if (clazz != null) {
+              worklist.addIfNotSeen(clazz);
+            }
+            worklist.addIfNotSeen(subtypes);
+          });
+    } else {
+      DexClass initialClass = appInfo.definitionFor(type);
+      if (initialClass == null) {
+        // If no definition for the type is found, populate the worklist with any
+        // instantiated subtypes and callback with any lambda instance.
+        worklist.addIfNotSeen(instantiatedHierarchy.getOrDefault(type, Collections.emptySet()));
+        instantiatedLambdas.getOrDefault(type, Collections.emptyList()).forEach(lambdaConsumer);
+      } else {
+        worklist.addIfNotSeen(initialClass);
+      }
+    }
+
+    while (worklist.hasNext()) {
+      DexClass clazz = worklist.next();
+      if (clazz.isProgramClass()) {
+        DexProgramClass programClass = clazz.asProgramClass();
+        if (isInstantiatedDirectly.test(programClass)) {
+          subTypeConsumer.accept(programClass);
+        }
+      }
+      worklist.addIfNotSeen(instantiatedHierarchy.getOrDefault(clazz.type, Collections.emptySet()));
+      instantiatedLambdas.getOrDefault(clazz.type, Collections.emptyList()).forEach(lambdaConsumer);
+    }
+  }
 }
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 bb10b76..f9cea40 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -8,10 +8,13 @@
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.InstantiatedObject;
-import com.google.common.collect.Sets;
+import com.android.tools.r8.utils.Box;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Set;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 
@@ -58,8 +61,6 @@
   public abstract boolean isAccessibleForVirtualDispatchFrom(
       DexProgramClass context, AppInfoWithClassHierarchy appInfo);
 
-  // TODO(b/145187573): Remove this and use proper access checks.
-  @Deprecated
   public abstract boolean isVirtualTarget();
 
   /** Lookup the single target of an invoke-special on this resolution result if possible. */
@@ -80,25 +81,30 @@
 
   public abstract LookupResult lookupVirtualDispatchTargets(
       DexProgramClass context,
-      AppView<? extends AppInfoWithClassHierarchy> appView,
+      AppInfoWithClassHierarchy appInfo,
       InstantiatedSubTypeInfo instantiatedInfo,
       PinnedPredicate pinnedPredicate);
 
   public final LookupResult lookupVirtualDispatchTargets(
-      DexProgramClass context, AppView<AppInfoWithLiveness> appView) {
-    AppInfoWithLiveness appInfoWithLiveness = appView.appInfo();
+      DexProgramClass context, AppInfoWithLiveness appInfo) {
     return lookupVirtualDispatchTargets(
-        context, appView, appInfoWithLiveness, appInfoWithLiveness::isPinned);
+        context, appInfo, appInfo, appInfo::isPinnedNotProgramOrLibraryOverride);
   }
 
-  public abstract DexClassAndMethod lookupVirtualDispatchTarget(
-      InstantiatedObject instance, AppView<? extends AppInfoWithClassHierarchy> appView);
+  public abstract LookupResult lookupVirtualDispatchTargets(
+      DexProgramClass context,
+      AppInfoWithLiveness appInfo,
+      DexProgramClass refinedReceiverUpperBound,
+      DexProgramClass refinedReceiverLowerBound);
+
+  public abstract LookupTarget lookupVirtualDispatchTarget(
+      InstantiatedObject instance, AppInfoWithClassHierarchy appInfo);
 
   public abstract DexClassAndMethod lookupVirtualDispatchTarget(
-      DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView);
+      DexProgramClass dynamicInstance, AppInfoWithClassHierarchy appInfo);
 
-  public abstract DexClassAndMethod lookupVirtualDispatchTarget(
-      LambdaDescriptor lambdaInstance, AppView<? extends AppInfoWithClassHierarchy> appView);
+  public abstract LookupTarget lookupVirtualDispatchTarget(
+      LambdaDescriptor lambdaInstance, AppInfoWithClassHierarchy appInfo);
 
   /** Result for a resolution that succeeds with a known declaration/definition. */
   public static class SingleResolutionResult extends ResolutionResult {
@@ -333,13 +339,13 @@
     @Override
     public LookupResult lookupVirtualDispatchTargets(
         DexProgramClass context,
-        AppView<? extends AppInfoWithClassHierarchy> appView,
+        AppInfoWithClassHierarchy appInfo,
         InstantiatedSubTypeInfo instantiatedInfo,
         PinnedPredicate pinnedPredicate) {
       // Check that the initial resolution holder is accessible from the context.
-      assert appView.isSubtype(initialResolutionHolder.type, resolvedHolder.type).isTrue()
+      assert appInfo.isSubtype(initialResolutionHolder.type, resolvedHolder.type)
           : initialResolutionHolder.type + " is not a subtype of " + resolvedHolder.type;
-      if (context != null && !isAccessibleFrom(context, appView.appInfo())) {
+      if (context != null && !isAccessibleFrom(context, appInfo)) {
         return LookupResult.createFailedResult();
       }
       if (resolvedMethod.isPrivateMethod()) {
@@ -350,37 +356,121 @@
             pinnedPredicate.isPinned(resolvedHolder.type)
                 && pinnedPredicate.isPinned(resolvedMethod.method);
         return LookupResult.createResult(
-            Collections.singleton(resolvedMethod),
+            Collections.singletonMap(
+                resolvedMethod, DexClassAndMethod.create(resolvedHolder, resolvedMethod)),
+            Collections.emptyList(),
             isIncomplete
                 ? LookupResultCollectionState.Incomplete
                 : LookupResultCollectionState.Complete);
       }
       assert resolvedMethod.isNonPrivateVirtualMethod();
-      Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
+      Map<DexEncodedMethod, DexClassAndMethod> methodTargets = new IdentityHashMap<>();
+      List<LookupLambdaTarget> lambdaTargets = new ArrayList<>();
       LookupCompletenessHelper incompleteness = new LookupCompletenessHelper(pinnedPredicate);
-      // TODO(b/150171154): Use instantiationHolder below.
       instantiatedInfo.forEachInstantiatedSubType(
-          resolvedHolder.type,
+          initialResolutionHolder.type,
           subClass -> {
             incompleteness.checkClass(subClass);
             DexClassAndMethod dexClassAndMethod =
-                lookupVirtualDispatchTarget(subClass, appView, resolvedHolder.type);
+                lookupVirtualDispatchTarget(subClass, appInfo, resolvedHolder.type);
             if (dexClassAndMethod != null) {
+              incompleteness.checkDexClassAndMethod(dexClassAndMethod);
               addVirtualDispatchTarget(
-                  dexClassAndMethod.getMethod(), resolvedHolder.isInterface(), result);
+                  dexClassAndMethod, resolvedHolder.isInterface(), methodTargets);
             }
           },
-          dexCallSite -> {
-            // TODO(b/148769279): We need to look at the call site to see if it overrides
-            //   the resolved method or not.
+          lambda -> {
+            assert resolvedHolder.isInterface();
+            LookupTarget target = lookupVirtualDispatchTarget(lambda, appInfo);
+            if (target != null) {
+              if (target.isLambdaTarget()) {
+                lambdaTargets.add(target.asLambdaTarget());
+              } else {
+                addVirtualDispatchTarget(
+                    target.asMethodTarget(), resolvedHolder.isInterface(), methodTargets);
+              }
+            }
           });
       return LookupResult.createResult(
-          result, incompleteness.computeCollectionState(resolvedMethod.method, appView));
+          methodTargets,
+          lambdaTargets,
+          incompleteness.computeCollectionState(resolvedMethod.method, appInfo));
+    }
+
+    @Override
+    public LookupResult lookupVirtualDispatchTargets(
+        DexProgramClass context,
+        AppInfoWithLiveness appInfo,
+        DexProgramClass refinedReceiverUpperBound,
+        DexProgramClass refinedReceiverLowerBound) {
+      assert refinedReceiverUpperBound != null;
+      assert appInfo.isSubtype(refinedReceiverUpperBound.type, initialResolutionHolder.type);
+      assert refinedReceiverLowerBound == null
+          || appInfo.isSubtype(refinedReceiverLowerBound.type, refinedReceiverUpperBound.type);
+      // TODO(b/148769279): Remove the check for hasInstantiatedLambdas.
+      Box<Boolean> hasInstantiatedLambdas = new Box<>(false);
+      InstantiatedSubTypeInfo instantiatedSubTypeInfo =
+          refinedReceiverLowerBound == null
+              ? instantiatedSubTypeInfoWithoutLowerBound(
+                  appInfo, refinedReceiverUpperBound, hasInstantiatedLambdas)
+              : instantiatedSubTypeInfoWithLowerBound(
+                  appInfo,
+                  refinedReceiverUpperBound,
+                  refinedReceiverLowerBound,
+                  hasInstantiatedLambdas);
+      LookupResult lookupResult =
+          lookupVirtualDispatchTargets(
+              context,
+              appInfo,
+              instantiatedSubTypeInfo,
+              appInfo::isPinnedNotProgramOrLibraryOverride);
+      if (hasInstantiatedLambdas.get() && lookupResult.isLookupResultSuccess()) {
+        lookupResult.asLookupResultSuccess().setIncomplete();
+      }
+      return lookupResult;
+    }
+
+    private InstantiatedSubTypeInfo instantiatedSubTypeInfoWithoutLowerBound(
+        AppInfoWithLiveness appInfo,
+        DexProgramClass refinedReceiverUpperBound,
+        Box<Boolean> hasInstantiatedLambdas) {
+      return (type, subTypeConsumer, callSiteConsumer) -> {
+        appInfo.forEachInstantiatedSubType(
+            refinedReceiverUpperBound.type,
+            subType -> {
+              if (appInfo.hasAnyInstantiatedLambdas(subType)) {
+                hasInstantiatedLambdas.set(true);
+              }
+              subTypeConsumer.accept(subType);
+            },
+            callSiteConsumer);
+      };
+    }
+
+    private InstantiatedSubTypeInfo instantiatedSubTypeInfoWithLowerBound(
+        AppInfoWithLiveness appInfo,
+        DexProgramClass refinedReceiverUpperBound,
+        DexProgramClass refinedReceiverLowerBound,
+        Box<Boolean> hasInstantiatedLambdas) {
+      return (type, subTypeConsumer, callSiteConsumer) -> {
+        List<DexProgramClass> subTypes =
+            appInfo.computeProgramClassRelationChain(
+                refinedReceiverLowerBound, refinedReceiverUpperBound);
+        for (DexProgramClass subType : subTypes) {
+          if (appInfo.hasAnyInstantiatedLambdas(subType)) {
+            hasInstantiatedLambdas.set(true);
+          }
+          subTypeConsumer.accept(subType);
+        }
+      };
     }
 
     private static void addVirtualDispatchTarget(
-        DexEncodedMethod target, boolean holderIsInterface, Set<DexEncodedMethod> result) {
-      assert !target.isPrivateMethod();
+        DexClassAndMethod target,
+        boolean holderIsInterface,
+        Map<DexEncodedMethod, DexClassAndMethod> result) {
+      DexEncodedMethod targetMethod = target.getMethod();
+      assert !targetMethod.isPrivateMethod();
       if (holderIsInterface) {
         // Add default interface methods to the list of targets.
         //
@@ -404,18 +494,18 @@
         //     public void bar() { }
         //   }
         //
-        if (target.isDefaultMethod()) {
-          result.add(target);
+        if (targetMethod.isDefaultMethod()) {
+          result.putIfAbsent(targetMethod, 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);
+        if (!targetMethod.accessFlags.isAbstract() && targetMethod.accessFlags.isBridge()) {
+          result.putIfAbsent(targetMethod, target);
         }
       } else {
-        result.add(target);
+        result.putIfAbsent(targetMethod, target);
       }
     }
 
@@ -425,55 +515,61 @@
      * we have an object ref on the stack.
      */
     @Override
-    public DexClassAndMethod lookupVirtualDispatchTarget(
-        InstantiatedObject instance, AppView<? extends AppInfoWithClassHierarchy> appView) {
+    public LookupTarget lookupVirtualDispatchTarget(
+        InstantiatedObject instance, AppInfoWithClassHierarchy appInfo) {
       return instance.isClass()
-          ? lookupVirtualDispatchTarget(instance.asClass(), appView)
-          : lookupVirtualDispatchTarget(instance.asLambda(), appView);
+          ? lookupVirtualDispatchTarget(instance.asClass(), appInfo)
+          : lookupVirtualDispatchTarget(instance.asLambda(), appInfo);
     }
 
     @Override
     public DexClassAndMethod lookupVirtualDispatchTarget(
-        DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView) {
-      return lookupVirtualDispatchTarget(dynamicInstance, appView, initialResolutionHolder.type);
+        DexProgramClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
+      return lookupVirtualDispatchTarget(dynamicInstance, appInfo, initialResolutionHolder.type);
     }
 
     @Override
-    public DexClassAndMethod lookupVirtualDispatchTarget(
-        LambdaDescriptor lambdaInstance, AppView<? extends AppInfoWithClassHierarchy> appView) {
+    public LookupTarget lookupVirtualDispatchTarget(
+        LambdaDescriptor lambdaInstance, AppInfoWithClassHierarchy appInfo) {
       if (lambdaInstance.getMainMethod().match(resolvedMethod)) {
         DexMethod method = lambdaInstance.implHandle.asMethod();
-        DexClass holder = appView.definitionFor(method.holder);
+        DexClass holder = appInfo.definitionFor(method.holder);
         if (holder == null) {
           assert false;
           return null;
         }
-        DexEncodedMethod encodedMethod = appView.definitionFor(method);
+        DexEncodedMethod encodedMethod = appInfo.definitionFor(method);
         if (encodedMethod == null) {
           // The targeted method might not exist, eg, Throwable.addSuppressed in an old library.
           return null;
         }
-        return DexClassAndMethod.create(holder, encodedMethod);
+        return new LookupLambdaTarget(
+            lambdaInstance, DexClassAndMethod.create(holder, encodedMethod));
       }
-      return lookupMaximallySpecificDispatchTarget(lambdaInstance, appView);
+      return lookupMaximallySpecificDispatchTarget(lambdaInstance, appInfo);
     }
 
     private DexClassAndMethod lookupVirtualDispatchTarget(
         DexProgramClass dynamicInstance,
-        AppView<? extends AppInfoWithClassHierarchy> appView,
+        AppInfoWithClassHierarchy appInfo,
         DexType resolutionHolder) {
-      assert appView.isSubtype(dynamicInstance.type, resolutionHolder).isTrue()
+      assert appInfo.isSubtype(dynamicInstance.type, resolutionHolder)
           : dynamicInstance.type + " is not a subtype of " + resolutionHolder;
       // TODO(b/148591377): Enable this assertion.
       // The dynamic type cannot be an interface.
       // assert !dynamicInstance.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.
+        return DexClassAndMethod.create(resolvedHolder, resolvedMethod);
+      }
       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);
+          overrideTarget = findWideningOverride(resolvedMethod, current, appInfo);
           allowPackageBlocked = false;
           continue;
         }
@@ -482,7 +578,7 @@
           if (current.type == overrideTarget.method.holder) {
             return null;
           }
-          current = current.superType == null ? null : appView.definitionFor(current.superType);
+          current = current.superType == null ? null : appInfo.definitionFor(current.superType);
           continue;
         }
         return DexClassAndMethod.create(current, candidate);
@@ -492,21 +588,17 @@
       if (!resolvedHolder.isInterface()) {
         return null;
       }
-      return lookupMaximallySpecificDispatchTarget(dynamicInstance, appView);
+      return lookupMaximallySpecificDispatchTarget(dynamicInstance, appInfo);
     }
 
     private DexClassAndMethod lookupMaximallySpecificDispatchTarget(
-        DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView) {
-      return appView
-          .appInfo()
-          .lookupMaximallySpecificMethod(dynamicInstance, resolvedMethod.method);
+        DexProgramClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
+      return appInfo.lookupMaximallySpecificMethod(dynamicInstance, resolvedMethod.method);
     }
 
     private DexClassAndMethod lookupMaximallySpecificDispatchTarget(
-        LambdaDescriptor lambdaDescriptor, AppView<? extends AppInfoWithClassHierarchy> appView) {
-      return appView
-          .appInfo()
-          .lookupMaximallySpecificMethod(lambdaDescriptor, resolvedMethod.method);
+        LambdaDescriptor lambdaDescriptor, AppInfoWithClassHierarchy appInfo) {
+      return appInfo.lookupMaximallySpecificMethod(lambdaDescriptor, resolvedMethod.method);
     }
 
     /**
@@ -526,9 +618,7 @@
     }
 
     private static DexEncodedMethod findWideningOverride(
-        DexEncodedMethod resolvedMethod,
-        DexClass clazz,
-        AppView<? extends AppInfoWithClassHierarchy> appView) {
+        DexEncodedMethod resolvedMethod, DexClass clazz, AppInfoWithClassHierarchy appView) {
       // Otherwise, lookup to first override that is distinct from resolvedMethod.
       assert resolvedMethod.accessFlags.isPackagePrivate();
       while (clazz.superType != null) {
@@ -555,7 +645,7 @@
      * 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(
+    public static boolean isOverriding(
         DexEncodedMethod resolvedMethod, DexEncodedMethod candidate) {
       assert resolvedMethod.method.match(candidate.method);
       assert !candidate.isPrivateMethod();
@@ -597,27 +687,36 @@
     @Override
     public LookupResult lookupVirtualDispatchTargets(
         DexProgramClass context,
-        AppView<? extends AppInfoWithClassHierarchy> appView,
+        AppInfoWithClassHierarchy appInfo,
         InstantiatedSubTypeInfo instantiatedInfo,
         PinnedPredicate pinnedPredicate) {
       return LookupResult.getIncompleteEmptyResult();
     }
 
     @Override
+    public LookupResult lookupVirtualDispatchTargets(
+        DexProgramClass context,
+        AppInfoWithLiveness appInfo,
+        DexProgramClass refinedReceiverUpperBound,
+        DexProgramClass refinedReceiverLowerBound) {
+      return LookupResult.getIncompleteEmptyResult();
+    }
+
+    @Override
     public DexClassAndMethod lookupVirtualDispatchTarget(
-        InstantiatedObject instance, AppView<? extends AppInfoWithClassHierarchy> appView) {
+        InstantiatedObject instance, AppInfoWithClassHierarchy appInfo) {
       return null;
     }
 
     @Override
     public DexClassAndMethod lookupVirtualDispatchTarget(
-        DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView) {
+        DexProgramClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
       return null;
     }
 
     @Override
     public DexClassAndMethod lookupVirtualDispatchTarget(
-        LambdaDescriptor lambdaInstance, AppView<? extends AppInfoWithClassHierarchy> appView) {
+        LambdaDescriptor lambdaInstance, AppInfoWithClassHierarchy appInfo) {
       return null;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index fb41239..f452556 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -35,16 +35,32 @@
 
   public abstract boolean registerInvokeSuper(DexMethod method);
 
+  public abstract boolean registerInstanceFieldRead(DexField field);
+
+  public boolean registerInstanceFieldReadFromMethodHandle(DexField field) {
+    return registerInstanceFieldRead(field);
+  }
+
   public abstract boolean registerInstanceFieldWrite(DexField field);
 
-  public abstract boolean registerInstanceFieldRead(DexField field);
+  public boolean registerInstanceFieldWriteFromMethodHandle(DexField field) {
+    return registerInstanceFieldWrite(field);
+  }
 
   public abstract boolean registerNewInstance(DexType type);
 
   public abstract boolean registerStaticFieldRead(DexField field);
 
+  public boolean registerStaticFieldReadFromMethodHandle(DexField field) {
+    return registerStaticFieldRead(field);
+  }
+
   public abstract boolean registerStaticFieldWrite(DexField field);
 
+  public boolean registerStaticFieldWriteFromMethodHandle(DexField field) {
+    return registerStaticFieldWrite(field);
+  }
+
   public abstract boolean registerTypeReference(DexType type);
 
   public boolean registerConstClass(DexType type) {
@@ -55,20 +71,19 @@
     return registerTypeReference(type);
   }
 
-  public void registerMethodHandle(
-      DexMethodHandle methodHandle, MethodHandleUse use) {
+  public void registerMethodHandle(DexMethodHandle methodHandle, MethodHandleUse use) {
     switch (methodHandle.type) {
       case INSTANCE_GET:
-        registerInstanceFieldRead(methodHandle.asField());
+        registerInstanceFieldReadFromMethodHandle(methodHandle.asField());
         break;
       case INSTANCE_PUT:
-        registerInstanceFieldWrite(methodHandle.asField());
+        registerInstanceFieldWriteFromMethodHandle(methodHandle.asField());
         break;
       case STATIC_GET:
-        registerStaticFieldRead(methodHandle.asField());
+        registerStaticFieldReadFromMethodHandle(methodHandle.asField());
         break;
       case STATIC_PUT:
-        registerStaticFieldWrite(methodHandle.asField());
+        registerStaticFieldWriteFromMethodHandle(methodHandle.asField());
         break;
       case INVOKE_INSTANCE:
         registerInvokeVirtual(methodHandle.asMethod());
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
index c85439b..d8d48a2 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
@@ -121,9 +121,8 @@
       registration.accept(virtualMethod);
     }
     DexProgramClass reverseWrapper = wrappersToReverseMap.get(wrapper);
-    if (reverseWrapper != null) {
-      registration.accept(getConvertMethod(reverseWrapper));
-    }
+    assert reverseWrapper != null;
+    registration.accept(getConvertMethod(reverseWrapper));
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
index 2b55be8..8374410 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
@@ -15,6 +15,9 @@
   /** Called when a class is found to be instantiated. */
   public void processNewlyInstantiatedClass(DexProgramClass clazz, DexEncodedMethod context) {}
 
+  /** Called when a class is found to be live. */
+  public void processNewlyLiveClass(DexProgramClass clazz, EnqueuerWorklist worklist) {}
+
   /** Called when a field is found to be live. */
   public void processNewlyLiveField(DexEncodedField field) {}
 
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 4064d79..d5bd10a 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
@@ -10,6 +10,7 @@
 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.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -75,6 +76,8 @@
    * is interpreted as if we known nothing about the value of the field.
    */
   private void initializeAbstractInstanceFieldValues() {
+    FieldAccessInfoCollection<?> fieldAccessInfos =
+        appView.appInfo().getFieldAccessInfoCollection();
     ObjectAllocationInfoCollection objectAllocationInfos =
         appView.appInfo().getObjectAllocationInfoCollection();
     objectAllocationInfos.forEachClassWithKnownAllocationSites(
@@ -91,7 +94,10 @@
           Map<DexEncodedField, AbstractValue> abstractInstanceFieldValuesForClass =
               new IdentityHashMap<>();
           for (DexEncodedField field : clazz.instanceFields()) {
-            abstractInstanceFieldValuesForClass.put(field, BottomValue.getInstance());
+            FieldAccessInfo fieldAccessInfo = fieldAccessInfos.get(field.field);
+            if (fieldAccessInfo != null && !fieldAccessInfo.hasReflectiveAccess()) {
+              abstractInstanceFieldValuesForClass.put(field, BottomValue.getInstance());
+            }
           }
           abstractInstanceFieldValues.put(clazz, abstractInstanceFieldValuesForClass);
         });
@@ -313,7 +319,7 @@
               assert false;
               return;
             }
-            if (!info.hasReflectiveAccess()) {
+            if (!info.hasReflectiveAccess() && !info.isWrittenFromMethodHandle()) {
               info.forEachWriteContext(
                   context ->
                       fieldWrites.computeIfAbsent(context, ignore -> new ArrayList<>()).add(field));
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
new file mode 100644
index 0000000..b06a015
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -0,0 +1,261 @@
+// 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.fieldaccess;
+
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.FieldAccessInfo;
+import com.android.tools.r8.graph.FieldAccessInfoCollection;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
+import com.android.tools.r8.ir.conversion.PostMethodProcessor;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Sets;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class TrivialFieldAccessReprocessor {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final PostMethodProcessor.Builder postMethodProcessorBuilder;
+
+  /** Updated concurrently from {@link #processClass(DexProgramClass)}. */
+  private final Set<DexEncodedField> fieldsOfInterest = Sets.newConcurrentHashSet();
+
+  /** Updated concurrently from {@link #processClass(DexProgramClass)}. */
+  private final Set<DexEncodedMethod> methodsToReprocess = Sets.newConcurrentHashSet();
+
+  public TrivialFieldAccessReprocessor(
+      AppView<AppInfoWithLiveness> appView,
+      PostMethodProcessor.Builder postMethodProcessorBuilder) {
+    this.appView = appView;
+    this.postMethodProcessorBuilder = postMethodProcessorBuilder;
+  }
+
+  public void run(
+      ExecutorService executorService, OptimizationFeedbackDelayed feedback, Timing timing)
+      throws ExecutionException {
+    AppInfoWithLiveness appInfo = appView.appInfo();
+
+    timing.begin("Trivial field accesses analysis");
+    assert feedback.noUpdatesLeft();
+
+    timing.begin("Compute fields of interest");
+    computeFieldsOfInterest(appInfo);
+    timing.end(); // Compute fields of interest
+
+    if (fieldsOfInterest.isEmpty()) {
+      timing.end(); // Trivial field accesses analysis
+      return;
+    }
+
+    timing.begin("Clear reads from fields of interest");
+    clearReadsFromFieldsOfInterest(appInfo);
+    timing.end(); // Clear reads from fields of interest
+
+    timing.begin("Enqueue methods for reprocessing");
+    enqueueMethodsForReprocessing(appInfo, executorService);
+    timing.end(); // Enqueue methods for reprocessing
+    timing.end(); // Trivial field accesses analysis
+
+    fieldsOfInterest.forEach(OptimizationFeedbackSimple.getInstance()::markFieldAsDead);
+  }
+
+  private void computeFieldsOfInterest(AppInfoWithLiveness appInfo) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    for (DexProgramClass clazz : appInfo.classes()) {
+      for (DexEncodedField field : clazz.instanceFields()) {
+        if (canOptimizeField(field, appView)) {
+          fieldsOfInterest.add(field);
+        }
+      }
+      OptionalBool mayRequireClinitField = OptionalBool.unknown();
+      for (DexEncodedField field : clazz.staticFields()) {
+        if (canOptimizeField(field, appView)) {
+          if (mayRequireClinitField.isUnknown()) {
+            mayRequireClinitField =
+                OptionalBool.of(clazz.classInitializationMayHaveSideEffects(appView));
+          }
+          fieldsOfInterest.add(field);
+        }
+      }
+      if (mayRequireClinitField.isTrue()) {
+        DexField clinitField = dexItemFactory.objectMembers.clinitField;
+        if (clazz.lookupStaticField(dexItemFactory.objectMembers.clinitField) == null) {
+          FieldAccessFlags accessFlags =
+              FieldAccessFlags.fromSharedAccessFlags(
+                  Constants.ACC_SYNTHETIC
+                      | Constants.ACC_FINAL
+                      | Constants.ACC_PUBLIC
+                      | Constants.ACC_STATIC);
+          clazz.appendStaticField(
+              new DexEncodedField(
+                  dexItemFactory.createField(clazz.type, clinitField.type, clinitField.name),
+                  accessFlags,
+                  DexAnnotationSet.empty(),
+                  null));
+          appView.appInfo().invalidateTypeCacheFor(clazz.type);
+        }
+      }
+    }
+    assert verifyNoConstantFieldsOnSynthesizedClasses(appView);
+  }
+
+  private void clearReadsFromFieldsOfInterest(AppInfoWithLiveness appInfo) {
+    FieldAccessInfoCollection<?> fieldAccessInfoCollection = appInfo.getFieldAccessInfoCollection();
+    for (DexEncodedField field : fieldsOfInterest) {
+      fieldAccessInfoCollection.get(field.field).asMutable().clearReads();
+    }
+  }
+
+  private void enqueueMethodsForReprocessing(
+      AppInfoWithLiveness appInfo, ExecutorService executorService) throws ExecutionException {
+    ThreadUtils.processItems(appInfo.classes(), this::processClass, executorService);
+    ThreadUtils.processItems(appInfo.synthesizedClasses(), this::processClass, executorService);
+    postMethodProcessorBuilder.put(methodsToReprocess);
+  }
+
+  private void processClass(DexProgramClass clazz) {
+    for (DexEncodedMethod method : clazz.methods()) {
+      if (method.hasCode()) {
+        method.getCode().registerCodeReferences(method, new TrivialFieldAccessUseRegistry(method));
+      }
+    }
+  }
+
+  private static boolean canOptimizeField(
+      DexEncodedField field, AppView<AppInfoWithLiveness> appView) {
+    FieldAccessInfo fieldAccessInfo =
+        appView.appInfo().getFieldAccessInfoCollection().get(field.field);
+    if (fieldAccessInfo == null || fieldAccessInfo.isAccessedFromMethodHandle()) {
+      return false;
+    }
+    AbstractValue abstractValue = field.getOptimizationInfo().getAbstractValue();
+    if (abstractValue.isSingleValue()) {
+      SingleValue singleValue = abstractValue.asSingleValue();
+      if (!singleValue.isMaterializableInAllContexts(appView)) {
+        return false;
+      }
+      if (singleValue.isSingleConstValue()) {
+        return true;
+      }
+      if (singleValue.isSingleFieldValue()) {
+        SingleFieldValue singleFieldValue = singleValue.asSingleFieldValue();
+        DexField singleField = singleFieldValue.getField();
+        return singleField != field.field
+            && !singleFieldValue.mayHaveFinalizeMethodDirectlyOrIndirectly(appView);
+      }
+    }
+    return false;
+  }
+
+  private static boolean verifyNoConstantFieldsOnSynthesizedClasses(
+      AppView<AppInfoWithLiveness> appView) {
+    for (DexProgramClass clazz : appView.appInfo().synthesizedClasses()) {
+      for (DexEncodedField field : clazz.fields()) {
+        assert field.getOptimizationInfo().getAbstractValue().isUnknown();
+      }
+    }
+    return true;
+  }
+
+  class TrivialFieldAccessUseRegistry extends UseRegistry {
+
+    private final DexEncodedMethod method;
+
+    TrivialFieldAccessUseRegistry(DexEncodedMethod method) {
+      super(appView.dexItemFactory());
+      this.method = method;
+    }
+
+    private boolean registerFieldAccess(DexField field, boolean isStatic) {
+      DexEncodedField encodedField = appView.appInfo().resolveField(field);
+      if (encodedField != null) {
+        if (encodedField.isStatic() == isStatic) {
+          if (fieldsOfInterest.contains(encodedField)) {
+            methodsToReprocess.add(method);
+          }
+        } else {
+          // Should generally not happen.
+          fieldsOfInterest.remove(encodedField);
+        }
+      }
+      return true;
+    }
+
+    @Override
+    public boolean registerInstanceFieldWrite(DexField field) {
+      return registerFieldAccess(field, false);
+    }
+
+    @Override
+    public boolean registerInstanceFieldRead(DexField field) {
+      return registerFieldAccess(field, false);
+    }
+
+    @Override
+    public boolean registerStaticFieldRead(DexField field) {
+      return registerFieldAccess(field, true);
+    }
+
+    @Override
+    public boolean registerStaticFieldWrite(DexField field) {
+      return registerFieldAccess(field, true);
+    }
+
+    @Override
+    public boolean registerInvokeVirtual(DexMethod method) {
+      return false;
+    }
+
+    @Override
+    public boolean registerInvokeDirect(DexMethod method) {
+      return false;
+    }
+
+    @Override
+    public boolean registerInvokeStatic(DexMethod method) {
+      return false;
+    }
+
+    @Override
+    public boolean registerInvokeInterface(DexMethod method) {
+      return false;
+    }
+
+    @Override
+    public boolean registerInvokeSuper(DexMethod method) {
+      return false;
+    }
+
+    @Override
+    public boolean registerNewInstance(DexType type) {
+      return false;
+    }
+
+    @Override
+    public boolean registerTypeReference(DexType type) {
+      return false;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java
index 90320b7..c6e42b6 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java
@@ -4,7 +4,9 @@
 
 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.GraphLense;
 
 /**
  * Implements a lifted subset lattice for fields.
@@ -42,6 +44,8 @@
 
   public abstract boolean contains(DexEncodedField field);
 
+  public abstract boolean isEmpty();
+
   public boolean isBottom() {
     return false;
   }
@@ -65,4 +69,6 @@
   public final boolean strictlyLessThan(AbstractFieldSet other) {
     return lessThanOrEqual(other) && !equals(other);
   }
+
+  public abstract AbstractFieldSet rewrittenWithLens(AppView<?> appView, GraphLense lens);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
index bc13aa1..a2a932c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
@@ -4,7 +4,9 @@
 
 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.GraphLense;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Sets;
@@ -65,6 +67,26 @@
   }
 
   @Override
+  public AbstractFieldSet rewrittenWithLens(AppView<?> appView, GraphLense lens) {
+    assert !isEmpty();
+    ConcreteMutableFieldSet rewrittenSet = new ConcreteMutableFieldSet();
+    for (DexEncodedField field : fields) {
+      DexEncodedField rewrittenField = appView.definitionFor(lens.lookupField(field.field));
+      if (rewrittenField == null) {
+        assert false;
+        continue;
+      }
+      rewrittenSet.add(field);
+    }
+    return rewrittenSet;
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return fields.isEmpty();
+  }
+
+  @Override
   public int size() {
     return fields.size();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java
index c50ded8..73559f3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java
@@ -4,7 +4,9 @@
 
 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.GraphLense;
 
 public class EmptyFieldSet extends AbstractFieldSet implements KnownFieldSet {
 
@@ -37,6 +39,16 @@
   }
 
   @Override
+  public AbstractFieldSet rewrittenWithLens(AppView<?> appView, GraphLense lens) {
+    return this;
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return true;
+  }
+
+  @Override
   public int size() {
     return 0;
   }
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 c934275..af990b1 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
@@ -40,6 +40,8 @@
   private DominatorTree dominatorTree;
   private Map<BasicBlock, AbstractFieldSet> fieldsMaybeReadBeforeBlockInclusiveCache;
 
+  final Map<DexEncodedField, LinkedList<FieldInstruction>> putsPerField = new IdentityHashMap<>();
+
   FieldValueAnalysis(
       AppView<AppInfoWithLiveness> appView,
       IRCode code,
@@ -78,7 +80,6 @@
     // Find all the static-put instructions that assign a field in the enclosing class which is
     // guaranteed to be assigned only in the current initializer.
     boolean isStraightLineCode = true;
-    Map<DexEncodedField, LinkedList<FieldInstruction>> putsPerField = new IdentityHashMap<>();
     for (BasicBlock block : code.blocks) {
       if (block.getSuccessors().size() >= 2) {
         isStraightLineCode = false;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index f25c566..10dd287 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.analysis.value.SingleEnumValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.ArrayPut;
@@ -55,6 +56,36 @@
   }
 
   @Override
+  void computeFieldOptimizationInfo(ClassInitializerDefaultsResult classInitializerDefaultsResult) {
+    super.computeFieldOptimizationInfo(classInitializerDefaultsResult);
+
+    classInitializerDefaultsResult.forEachOptimizedField(
+        (field, value) -> {
+          if (putsPerField.containsKey(field)
+              || !appView.appInfo().isFieldOnlyWrittenInMethod(field, method)) {
+            return;
+          }
+
+          AbstractValueFactory factory = appView.abstractValueFactory();
+          if (value.isDexValueNumber()) {
+            feedback.recordFieldHasAbstractValue(
+                field,
+                appView,
+                factory.createSingleNumberValue(value.asDexValueNumber().getRawValue()));
+          } else if (value.isDexValueString()) {
+            feedback.recordFieldHasAbstractValue(
+                field,
+                appView,
+                factory.createSingleStringValue(value.asDexValueString().getValue()));
+          } else if (value.isDexItemBasedValueString()) {
+            // TODO(b/150835624): Extend to dex item based const strings.
+          } else {
+            assert false : value.getClass().getName();
+          }
+        });
+  }
+
+  @Override
   boolean isSubjectToOptimization(DexEncodedField field) {
     return field.isStatic()
         && field.holder() == clazz.type
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java
index 9df87cc..f083438 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java
@@ -4,7 +4,9 @@
 
 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.GraphLense;
 
 public class UnknownFieldSet extends AbstractFieldSet {
 
@@ -22,7 +24,17 @@
   }
 
   @Override
+  public boolean isEmpty() {
+    return false;
+  }
+
+  @Override
   public boolean isTop() {
     return true;
   }
+
+  @Override
+  public AbstractFieldSet rewrittenWithLens(AppView<?> appView, GraphLense lens) {
+    return this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java b/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java
index 57f8d2e..34ab168 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java
@@ -36,7 +36,7 @@
     // Modeling of other library methods.
     DexType holder = invokedMethod.holder;
     if (holder == dexItemFactory.objectType) {
-      if (invokedMethod == dexItemFactory.objectMethods.constructor) {
+      if (invokedMethod == dexItemFactory.objectMembers.constructor) {
         return EmptyFieldSet.getInstance();
       }
     }
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 717df30..8da13ed 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
@@ -11,36 +11,51 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 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.CheckCast;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.IntSwitch;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
+import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer;
 import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.ir.optimize.inliner.FixedInliningReasonStrategy;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.PredicateSet;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.function.BooleanSupplier;
 
-// TODO(b/112437944): Should remove the new Builder() instructions from each dynamicMethod() that
-//  references a dead proto builder.
 public class GeneratedMessageLiteBuilderShrinker {
 
   private final AppView<? extends AppInfoWithSubtyping> appView;
   private final ProtoReferences references;
 
+  private final Map<DexProgramClass, DexEncodedMethod> builders = new IdentityHashMap<>();
+
   GeneratedMessageLiteBuilderShrinker(
       AppView<? extends AppInfoWithSubtyping> appView, ProtoReferences references) {
     this.appView = appView;
@@ -51,24 +66,66 @@
   public boolean deferDeadProtoBuilders(
       DexProgramClass clazz, DexEncodedMethod context, BooleanSupplier register) {
     if (references.isDynamicMethod(context) && references.isGeneratedMessageLiteBuilder(clazz)) {
-      return register.getAsBoolean();
+      if (register.getAsBoolean()) {
+        assert builders.getOrDefault(clazz, context) == context;
+        builders.put(clazz, context);
+        return true;
+      }
     }
     return false;
   }
 
+  /**
+   * Reprocesses each dynamicMethod() that references a dead builder to remove the dead builder
+   * references.
+   */
+  public void removeDeadBuilderReferencesFromDynamicMethods(
+      AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
+      throws ExecutionException {
+    timing.begin("Remove dead builder references");
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    IRConverter converter = new IRConverter(appView, Timing.empty());
+    CodeRewriter codeRewriter = new CodeRewriter(appView, converter);
+    MethodToInvokeSwitchCaseAnalyzer switchCaseAnalyzer = new MethodToInvokeSwitchCaseAnalyzer();
+    if (switchCaseAnalyzer.isInitialized()) {
+      ThreadUtils.processItems(
+          builders.entrySet(),
+          entry -> {
+            if (!appInfo.isLiveProgramClass(entry.getKey())) {
+              removeDeadBuilderReferencesFromDynamicMethod(
+                  appView, entry.getValue(), converter, codeRewriter, switchCaseAnalyzer);
+            }
+          },
+          executorService);
+    }
+    builders.clear();
+    timing.end(); // Remove dead builder references
+  }
+
+  private void removeDeadBuilderReferencesFromDynamicMethod(
+      AppView<AppInfoWithLiveness> appView,
+      DexEncodedMethod dynamicMethod,
+      IRConverter converter,
+      CodeRewriter codeRewriter,
+      SwitchCaseAnalyzer switchCaseAnalyzer) {
+    Origin origin = appView.appInfo().originFor(dynamicMethod.holder());
+    IRCode code = dynamicMethod.buildIR(appView, origin);
+    codeRewriter.rewriteSwitch(code, switchCaseAnalyzer);
+    converter.removeDeadCodeAndFinalizeIR(
+        dynamicMethod, code, OptimizationFeedbackSimple.getInstance(), Timing.empty());
+  }
+
   public static void addInliningHeuristicsForBuilderInlining(
       AppView<? extends AppInfoWithSubtyping> appView,
       PredicateSet<DexType> alwaysClassInline,
       Set<DexType> neverMerge,
       Set<DexMethod> alwaysInline,
-      Set<DexMethod> neverInline,
       Set<DexMethod> bypassClinitforInlining) {
     new RootSetExtension(
             appView,
             alwaysClassInline,
             neverMerge,
             alwaysInline,
-            neverInline,
             bypassClinitforInlining)
         .extend();
   }
@@ -177,6 +234,47 @@
     }
   }
 
+  private class MethodToInvokeSwitchCaseAnalyzer extends SwitchCaseAnalyzer {
+
+    private final int newBuilderOrdinal;
+
+    private MethodToInvokeSwitchCaseAnalyzer() {
+      EnumValueInfoMap enumValueInfoMap =
+          appView.appInfo().withLiveness().getEnumValueInfoMap(references.methodToInvokeType);
+      if (enumValueInfoMap != null) {
+        EnumValueInfo newBuilderValueInfo =
+            enumValueInfoMap.getEnumValueInfo(references.methodToInvokeMembers.newBuilderField);
+        if (newBuilderValueInfo != null) {
+          newBuilderOrdinal = newBuilderValueInfo.ordinal;
+          return;
+        }
+      }
+      newBuilderOrdinal = -1;
+    }
+
+    public boolean isInitialized() {
+      return newBuilderOrdinal >= 0;
+    }
+
+    @Override
+    public boolean switchCaseIsUnreachable(IntSwitch theSwitch, int index) {
+      if (index != newBuilderOrdinal) {
+        return false;
+      }
+      Value switchValue = theSwitch.value();
+      if (!switchValue.isDefinedByInstructionSatisfying(Instruction::isInvokeVirtual)) {
+        return false;
+      }
+      InvokeVirtual definition = switchValue.definition.asInvokeVirtual();
+      if (definition.getInvokedMethod() != appView.dexItemFactory().enumMethods.ordinal) {
+        return false;
+      }
+      TypeLatticeElement enumType = definition.getReceiver().getTypeLattice();
+      return enumType.isClassType()
+          && enumType.asClassTypeLatticeElement().getClassType() == references.methodToInvokeType;
+    }
+  }
+
   private static class RootSetExtension {
 
     private final AppView<? extends AppInfoWithSubtyping> appView;
@@ -186,7 +284,6 @@
     private final Set<DexType> neverMerge;
 
     private final Set<DexMethod> alwaysInline;
-    private final Set<DexMethod> neverInline;
     private final Set<DexMethod> bypassClinitforInlining;
 
     RootSetExtension(
@@ -194,14 +291,12 @@
         PredicateSet<DexType> alwaysClassInline,
         Set<DexType> neverMerge,
         Set<DexMethod> alwaysInline,
-        Set<DexMethod> neverInline,
         Set<DexMethod> bypassClinitforInlining) {
       this.appView = appView;
       this.references = appView.protoShrinker().references;
       this.alwaysClassInline = alwaysClassInline;
       this.neverMerge = neverMerge;
       this.alwaysInline = alwaysInline;
-      this.neverInline = neverInline;
       this.bypassClinitforInlining = bypassClinitforInlining;
     }
 
@@ -210,7 +305,6 @@
 
       // GeneratedMessageLite heuristics.
       alwaysInlineCreateBuilderFromGeneratedMessageLite();
-      neverInlineIsInitializedFromGeneratedMessageLite();
 
       // * extends GeneratedMessageLite heuristics.
       bypassClinitforInliningNewBuilderMethods();
@@ -257,13 +351,5 @@
       neverMerge.add(references.generatedMessageLiteBuilderType);
       neverMerge.add(references.generatedMessageLiteExtendableBuilderType);
     }
-
-    /**
-     * Without this rule, GeneratedMessageLite$Builder.build() becomes too big for class inlining.
-     * TODO(b/112437944): Maybe introduce a -neverinlineinto rule instead?
-     */
-    private void neverInlineIsInitializedFromGeneratedMessageLite() {
-      neverInline.add(references.generatedMessageLiteMethods.isInitializedMethod);
-    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index 4ff6283..bf6c8b1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -32,6 +33,7 @@
 import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
+import com.android.tools.r8.shaking.InstantiationReason;
 import com.android.tools.r8.shaking.KeepReason;
 import com.android.tools.r8.utils.BitUtils;
 import com.android.tools.r8.utils.OptionalBool;
@@ -89,6 +91,34 @@
     this.references = protoShrinker.references;
   }
 
+  @Override
+  public void processNewlyLiveClass(DexProgramClass clazz, EnqueuerWorklist worklist) {
+    assert appView.appInfo().hasClassHierarchy();
+    AppInfoWithClassHierarchy appInfo = appView.appInfo().withClassHierarchy();
+    if (appInfo.isStrictSubtypeOf(clazz.type, references.generatedMessageLiteType)) {
+      markGeneratedMessageLiteSubtypeAsInstantiated(clazz, worklist);
+    }
+  }
+
+  private void markGeneratedMessageLiteSubtypeAsInstantiated(
+      DexProgramClass clazz, EnqueuerWorklist worklist) {
+    if (clazz.isAbstract()) {
+      assert clazz.type == references.extendableMessageType;
+      return;
+    }
+    DexEncodedMethod dynamicMethod = clazz.lookupVirtualMethod(references::isDynamicMethod);
+    if (dynamicMethod != null) {
+      worklist.enqueueMarkInstantiatedAction(
+          clazz,
+          dynamicMethod,
+          InstantiationReason.REFLECTION,
+          KeepReason.reflectiveUseIn(dynamicMethod));
+    } else {
+      assert false
+          : "Expected class `" + clazz.type.toSourceString() + "` to declare a dynamicMethod()";
+    }
+  }
+
   /**
    * When a dynamicMethod() of a proto message becomes live, then build the corresponding {@link
    * ProtoMessageInfo} object, and create a mapping from the holder to it.
@@ -372,6 +402,7 @@
             newlyLiveField = protoFieldInfo.getOneOfCaseField(appView, protoMessageInfo);
           } else if (protoFieldInfo.hasHazzerBitField(protoMessageInfo)) {
             newlyLiveField = protoFieldInfo.getHazzerBitField(appView, protoMessageInfo);
+            enqueuer.registerReflectiveFieldAccess(valueStorage.field, dynamicMethod);
           }
         } else {
           // For one-of fields, mark the one-of field as live if the one-of-case field is live, and
@@ -556,8 +587,10 @@
     DexType baseMessageType = protoFieldInfo.getBaseMessageType(factory);
     if (baseMessageType != null) {
       ProtoMessageInfo protoMessageInfo = getOrCreateProtoMessageInfo(baseMessageType);
-      assert protoMessageInfo != null;
-      return reachesMapOrRequiredField(protoMessageInfo);
+      if (protoMessageInfo != null) {
+        return reachesMapOrRequiredField(protoMessageInfo);
+      }
+      assert false : "Unable to find proto message info for `" + baseMessageType + "`";
     }
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeUtils.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeUtils.java
new file mode 100644
index 0000000..912b379
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeUtils.java
@@ -0,0 +1,15 @@
+// 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.type;
+
+import com.android.tools.r8.graph.AppView;
+
+public class TypeUtils {
+
+  public static boolean isNullPointerException(TypeLatticeElement type, AppView<?> appView) {
+    return type.isClassType()
+        && type.asClassTypeLatticeElement().getClassType() == appView.dexItemFactory().npeType;
+  }
+}
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 5371255..404faff 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
@@ -4,6 +4,10 @@
 
 package com.android.tools.r8.ir.analysis.value;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
 public abstract class AbstractValue {
 
   public abstract boolean isNonTrivial();
@@ -28,6 +32,10 @@
     return null;
   }
 
+  public boolean isSingleConstValue() {
+    return false;
+  }
+
   public boolean isSingleConstClassValue() {
     return false;
   }
@@ -85,6 +93,9 @@
     return UnknownValue.getInstance();
   }
 
+  public abstract AbstractValue rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLense lens);
+
   @Override
   public abstract boolean equals(Object o);
 
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
index ca376e7..f5b27f7 100644
--- 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
@@ -4,6 +4,10 @@
 
 package com.android.tools.r8.ir.analysis.value;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
 public class BottomValue extends AbstractValue {
 
   private static final BottomValue INSTANCE = new BottomValue();
@@ -20,6 +24,11 @@
   }
 
   @Override
+  public AbstractValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLense lens) {
+    return this;
+  }
+
+  @Override
   public boolean isNonTrivial() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
index a6bf499..897b31e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
@@ -13,14 +13,16 @@
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
-public class SingleConstClassValue extends SingleValue {
+public class SingleConstClassValue extends SingleConstValue {
 
   private final DexType type;
 
@@ -88,4 +90,21 @@
     assert baseType.isPrimitiveType();
     return true;
   }
+
+  @Override
+  public boolean isMaterializableInAllContexts(AppView<?> appView) {
+    DexType baseType = type.toBaseType(appView.dexItemFactory());
+    if (baseType.isClassType()) {
+      DexClass clazz = appView.definitionFor(type);
+      return clazz != null && clazz.isPublic() && clazz.isResolvable(appView);
+    }
+    assert baseType.isPrimitiveType();
+    return true;
+  }
+
+  @Override
+  public SingleValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLense lens) {
+    assert lens.lookupType(type) == type;
+    return this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstValue.java
new file mode 100644
index 0000000..ecf9838
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstValue.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+public abstract class SingleConstValue extends SingleValue {
+
+  @Override
+  public boolean isSingleConstValue() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java
index 313f977..4beb525 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java
@@ -4,7 +4,11 @@
 
 package com.android.tools.r8.ir.analysis.value;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public class SingleEnumValue extends SingleFieldValue {
 
@@ -27,4 +31,18 @@
   public String toString() {
     return "SingleEnumValue(" + getField().toSourceString() + ")";
   }
+
+  @Override
+  public SingleValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLense lens) {
+    DexField field = getField();
+    EnumValueInfoMap unboxedEnumInfo = appView.unboxedEnums().getEnumValueInfoMap(field.type);
+    if (unboxedEnumInfo != null) {
+      // Return the ordinal of the unboxed enum.
+      assert unboxedEnumInfo.hasEnumValueInfo(field);
+      return appView
+          .abstractValueFactory()
+          .createSingleNumberValue(unboxedEnumInfo.getEnumValueInfo(field).convertToInt());
+    }
+    return appView.abstractValueFactory().createSingleEnumValue(lens.lookupField(field));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
index 0adfe65..dffd9e0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -9,15 +9,19 @@
 
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public class SingleFieldValue extends SingleValue {
 
@@ -32,6 +36,18 @@
     return field;
   }
 
+  public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(AppView<AppInfoWithLiveness> appView) {
+    DexType fieldType = field.type;
+    if (fieldType.isClassType()) {
+      ClassTypeLatticeElement fieldClassType =
+          TypeLatticeElement.fromDexType(fieldType, maybeNull(), appView)
+              .asClassTypeLatticeElement();
+      return appView.appInfo().mayHaveFinalizeMethodDirectlyOrIndirectly(fieldClassType);
+    }
+    assert fieldType.isArrayType() || fieldType.isPrimitiveType();
+    return false;
+  }
+
   @Override
   public boolean isSingleFieldValue() {
     return true;
@@ -72,4 +88,30 @@
     return isMemberVisibleFromOriginalContext(
         appView, context, encodedField.field.holder, encodedField.accessFlags);
   }
+
+  @Override
+  public boolean isMaterializableInAllContexts(AppView<?> appView) {
+    DexEncodedField encodedField = appView.appInfo().resolveField(field);
+    if (encodedField == null) {
+      assert false;
+      return false;
+    }
+    if (!encodedField.isPublic()) {
+      return false;
+    }
+    DexClass holder = appView.definitionFor(encodedField.holder());
+    if (holder == null) {
+      assert false;
+      return false;
+    }
+    return holder.isPublic();
+  }
+
+  @Override
+  public SingleValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLense lens) {
+    DexField rewrittenField = lens.lookupField(field);
+    assert !appView.unboxedEnums().containsEnum(field.holder)
+        || !appView.definitionFor(rewrittenField).accessFlags.isEnum();
+    return appView.abstractValueFactory().createSingleFieldValue(rewrittenField);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
index 4a5ad60..b408c90 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
@@ -8,14 +8,16 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
-public class SingleNumberValue extends SingleValue {
+public class SingleNumberValue extends SingleConstValue {
 
   private final long value;
 
@@ -79,4 +81,14 @@
   public boolean isMaterializableInContext(AppView<?> appView, DexType context) {
     return true;
   }
+
+  @Override
+  public boolean isMaterializableInAllContexts(AppView<?> appView) {
+    return true;
+  }
+
+  @Override
+  public SingleValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLense lens) {
+    return this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
index 417fe19..a7439c0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstString;
@@ -19,8 +20,9 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
-public class SingleStringValue extends SingleValue {
+public class SingleStringValue extends SingleConstValue {
 
   private final DexString string;
 
@@ -84,4 +86,14 @@
   public boolean isMaterializableInContext(AppView<?> appView, DexType context) {
     return true;
   }
+
+  @Override
+  public boolean isMaterializableInAllContexts(AppView<?> appView) {
+    return true;
+  }
+
+  @Override
+  public SingleValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLense lens) {
+    return this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
index c7b249f..1b3e59a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
@@ -7,10 +7,12 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public abstract class SingleValue extends AbstractValue implements InstanceFieldInitializationInfo {
 
@@ -37,4 +39,10 @@
       AppView<? extends AppInfoWithSubtyping> appView, IRCode code, TypeAndLocalInfoSupplier info);
 
   public abstract boolean isMaterializableInContext(AppView<?> appView, DexType context);
+
+  public abstract boolean isMaterializableInAllContexts(AppView<?> appView);
+
+  @Override
+  public abstract SingleValue rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLense lens);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java
index 216ea99..2b4f841 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java
@@ -4,6 +4,10 @@
 
 package com.android.tools.r8.ir.analysis.value;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
 public class UnknownValue extends AbstractValue {
 
   private static final UnknownValue INSTANCE = new UnknownValue();
@@ -25,6 +29,11 @@
   }
 
   @Override
+  public AbstractValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLense lens) {
+    return this;
+  }
+
+  @Override
   public boolean equals(Object o) {
     return this == o;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index 0c118fb..17dcc5e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -251,7 +251,7 @@
   }
 
   @Override
-  public boolean throwsNpeIfValueIsNull(Value value, DexItemFactory dexItemFactory) {
+  public boolean throwsNpeIfValueIsNull(Value value, AppView<?> appView, DexType context) {
     return array() == value;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index b2f908c..12ff943 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.cf.code.CfArrayLength;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
@@ -140,7 +139,7 @@
   }
 
   @Override
-  public boolean throwsNpeIfValueIsNull(Value value, DexItemFactory dexItemFactory) {
+  public boolean throwsNpeIfValueIsNull(Value value, AppView<?> appView, DexType context) {
     return array() == value;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index b8d8526..8ab298a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.ArrayTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
@@ -252,7 +251,7 @@
   }
 
   @Override
-  public boolean throwsNpeIfValueIsNull(Value value, DexItemFactory dexItemFactory) {
+  public boolean throwsNpeIfValueIsNull(Value value, AppView<?> appView, DexType context) {
     return array() == value;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index abc0440..c28bc4a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -4,6 +4,9 @@
 
 package com.android.tools.r8.ir.code;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isClassTypeVisibleFromContext;
+
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.cf.code.CfCheckCast;
@@ -11,12 +14,15 @@
 import com.android.tools.r8.code.MoveObjectFrom16;
 import com.android.tools.r8.dex.Constants;
 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.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public class CheckCast extends Instruction {
 
@@ -89,6 +95,44 @@
   }
 
   @Override
+  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+    return instructionInstanceCanThrow(appView, context).isThrowing();
+  }
+
+  @Override
+  public AbstractError instructionInstanceCanThrow(AppView<?> appView, DexType context) {
+    if (appView.options().debug || !appView.appInfo().hasLiveness()) {
+      return AbstractError.top();
+    }
+    if (type.isPrimitiveType()) {
+      return AbstractError.top();
+    }
+    DexType baseType = type.toBaseType(appView.dexItemFactory());
+    if (baseType.isClassType()) {
+      DexClass dexClass = appView.definitionFor(baseType);
+      // * NoClassDefFoundError (resolution failure).
+      if (dexClass == null || !dexClass.isResolvable(appView)) {
+        return AbstractError.specific(appView.dexItemFactory().noClassDefFoundErrorType);
+      }
+      // * IllegalAccessError (not visible from the access context).
+      if (!isClassTypeVisibleFromContext(appView, context, dexClass)) {
+        return AbstractError.specific(appView.dexItemFactory().illegalAccessErrorType);
+      }
+    }
+    AppView<? extends AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+    TypeLatticeElement castType =
+        TypeLatticeElement.fromDexType(type, definitelyNotNull(), appView);
+    if (object()
+        .getDynamicUpperBoundType(appViewWithLiveness)
+        .lessThanOrEqualUpToNullability(castType, appView)) {
+      // This is a check-cast that has to be there for bytecode correctness, but R8 has proven
+      // that this cast will never throw.
+      return AbstractError.bottom();
+    }
+    return AbstractError.top();
+  }
+
+  @Override
   public boolean instructionTypeCanThrow() {
     return true;
   }
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 69c6281..366f0cf 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
@@ -19,6 +19,7 @@
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
@@ -29,8 +30,13 @@
 
   public enum Assumption {
     NONE,
+    CLASS_ALREADY_INITIALIZED,
     RECEIVER_NOT_NULL;
 
+    boolean canAssumeClassIsAlreadyInitialized() {
+      return this == CLASS_ALREADY_INITIALIZED;
+    }
+
     boolean canAssumeReceiverIsNotNull() {
       return this == RECEIVER_NOT_NULL;
     }
@@ -132,7 +138,8 @@
     if (!appView.enableWholeProgramOptimizations()) {
       return AbstractError.bottom();
     }
-    boolean mayTriggerClassInitialization = isStaticGet() || isStaticPut();
+    boolean mayTriggerClassInitialization =
+        isStaticFieldInstruction() && !assumption.canAssumeClassIsAlreadyInitialized();
     if (mayTriggerClassInitialization) {
       // Only check for <clinit> side effects if there is no -assumenosideeffects rule.
       if (appView.appInfo().hasLiveness()) {
@@ -192,34 +199,47 @@
    * default finalize() method in a field. In that case, it is not safe to remove this instruction,
    * since that could change the lifetime of the value.
    */
-  boolean isStoringObjectWithFinalizer(AppInfoWithLiveness appInfo) {
+  boolean isStoringObjectWithFinalizer(
+      AppView<AppInfoWithLiveness> appView, DexEncodedField field) {
     assert isFieldPut();
+
     TypeLatticeElement type = value().getTypeLattice();
     TypeLatticeElement baseType =
         type.isArrayType() ? type.asArrayTypeLatticeElement().getArrayBaseTypeLattice() : type;
-    if (baseType.isClassType()) {
-      Value root = value().getAliasedValue();
-      if (!root.isPhi() && root.definition.isNewInstance()) {
-        DexClass clazz = appInfo.definitionFor(root.definition.asNewInstance().clazz);
-        if (clazz == null) {
-          return true;
-        }
-        if (clazz.superType == null) {
-          return false;
-        }
-        DexItemFactory dexItemFactory = appInfo.dexItemFactory();
-        DexEncodedMethod resolutionResult =
-            appInfo
-                .resolveMethod(clazz.type, dexItemFactory.objectMethods.finalize)
-                .getSingleTarget();
-        return resolutionResult != null && resolutionResult.isProgramMethod(appInfo);
-      }
-
-      return appInfo.mayHaveFinalizeMethodDirectlyOrIndirectly(
-          baseType.asClassTypeLatticeElement());
+    if (!baseType.isClassType()) {
+      return false;
     }
 
-    return false;
+    AbstractValue abstractValue = field.getOptimizationInfo().getAbstractValue();
+    if (abstractValue.isSingleValue()) {
+      if (abstractValue.isZero()) {
+        return false;
+      }
+      if (abstractValue.isSingleFieldValue()) {
+        SingleFieldValue singleFieldValue = abstractValue.asSingleFieldValue();
+        return singleFieldValue.mayHaveFinalizeMethodDirectlyOrIndirectly(appView);
+      }
+    }
+
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    Value root = value().getAliasedValue();
+    if (!root.isPhi() && root.definition.isNewInstance()) {
+      DexClass clazz = appView.definitionFor(root.definition.asNewInstance().clazz);
+      if (clazz == null) {
+        return true;
+      }
+      if (clazz.superType == null) {
+        return false;
+      }
+      DexItemFactory dexItemFactory = appView.dexItemFactory();
+      DexEncodedMethod resolutionResult =
+          appInfo
+              .resolveMethod(clazz.type, dexItemFactory.objectMembers.finalize)
+              .getSingleTarget();
+      return resolutionResult != null && resolutionResult.isProgramMethod(appView);
+    }
+
+    return appInfo.mayHaveFinalizeMethodDirectlyOrIndirectly(baseType.asClassTypeLatticeElement());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index ff88eed..7f8d5d3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -50,6 +50,11 @@
   }
 
   @Override
+  public void removeInstructionIgnoreOutValue() {
+    instructionIterator.removeInstructionIgnoreOutValue();
+  }
+
+  @Override
   public void replaceCurrentInstructionWithThrowNull(
       AppView<? extends AppInfoWithSubtyping> appView,
       IRCode code,
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 8db09e9..5c488c4 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
@@ -65,6 +65,10 @@
     return get(Opcodes.AND);
   }
 
+  public boolean mayHaveArrayLength() {
+    return get(Opcodes.ARRAY_LENGTH);
+  }
+
   public boolean mayHaveCheckCast() {
     return get(Opcodes.CHECK_CAST);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java
new file mode 100644
index 0000000..94bfdaf
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java
@@ -0,0 +1,30 @@
+// 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.code;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.FieldInstruction.Assumption;
+
+public interface InstanceFieldInstruction {
+
+  boolean hasOutValue();
+
+  Value outValue();
+
+  Value object();
+
+  boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context, Assumption assumption);
+
+  FieldInstruction asFieldInstruction();
+
+  boolean isInstanceGet();
+
+  InstanceGet asInstanceGet();
+
+  boolean isInstancePut();
+
+  InstancePut asInstancePut();
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 2ca1b2a..b070660 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
@@ -31,7 +30,7 @@
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.Set;
 
-public class InstanceGet extends FieldInstruction {
+public class InstanceGet extends FieldInstruction implements InstanceFieldInstruction {
 
   public InstanceGet(Value dest, Value object, DexField field) {
     super(field, dest, object);
@@ -56,6 +55,7 @@
     return outValue;
   }
 
+  @Override
   public Value object() {
     assert inValues.size() == 1;
     return inValues.get(0);
@@ -119,6 +119,7 @@
     return instructionMayHaveSideEffects(appView, context, Assumption.NONE);
   }
 
+  @Override
   public boolean instructionMayHaveSideEffects(
       AppView<?> appView, DexType context, Assumption assumption) {
     return instructionInstanceCanThrow(appView, context, assumption).isThrowing();
@@ -198,7 +199,7 @@
   }
 
   @Override
-  public boolean throwsNpeIfValueIsNull(Value value, DexItemFactory dexItemFactory) {
+  public boolean throwsNpeIfValueIsNull(Value value, AppView<?> appView, DexType context) {
     return object() == value;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index cd71750..00a5f30 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
@@ -31,7 +30,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Arrays;
 
-public class InstancePut extends FieldInstruction {
+public class InstancePut extends FieldInstruction implements InstanceFieldInstruction {
 
   public InstancePut(DexField field, Value object, Value value) {
     this(field, object, value, false);
@@ -63,6 +62,7 @@
     return visitor.visit(this);
   }
 
+  @Override
   public Value object() {
     return inValues.get(0);
   }
@@ -115,17 +115,24 @@
 
   @Override
   public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
-    if (appView.appInfo().hasLiveness()) {
-      AppInfoWithLiveness appInfoWithLiveness = appView.appInfo().withLiveness();
+    return instructionMayHaveSideEffects(appView, context, Assumption.NONE);
+  }
 
-      if (instructionInstanceCanThrow(appView, context).isThrowing()) {
+  @Override
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, Assumption assumption) {
+    if (appView.appInfo().hasLiveness()) {
+      AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+      AppInfoWithLiveness appInfoWithLiveness = appViewWithLiveness.appInfo();
+
+      if (instructionInstanceCanThrow(appView, context, assumption).isThrowing()) {
         return true;
       }
 
       DexEncodedField encodedField = appInfoWithLiveness.resolveField(getField());
       assert encodedField != null : "NoSuchFieldError (resolution failure) should be caught.";
       return appInfoWithLiveness.isFieldRead(encodedField)
-          || isStoringObjectWithFinalizer(appInfoWithLiveness);
+          || isStoringObjectWithFinalizer(appViewWithLiveness, encodedField);
     }
 
     // In D8, we always have to assume that the field can be read, and thus have side effects.
@@ -220,7 +227,7 @@
   }
 
   @Override
-  public boolean throwsNpeIfValueIsNull(Value value, DexItemFactory dexItemFactory) {
+  public boolean throwsNpeIfValueIsNull(Value value, AppView<?> appView, DexType context) {
     return object() == value;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 8deda3f..b1459be 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
@@ -533,6 +532,29 @@
     return false;
   }
 
+  public boolean isBlockLocalInstructionWithoutSideEffects(AppView<?> appView, DexType context) {
+    return definesBlockLocalValue() && !instructionMayHaveSideEffects(appView, context);
+  }
+
+  private boolean definesBlockLocalValue() {
+    return !definesValueWithNonLocalUsages();
+  }
+
+  private boolean definesValueWithNonLocalUsages() {
+    if (hasOutValue()) {
+      Value outValue = outValue();
+      if (outValue.numberOfPhiUsers() > 0) {
+        return true;
+      }
+      for (Instruction user : outValue.uniqueUsers()) {
+        if (user.getBlock() != getBlock()) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
   public boolean instructionTypeCanBeCanonicalized() {
     return false;
   }
@@ -990,6 +1012,10 @@
     return null;
   }
 
+  public boolean isStaticFieldInstruction() {
+    return false;
+  }
+
   public boolean isStaticGet() {
     return false;
   }
@@ -1372,11 +1398,12 @@
    * given value is null at runtime execution.
    *
    * @param value the value representing an object that may be null at runtime execution.
-   * @param dexItemFactory where pre-defined descriptors are retrieved
+   * @param appView where pre-defined descriptors are retrieved
+   * @param context
    * @return true if the instruction throws NullPointerException if value is null at runtime, false
    *     otherwise.
    */
-  public boolean throwsNpeIfValueIsNull(Value value, DexItemFactory dexItemFactory) {
+  public boolean throwsNpeIfValueIsNull(Value value, AppView<?> appView, DexType context) {
     return false;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index db939e8..ec0c68f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -159,10 +159,6 @@
 
   @Override
   public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
-    if (!appView.enableWholeProgramOptimizations()) {
-      return true;
-    }
-
     if (appView.options().debug) {
       return true;
     }
@@ -181,42 +177,41 @@
     }
 
     // Find the target and check if the invoke may have side effects.
-    if (appView.appInfo().hasLiveness()) {
-      AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-      DexEncodedMethod target = lookupSingleTarget(appViewWithLiveness, context);
-      if (target == null) {
-        return true;
-      }
-
-      // Verify that the target method is accessible in the current context.
-      if (!isMemberVisibleFromOriginalContext(
-          appView, context, target.method.holder, target.accessFlags)) {
-        return true;
-      }
-
-      // Verify that the target method does not have side-effects.
-      DexClass clazz = appView.definitionFor(target.method.holder);
-      if (clazz == null) {
-        assert false : "Expected to be able to find the enclosing class of a method definition";
-        return true;
-      }
-
-      if (appViewWithLiveness.appInfo().noSideEffects.containsKey(target.method)) {
-        return false;
-      }
-
-      MethodOptimizationInfo optimizationInfo = target.getOptimizationInfo();
-      if (target.isInstanceInitializer()) {
-        InstanceInitializerInfo initializerInfo = optimizationInfo.getInstanceInitializerInfo();
-        if (!initializerInfo.mayHaveOtherSideEffectsThanInstanceFieldAssignments()) {
-          return false;
-        }
-      }
-
-      return optimizationInfo.mayHaveSideEffects();
+    if (!appView.appInfo().hasLiveness()) {
+      return true;
     }
 
-    return true;
+    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+    DexEncodedMethod target = lookupSingleTarget(appViewWithLiveness, context);
+    if (target == null) {
+      return true;
+    }
+
+    // Verify that the target method is accessible in the current context.
+    if (!isMemberVisibleFromOriginalContext(
+        appView, context, target.method.holder, target.accessFlags)) {
+      return true;
+    }
+
+    // Verify that the target method does not have side-effects.
+    DexClass clazz = appView.definitionFor(target.method.holder);
+    if (clazz == null) {
+      assert false : "Expected to be able to find the enclosing class of a method definition";
+      return true;
+    }
+
+    if (appViewWithLiveness.appInfo().noSideEffects.containsKey(target.method)) {
+      return false;
+    }
+
+    MethodOptimizationInfo optimizationInfo = target.getOptimizationInfo();
+    if (target.isInstanceInitializer()) {
+      InstanceInitializerInfo initializerInfo = optimizationInfo.getInstanceInitializerInfo();
+      if (!initializerInfo.mayHaveOtherSideEffectsThanInstanceFieldAssignments()) {
+        return false;
+      }
+    }
+    return optimizationInfo.mayHaveSideEffects();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
index 860080f..e9f3063 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
@@ -89,12 +89,15 @@
   public DexEncodedMethod lookupSingleTarget(AppView<?> appView, DexType invocationContext) {
     if (appView.appInfo().hasLiveness()) {
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-      AppInfoWithLiveness appInfo = appViewWithLiveness.appInfo();
-      return appInfo.lookupSingleInterfaceTarget(
-          getInvokedMethod(),
-          invocationContext,
-          TypeAnalysis.getRefinedReceiverType(appViewWithLiveness, this),
-          getReceiver().getDynamicLowerBoundType(appViewWithLiveness));
+      return appViewWithLiveness
+          .appInfo()
+          .lookupSingleVirtualTarget(
+              getInvokedMethod(),
+              invocationContext,
+              true,
+              appView,
+              TypeAnalysis.getRefinedReceiverType(appViewWithLiveness, this),
+              getReceiver().getDynamicLowerBoundType(appViewWithLiveness));
     }
     return null;
   }
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 df0c10d..401b4df 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
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -25,6 +26,7 @@
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
+import java.util.BitSet;
 import java.util.Collection;
 import java.util.List;
 import java.util.Set;
@@ -89,12 +91,17 @@
             .appInfo()
             .resolveMethod(method.holder, method)
             .lookupVirtualDispatchTargets(
-                appView.definitionForProgramType(invocationContext), appView.withLiveness())
+                appView.definitionForProgramType(invocationContext),
+                appView.withLiveness().appInfo())
             .asLookupResultSuccess();
-    if (lookupResult == null) {
+    if (lookupResult == null || lookupResult.isEmpty()) {
       return null;
     }
-    assert lookupResult.getMethodTargets() != null;
+    assert lookupResult.hasMethodTargets();
+    if (lookupResult.hasLambdaTargets()) {
+      // TODO(b/150277553): Support lambda targets.
+      return null;
+    }
     DexType staticReceiverType = getInvokedMethod().holder;
     DexType refinedReceiverType =
         TypeAnalysis.getRefinedReceiverType(
@@ -107,17 +114,30 @@
       if (refinedResolution.isSingleResolution()) {
         DexEncodedMethod refinedTarget = refinedResolution.getSingleTarget();
         Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
-        for (DexEncodedMethod target : lookupResult.getMethodTargets()) {
-          if (target == refinedTarget
-              || appView.isSubtype(target.method.holder, refinedReceiverType).isPossiblyTrue()) {
-            result.add(target);
-          }
-        }
+        lookupResult.forEach(
+            methodTarget -> {
+              DexEncodedMethod target = methodTarget.getMethod();
+              if (target == refinedTarget
+                  || appView
+                      .isSubtype(target.method.holder, refinedReceiverType)
+                      .isPossiblyTrue()) {
+                result.add(target);
+              }
+            },
+            lambdaTarget -> {
+              throw new Unreachable();
+            });
         return result;
       }
       // If resolution at the refined type fails, conservatively return the full set of targets.
     }
-    return lookupResult.getMethodTargets();
+    Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
+    lookupResult.forEach(
+        methodTarget -> result.add(methodTarget.getMethod()),
+        lambda -> {
+          assert false;
+        });
+    return result;
   }
 
   public abstract InlineAction computeInlining(
@@ -202,4 +222,17 @@
     assert lookupDirectTargetOnItself == hierarchyResult;
     return true;
   }
+
+  @Override
+  public boolean throwsNpeIfValueIsNull(Value value, AppView<?> appView, DexType context) {
+    DexEncodedMethod singleTarget = lookupSingleTarget(appView, context);
+    if (singleTarget != null) {
+      BitSet nonNullParamOrThrow = singleTarget.getOptimizationInfo().getNonNullParamOrThrow();
+      if (nonNullParamOrThrow != null) {
+        int argumentIndex = inValues.indexOf(value);
+        return argumentIndex >= 0 && nonNullParamOrThrow.get(argumentIndex);
+      }
+    }
+    return false;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index a42988e..e15a387 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
@@ -52,8 +51,8 @@
   }
 
   @Override
-  public boolean throwsNpeIfValueIsNull(Value value, DexItemFactory dexItemFactory) {
-    return getReceiver() == value;
+  public boolean throwsNpeIfValueIsNull(Value value, AppView<?> appView, DexType context) {
+    return value == getReceiver() || super.throwsNpeIfValueIsNull(value, appView, context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index e1f67a9..1ec8873 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -93,12 +93,15 @@
   public DexEncodedMethod lookupSingleTarget(AppView<?> appView, DexType invocationContext) {
     if (appView.appInfo().hasLiveness()) {
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-      AppInfoWithLiveness appInfo = appViewWithLiveness.appInfo();
-      return appInfo.lookupSingleVirtualTarget(
-          getInvokedMethod(),
-          invocationContext,
-          TypeAnalysis.getRefinedReceiverType(appViewWithLiveness, this),
-          getReceiver().getDynamicLowerBoundType(appViewWithLiveness));
+      return appViewWithLiveness
+          .appInfo()
+          .lookupSingleVirtualTarget(
+              getInvokedMethod(),
+              invocationContext,
+              false,
+              appView,
+              TypeAnalysis.getRefinedReceiverType(appViewWithLiveness, this),
+              getReceiver().getDynamicLowerBoundType(appViewWithLiveness));
     }
     // In D8, allow lookupSingleTarget() to be used for finding final library methods. This is used
     // for library modeling.
@@ -154,6 +157,11 @@
       return true;
     }
 
+    if (getInvokedMethod().holder.isArrayType()
+        && getInvokedMethod().match(appView.dexItemFactory().objectMembers.clone)) {
+      return false;
+    }
+
     // Check if it is a call to one of library methods that are known to be side-effect free.
     Predicate<InvokeMethod> noSideEffectsPredicate =
         appView.dexItemFactory().libraryMethodsWithoutSideEffects.get(getInvokedMethod());
diff --git a/src/main/java/com/android/tools/r8/ir/code/Monitor.java b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
index 78b9b64..598bc6c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Monitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.code.MonitorExit;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -142,7 +141,7 @@
   }
 
   @Override
-  public boolean throwsNpeIfValueIsNull(Value value, DexItemFactory dexItemFactory) {
+  public boolean throwsNpeIfValueIsNull(Value value, AppView<?> appView, DexType context) {
     return object() == value;
   }
 
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 a8fa713..23ac981 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
@@ -142,8 +142,10 @@
 
   @Override
   public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
     if (!appView.enableWholeProgramOptimizations()) {
-      return true;
+      return !(dexItemFactory.libraryTypesAssumedToBePresent.contains(clazz)
+          && dexItemFactory.libraryClassesWithoutStaticInitialization.contains(clazz));
     }
 
     if (clazz.isPrimitiveType() || clazz.isArrayType()) {
@@ -157,7 +159,7 @@
     }
 
     if (definition.isLibraryClass()
-        && !appView.dexItemFactory().libraryTypesAssumedToBePresent.contains(clazz)) {
+        && !dexItemFactory.libraryTypesAssumedToBePresent.contains(clazz)) {
       return true;
     }
 
@@ -178,13 +180,12 @@
     }
 
     // Verify that the object does not have a finalizer.
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
     ResolutionResult finalizeResolutionResult =
-        appView.appInfo().resolveMethod(clazz, dexItemFactory.objectMethods.finalize);
+        appView.appInfo().resolveMethod(clazz, dexItemFactory.objectMembers.finalize);
     if (finalizeResolutionResult.isSingleResolution()) {
       DexMethod finalizeMethod = finalizeResolutionResult.getSingleTarget().method;
       if (finalizeMethod != dexItemFactory.enumMethods.finalize
-          && finalizeMethod != dexItemFactory.objectMethods.finalize) {
+          && finalizeMethod != dexItemFactory.objectMembers.finalize) {
         return true;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java
new file mode 100644
index 0000000..9c5a123
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java
@@ -0,0 +1,30 @@
+// 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.code;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.FieldInstruction.Assumption;
+
+public interface StaticFieldInstruction {
+
+  boolean hasOutValue();
+
+  Value outValue();
+
+  boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context, Assumption assumption);
+
+  FieldInstruction asFieldInstruction();
+
+  boolean isStaticFieldInstruction();
+
+  boolean isStaticGet();
+
+  StaticGet asStaticGet();
+
+  boolean isStaticPut();
+
+  StaticPut asStaticPut();
+}
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 08285c5..e7c2dee 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
@@ -30,7 +30,7 @@
 import com.google.common.collect.Sets;
 import java.util.Set;
 
-public class StaticGet extends FieldInstruction {
+public class StaticGet extends FieldInstruction implements StaticFieldInstruction {
 
   public StaticGet(Value dest, DexField field) {
     super(field, dest, (Value) null);
@@ -137,7 +137,13 @@
 
   @Override
   public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
-    return instructionInstanceCanThrow(appView, context).isThrowing();
+    return instructionMayHaveSideEffects(appView, context, Assumption.NONE);
+  }
+
+  @Override
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, Assumption assumption) {
+    return instructionInstanceCanThrow(appView, context, assumption).isThrowing();
   }
 
   @Override
@@ -181,6 +187,11 @@
   }
 
   @Override
+  public boolean isStaticFieldInstruction() {
+    return true;
+  }
+
+  @Override
   public boolean isStaticGet() {
     return true;
   }
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 c9bab8b..9d25691 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
@@ -30,7 +30,7 @@
 import com.android.tools.r8.shaking.ProguardMemberRule;
 import com.google.common.collect.Sets;
 
-public class StaticPut extends FieldInstruction {
+public class StaticPut extends FieldInstruction implements StaticFieldInstruction {
 
   public StaticPut(Value source, DexField field) {
     super(field, null, source);
@@ -95,8 +95,15 @@
 
   @Override
   public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+    return instructionMayHaveSideEffects(appView, context, Assumption.NONE);
+  }
+
+  @Override
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, Assumption assumption) {
     if (appView.appInfo().hasLiveness()) {
-      AppInfoWithLiveness appInfoWithLiveness = appView.appInfo().withLiveness();
+      AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+      AppInfoWithLiveness appInfoWithLiveness = appViewWithLiveness.appInfo();
       // MemberValuePropagation will replace the field read only if the target field has bound
       // -assumevalues rule whose return value is *single*.
       //
@@ -107,7 +114,7 @@
         return false;
       }
 
-      if (instructionInstanceCanThrow(appView, context).isThrowing()) {
+      if (instructionInstanceCanThrow(appView, context, assumption).isThrowing()) {
         return true;
       }
 
@@ -122,7 +129,7 @@
       }
 
       return appInfoWithLiveness.isFieldRead(encodedField)
-          || isStoringObjectWithFinalizer(appInfoWithLiveness);
+          || isStoringObjectWithFinalizer(appViewWithLiveness, encodedField);
     }
 
     // In D8, we always have to assume that the field can be read, and thus have side effects.
@@ -195,6 +202,11 @@
   }
 
   @Override
+  public boolean isStaticFieldInstruction() {
+    return true;
+  }
+
+  @Override
   public boolean isStaticPut() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Throw.java b/src/main/java/com/android/tools/r8/ir/code/Throw.java
index 8a513c2..115e9fd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Throw.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Throw.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -87,7 +87,7 @@
   }
 
   @Override
-  public boolean throwsNpeIfValueIsNull(Value value, DexItemFactory dexItemFactory) {
+  public boolean throwsNpeIfValueIsNull(Value value, AppView<?> appView, DexType context) {
     if (exception() == value) {
       return true;
     }
@@ -105,7 +105,7 @@
     if (!aliasedValue.isPhi()) {
       Instruction definition = aliasedValue.getDefinition();
       if (definition.isNewInstance()
-          && definition.asNewInstance().clazz == dexItemFactory.npeType) {
+          && definition.asNewInstance().clazz == appView.dexItemFactory().npeType) {
         // throw new NullPointerException()
         return true;
       }
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 bdaa741..25ed39f 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
@@ -864,6 +864,10 @@
     return isConstant() && getConstInstruction().isConstNumber();
   }
 
+  public boolean isConstZero() {
+    return isConstNumber() && definition.asConstNumber().isZero();
+  }
+
   public boolean isConstString() {
     return isConstant() && getConstInstruction().isConstString();
   }
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 2bb3869..25ddb6a 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
@@ -34,6 +34,7 @@
 import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Deque;
+import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
@@ -173,7 +174,7 @@
         }
       } else {
         DexEncodedMethod singleTarget =
-            appView.appInfo().lookupSingleTarget(type, method, context.holder);
+            appView.appInfo().lookupSingleTarget(type, method, context.holder, appView);
         if (singleTarget != null) {
           assert !source.accessFlags.isBridge() || singleTarget != currentMethod.method;
           DexProgramClass clazz =
@@ -215,9 +216,18 @@
                 if (resolution.isVirtualTarget()) {
                   LookupResult lookupResult =
                       resolution.lookupVirtualDispatchTargets(
-                          appView.definitionForProgramType(context), appView);
+                          appView.definitionForProgramType(context), appView.appInfo());
                   if (lookupResult.isLookupResultSuccess()) {
-                    return lookupResult.asLookupResultSuccess().getMethodTargets();
+                    // TODO(b/150277553): Add lambda methods to the call graph.
+                    Set<DexEncodedMethod> targets = new HashSet<>();
+                    lookupResult
+                        .asLookupResultSuccess()
+                        .forEach(
+                            methodTarget -> targets.add(methodTarget.getMethod()),
+                            lambdaTarget -> {
+                              assert false;
+                            });
+                    return targets;
                   }
                 }
                 return null;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
index 99c2933..a6d6267 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
@@ -15,6 +15,8 @@
 
   void markFieldCannotBeKept(DexEncodedField field);
 
+  void markFieldAsDead(DexEncodedField field);
+
   void markFieldAsPropagated(DexEncodedField field);
 
   void markFieldHasDynamicLowerBoundType(DexEncodedField field, ClassTypeLatticeElement type);
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 47c2c79..f4e315b 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
@@ -27,6 +27,7 @@
 import com.android.tools.r8.ir.analysis.TypeChecker;
 import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
 import com.android.tools.r8.ir.analysis.fieldaccess.FieldAccessAnalysis;
+import com.android.tools.r8.ir.analysis.fieldaccess.TrivialFieldAccessReprocessor;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.InstanceFieldValueAnalysis;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValueAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
@@ -94,6 +95,7 @@
 import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
@@ -700,8 +702,11 @@
     }
     if (enumUnboxer != null) {
       enumUnboxer.finishAnalysis();
-      enumUnboxer.unboxEnums(postMethodProcessorBuilder);
+      enumUnboxer.unboxEnums(postMethodProcessorBuilder, executorService, feedback);
     }
+    new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
+        .run(executorService, feedback, timing);
+
     timing.begin("IR conversion phase 2");
     graphLenseForIR = appView.graphLense();
     PostMethodProcessor postMethodProcessor =
@@ -754,6 +759,7 @@
     generateDesugaredLibraryAPIWrappers(builder, executorService);
 
     if (serviceLoaderRewriter != null && serviceLoaderRewriter.getSynthesizedClass() != null) {
+      appView.appInfo().addSynthesizedClass(serviceLoaderRewriter.getSynthesizedClass());
       processSynthesizedServiceLoaderMethods(
           serviceLoaderRewriter.getSynthesizedClass(), executorService);
       builder.addSynthesizedClass(serviceLoaderRewriter.getSynthesizedClass(), true);
@@ -824,13 +830,8 @@
 
     // Check if what we've added to the application builder as synthesized classes are same as
     // what we've added and used through AppInfo.
-    assert appView
-            .appInfo()
-            .getSynthesizedClassesForSanityCheck()
-            .containsAll(builder.getSynthesizedClasses())
-        && builder
-            .getSynthesizedClasses()
-            .containsAll(appView.appInfo().getSynthesizedClassesForSanityCheck());
+    assert appView.appInfo().synthesizedClasses().containsAll(builder.getSynthesizedClasses())
+        && builder.getSynthesizedClasses().containsAll(appView.appInfo().synthesizedClasses());
     return builder.build();
   }
 
@@ -1066,18 +1067,10 @@
   private Timing rewriteCode(
       DexEncodedMethod method, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
     Origin origin = appView.appInfo().originFor(method.method.holder);
-    try {
-      return rewriteCodeInternal(method, feedback, methodProcessor, origin);
-    } catch (CompilationError e) {
-      // If rewriting throws a compilation error, attach the origin and method if missing.
-      throw e.withAdditionalOriginAndPositionInfo(origin, new MethodPosition(method.method));
-    } catch (NullPointerException e) {
-      throw new CompilationError(
-          "NullPointerException during IR Conversion",
-          e,
-          origin,
-          new MethodPosition(method.method));
-    }
+    return ExceptionUtils.withOriginAttachmentHandler(
+        origin,
+        new MethodPosition(method.method),
+        () -> rewriteCodeInternal(method, feedback, methodProcessor, origin));
   }
 
   private Timing rewriteCodeInternal(
@@ -1357,9 +1350,11 @@
       invertConditionalsForTesting(code);
     }
 
-    timing.begin("Rewrite throw NPE");
-    codeRewriter.rewriteThrowNullPointerException(code);
-    timing.end();
+    if (!isDebugMode) {
+      timing.begin("Rewrite throw NPE");
+      codeRewriter.rewriteThrowNullPointerException(code);
+      timing.end();
+    }
 
     timing.begin("Optimize class initializers");
     ClassInitializerDefaultsResult classInitializerDefaultsResult =
@@ -1673,18 +1668,27 @@
 
   private void markProcessed(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     // After all the optimizations have take place, we compute whether method should be inlined.
-    ConstraintWithTarget state;
-    if (!options.enableInlining
-        || inliner == null
-        || method.isClassInitializer()
-        || method.getOptimizationInfo().isReachabilitySensitive()) {
-      state = ConstraintWithTarget.NEVER;
-    } else {
-      state = inliner.computeInliningConstraint(code, method);
-    }
+    ConstraintWithTarget state =
+        shouldComputeInliningConstraint(method)
+            ? inliner.computeInliningConstraint(code, method)
+            : ConstraintWithTarget.NEVER;
     feedback.markProcessed(method, state);
   }
 
+  private boolean shouldComputeInliningConstraint(DexEncodedMethod method) {
+    if (!options.enableInlining || inliner == null) {
+      return false;
+    }
+    if (method.isClassInitializer() || method.getOptimizationInfo().isReachabilitySensitive()) {
+      return false;
+    }
+    if (appView.appInfo().hasLiveness()
+        && appView.appInfo().withLiveness().isPinned(method.method)) {
+      return false;
+    }
+    return true;
+  }
+
   private synchronized void updateHighestSortingStrings(DexEncodedMethod method) {
     DexString highestSortingReferencedString = method.getCode().asDexCode().highestSortingString;
     if (highestSortingReferencedString != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index 6052427..e742528 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -83,7 +83,7 @@
       }
     }
 
-    void put(Set<DexEncodedMethod> methodsToRevisit) {
+    public void put(Set<DexEncodedMethod> methodsToRevisit) {
       put(methodsToRevisit, defaultCodeOptimizations);
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
index b6a02df..41d1363 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
@@ -634,28 +634,29 @@
         if (numberOfInstructions == 1) {
           JumpInstruction exit = end.exit();
           if (exit.isIf()) {
-            return extendWithIf(toBeExtended, exit.asIf());
+            return extendWithIf(toBeExtended, exit.asIf(), block);
           }
           if (exit.isIntSwitch()) {
-            return extendWithSwitch(toBeExtended, exit.asIntSwitch());
+            return extendWithSwitch(toBeExtended, exit.asIntSwitch(), block);
           }
         }
         if (numberOfInstructions == 2) {
           Instruction entry = end.entry();
           Instruction exit = end.exit();
           if (entry.isConstNumber() && entry.outValue().onlyUsedInBlock(end) && exit.isIf()) {
-            return extendWithIf(toBeExtended, exit.asIf());
+            return extendWithIf(toBeExtended, exit.asIf(), block);
           }
         }
         // Not an extension of `toBeExtended`.
         return setFallthroughBlock(toBeExtended, block);
       }
 
-      private IdToTargetMapping extendWithIf(IdToTargetMapping toBeExtended, If theIf) {
+      private IdToTargetMapping extendWithIf(
+          IdToTargetMapping toBeExtended, If theIf, BasicBlock fallthroughBlock) {
         If.Type type = theIf.getType();
         if (type != If.Type.EQ && type != If.Type.NE) {
           // Not an extension of `toBeExtended`.
-          return setFallthroughBlock(toBeExtended, theIf.getBlock());
+          return setFallthroughBlock(toBeExtended, fallthroughBlock);
         }
 
         // Read the `id` value. This value is known to be a phi, so just look for a phi.
@@ -672,7 +673,7 @@
 
         if (idValue == null || (toBeExtended != null && idValue != toBeExtended.idValue)) {
           // Not an extension of `toBeExtended`.
-          return setFallthroughBlock(toBeExtended, theIf.getBlock());
+          return setFallthroughBlock(toBeExtended, fallthroughBlock);
         }
 
         // Now read the constant value that `id` is being compared to in this if-instruction.
@@ -684,7 +685,7 @@
           Value root = other.getAliasedValue();
           if (root.isPhi() || !root.definition.isConstNumber()) {
             // Not an extension of `toBeExtended`.
-            return setFallthroughBlock(toBeExtended, theIf.getBlock());
+            return setFallthroughBlock(toBeExtended, fallthroughBlock);
           }
           ConstNumber constNumberInstruction = root.definition.asConstNumber();
           id = constNumberInstruction.getIntValue();
@@ -701,11 +702,11 @@
       }
 
       private IdToTargetMapping extendWithSwitch(
-          IdToTargetMapping toBeExtended, IntSwitch theSwitch) {
+          IdToTargetMapping toBeExtended, IntSwitch theSwitch, BasicBlock fallthroughBlock) {
         Value switchValue = theSwitch.value();
         if (!switchValue.isPhi() || (toBeExtended != null && switchValue != toBeExtended.idValue)) {
           // Not an extension of `toBeExtended`.
-          return setFallthroughBlock(toBeExtended, theSwitch.getBlock());
+          return setFallthroughBlock(toBeExtended, fallthroughBlock);
         }
 
         Phi idValue = switchValue.asPhi();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
index 41f22a4..2e8bf77 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
@@ -77,6 +77,8 @@
       BasicBlock block,
       StringSwitch theSwitch,
       Set<BasicBlock> newBlocks) {
+    int nextBlockNumber = code.getHighestBlockNumber() + 1;
+
     BasicBlock fallthroughBlock = theSwitch.fallthroughBlock();
     Map<DexString, BasicBlock> stringToTargetMap = new IdentityHashMap<>();
     theSwitch.forEachCase(stringToTargetMap::put);
@@ -118,7 +120,7 @@
       if (blocksTargetedByMultipleSwitchCases.contains(targetBlock)) {
         // Need an intermediate block to avoid critical edges.
         BasicBlock intermediateBlock =
-            BasicBlock.createGotoBlock(code.blocks.size(), Position.none(), code.metadata());
+            BasicBlock.createGotoBlock(nextBlockNumber++, Position.none(), code.metadata());
         intermediateBlock.link(targetBlock);
         blockIterator.add(intermediateBlock);
         newBlocks.add(intermediateBlock);
@@ -127,7 +129,7 @@
 
       BasicBlock newBlock =
           BasicBlock.createIfBlock(
-              code.blocks.size(),
+              nextBlockNumber++,
               ifInstruction,
               code.metadata(),
               constStringInstruction,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index 069015f..3527f62 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -20,7 +20,6 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
@@ -120,12 +119,6 @@
     this.vivifiedSourceFile = appView.dexItemFactory().createString("vivified");
   }
 
-  public static boolean isSynthesizedWrapper(DexType type) {
-    // Slow path, but more convenient since no instance is needed. Use hasSynthesized(DexType) when
-    // possible.
-    return type.descriptor.toString().startsWith("L" + WRAPPER_PREFIX);
-  }
-
   boolean hasSynthesized(DexType type) {
     return type.descriptor.startsWith(dexWrapperPrefix);
   }
@@ -139,10 +132,14 @@
   }
 
   DexType getTypeWrapper(DexType type) {
+    // Force create the reverse wrapper.
+    getWrapper(type, VIVIFIED_TYPE_WRAPPER_SUFFIX, vivifiedTypeWrappers);
     return getWrapper(type, TYPE_WRAPPER_SUFFIX, typeWrappers);
   }
 
   DexType getVivifiedTypeWrapper(DexType type) {
+    // Force create the reverse wrapper.
+    getWrapper(type, TYPE_WRAPPER_SUFFIX, typeWrappers);
     return getWrapper(type, VIVIFIED_TYPE_WRAPPER_SUFFIX, vivifiedTypeWrappers);
   }
 
@@ -249,25 +246,10 @@
         new DexEncodedMethod[] {synthesizeConstructor(wrapperField.field), conversionMethod},
         virtualMethods,
         factory.getSkipNameValidationForTesting(),
-        getChecksumSupplier(this, clazz.type),
+        DexProgramClass::checksumFromType,
         Collections.emptyList());
   }
 
-  private ChecksumSupplier getChecksumSupplier(
-      DesugaredLibraryWrapperSynthesizer synthesizer, DexType keyType) {
-    return clazz -> {
-      // The synthesized type wrappers are constructed lazily, so their lookup must be delayed
-      // until the point the checksum is requested (at write time). The presence of a wrapper
-      // affects the implementation of the conversion functions, so they must be accounted for in
-      // the checksum.
-      boolean hasWrapper = synthesizer.typeWrappers.containsKey(keyType);
-      boolean hasViviWrapper = synthesizer.vivifiedTypeWrappers.containsKey(keyType);
-      return ((long) clazz.type.hashCode())
-          + 7 * (long) Boolean.hashCode(hasWrapper)
-          + 11 * (long) Boolean.hashCode(hasViviWrapper);
-    };
-  }
-
   private DexEncodedMethod[] synthesizeVirtualMethodsForVivifiedTypeWrapper(
       DexClass dexClass, DexEncodedField wrapperField) {
     List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
@@ -284,7 +266,14 @@
     Set<DexMethod> finalMethods = Sets.newIdentityHashSet();
     for (DexEncodedMethod dexEncodedMethod : dexMethods) {
       DexClass holderClass = appView.definitionFor(dexEncodedMethod.method.holder);
-      assert holderClass != null;
+      boolean isInterface;
+      if (holderClass == null) {
+        assert appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface()
+            .containsValue(dexEncodedMethod.method.holder);
+        isInterface = true;
+      } else {
+        isInterface = holderClass.isInterface();
+      }
       DexMethod methodToInstall =
           factory.createMethod(
               wrapperField.field.holder,
@@ -302,7 +291,7 @@
                     methodToInstall,
                     wrapperField.field,
                     converter,
-                    holderClass.isInterface())
+                isInterface)
                 .generateCfCode();
       }
       DexEncodedMethod newDexEncodedMethod =
@@ -585,23 +574,25 @@
 
   private DexEncodedMethod generateTypeConversion(DexType type, DexType typeWrapperType) {
     DexType reverse = vivifiedTypeWrappers.get(type);
+    assert reverse != null;
     return synthesizeConversionMethod(
         typeWrapperType,
         type,
         type,
         vivifiedTypeFor(type),
-        reverse == null ? null : wrappedValueField(reverse, vivifiedTypeFor(type)));
+        wrappedValueField(reverse, vivifiedTypeFor(type)));
   }
 
   private DexEncodedMethod generateVivifiedTypeConversion(
       DexType type, DexType vivifiedTypeWrapperType) {
     DexType reverse = typeWrappers.get(type);
+    assert reverse != null;
     return synthesizeConversionMethod(
         vivifiedTypeWrapperType,
         type,
         vivifiedTypeFor(type),
         type,
-        reverse == null ? null : wrappedValueField(reverse, type));
+        wrappedValueField(reverse, type));
   }
 
   private DexEncodedMethod synthesizeConversionMethod(
@@ -609,7 +600,7 @@
       DexType type,
       DexType argType,
       DexType returnType,
-      DexField reverseFieldOrNull) {
+      DexField reverseField) {
     DexMethod method =
         factory.createMethod(
             holder, factory.createProto(returnType, argType), factory.convertMethodName);
@@ -629,7 +620,7 @@
           new APIConverterWrapperConversionCfCodeProvider(
                   appView,
                   argType,
-                  reverseFieldOrNull,
+                  reverseField,
                   factory.createField(holder, returnType, factory.wrapperFieldName))
               .generateCfCode();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java
index 2900761..a8161d4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java
@@ -22,7 +22,7 @@
   @Override
   protected void prepareInstructions() {
     // Super constructor call (always java.lang.Object.<init>()).
-    DexMethod objectInitMethod = factory().objectMethods.constructor;
+    DexMethod objectInitMethod = factory().objectMembers.constructor;
     add(
         builder -> {
           assert builder.getReceiverValue() != null;
@@ -55,7 +55,7 @@
     // be treated as equal, since it only has one call to super constructor,
     // which is always java.lang.Object.<init>().
     return captures().length == 0
-        ? System.identityHashCode(factory().objectMethods.constructor)
+        ? System.identityHashCode(factory().objectMembers.constructor)
         : super.hashCode();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSynthesizedCode.java
index 906e15f..1809d69 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSynthesizedCode.java
@@ -22,7 +22,7 @@
   @Override
   public Consumer<UseRegistry> getRegistryCallback() {
     return registry -> {
-      registry.registerInvokeDirect(dexItemFactory().objectMethods.constructor);
+      registry.registerInvokeDirect(dexItemFactory().objectMembers.constructor);
       DexType[] capturedTypes = captures();
       for (int i = 0; i < capturedTypes.length; i++) {
         registry.registerInstanceFieldWrite(lambda.getCaptureField(i));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
index 911faa5..de16e98 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
@@ -35,7 +35,7 @@
       DexItemFactory factory,
       Set<Value> affectedValues) {
     InvokeVirtual getClass =
-        new InvokeVirtual(factory.objectMethods.getClass, null, invoke.inValues());
+        new InvokeVirtual(factory.objectMembers.getClass, null, invoke.inValues());
     if (invoke.hasOutValue()) {
       affectedValues.addAll(invoke.outValue().affectedValues());
       invoke.outValue().replaceUsers(invoke.inValues().get(0));
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 80c36cc..60813ab 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
@@ -26,6 +26,7 @@
 import com.android.tools.r8.ir.analysis.equivalence.BasicBlockBehavioralSubsumption;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeUtils;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.AlwaysMaterializingNop;
 import com.android.tools.r8.ir.code.ArrayLength;
@@ -73,6 +74,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.controlflow.SwitchCaseAnalyzer;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOutputMode;
@@ -240,10 +242,64 @@
 
   // Rewrite 'throw new NullPointerException()' to 'throw null'.
   public void rewriteThrowNullPointerException(IRCode code) {
+    boolean shouldRemoveUnreachableBlocks = false;
     for (BasicBlock block : code.blocks) {
       InstructionListIterator it = block.listIterator(code);
       while (it.hasNext()) {
         Instruction instruction = it.next();
+
+        // Check for the patterns 'if (x == null) throw null' and
+        // 'if (x == null) throw new NullPointerException()'.
+        if (instruction.isIf()) {
+          if (appView.dexItemFactory().objectsMethods.isRequireNonNullMethod(code.method.method)) {
+            continue;
+          }
+
+          If ifInstruction = instruction.asIf();
+          if (!ifInstruction.isZeroTest()) {
+            continue;
+          }
+
+          Value value = ifInstruction.lhs();
+          if (!value.getTypeLattice().isReference()) {
+            assert value.getTypeLattice().isPrimitive();
+            continue;
+          }
+
+          BasicBlock valueIsNullTarget = ifInstruction.targetFromCondition(0);
+          if (valueIsNullTarget.getPredecessors().size() != 1
+              || !valueIsNullTarget.exit().isThrow()) {
+            continue;
+          }
+
+          Throw throwInstruction = valueIsNullTarget.exit().asThrow();
+          Value exceptionValue = throwInstruction.exception();
+          if (!exceptionValue.isConstZero()
+              && !TypeUtils.isNullPointerException(exceptionValue.getTypeLattice(), appView)) {
+            continue;
+          }
+
+          boolean canDetachValueIsNullTarget = true;
+          for (Instruction i : valueIsNullTarget.instructionsBefore(throwInstruction)) {
+            if (!i.isBlockLocalInstructionWithoutSideEffects(appView, code.method.holder())) {
+              canDetachValueIsNullTarget = false;
+              break;
+            }
+          }
+          if (!canDetachValueIsNullTarget) {
+            continue;
+          }
+
+          rewriteIfToRequireNonNull(
+              block,
+              it,
+              ifInstruction,
+              ifInstruction.targetFromCondition(1),
+              valueIsNullTarget,
+              throwInstruction.getPosition());
+          shouldRemoveUnreachableBlocks = true;
+        }
+
         // Check for 'new-instance NullPointerException' with 2 users, not declaring a local and
         // not ending the scope of any locals.
         if (instruction.isNewInstance()
@@ -297,6 +353,12 @@
         }
       }
     }
+    if (shouldRemoveUnreachableBlocks) {
+      Set<Value> affectedValues = code.removeUnreachableBlocks();
+      if (!affectedValues.isEmpty()) {
+        new TypeAnalysis(appView).narrowing(affectedValues);
+      }
+    }
     assert code.isConsistentSSA();
   }
 
@@ -843,7 +905,11 @@
     return outliersAsIfSize;
   }
 
-  private boolean rewriteSwitch(IRCode code) {
+  public boolean rewriteSwitch(IRCode code) {
+    return rewriteSwitch(code, SwitchCaseAnalyzer.getInstance());
+  }
+
+  public boolean rewriteSwitch(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) {
     if (!code.metadata().mayHaveIntSwitch()) {
       return false;
     }
@@ -859,7 +925,7 @@
           IntSwitch theSwitch = instruction.asIntSwitch();
           if (options.testing.enableDeadSwitchCaseElimination) {
             SwitchCaseEliminator eliminator =
-                removeUnnecessarySwitchCases(code, theSwitch, iterator);
+                removeUnnecessarySwitchCases(code, theSwitch, iterator, switchCaseAnalyzer);
             if (eliminator != null) {
               if (eliminator.mayHaveIntroducedUnreachableBlocks()) {
                 needToRemoveUnreachableBlocks = true;
@@ -1004,7 +1070,10 @@
   }
 
   private SwitchCaseEliminator removeUnnecessarySwitchCases(
-      IRCode code, IntSwitch theSwitch, InstructionListIterator iterator) {
+      IRCode code,
+      IntSwitch theSwitch,
+      InstructionListIterator iterator,
+      SwitchCaseAnalyzer switchCaseAnalyzer) {
     BasicBlock defaultTarget = theSwitch.fallthroughBlock();
     SwitchCaseEliminator eliminator = null;
     BasicBlockBehavioralSubsumption behavioralSubsumption =
@@ -1016,7 +1085,7 @@
 
       // This switch case can be removed if the behavior of the target block is equivalent to the
       // behavior of the default block, or if the switch case is unreachable.
-      if (switchCaseIsUnreachable(theSwitch, i)
+      if (switchCaseAnalyzer.switchCaseIsUnreachable(theSwitch, i)
           || behavioralSubsumption.isSubsumedBy(targetBlock, defaultTarget)) {
         if (eliminator == null) {
           eliminator = new SwitchCaseEliminator(theSwitch, iterator);
@@ -1030,12 +1099,6 @@
     return eliminator;
   }
 
-  private boolean switchCaseIsUnreachable(IntSwitch theSwitch, int index) {
-    Value switchValue = theSwitch.value();
-    return switchValue.hasValueRange()
-        && !switchValue.getValueRange().containsValue(theSwitch.getKey(index));
-  }
-
   /**
    * 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
@@ -2701,7 +2764,10 @@
         throwNullInsnIterator.replaceCurrentInstruction(notReachableThrow);
       }
     }
-    code.removeUnreachableBlocks();
+    Set<Value> affectedValues = code.removeUnreachableBlocks();
+    if (!affectedValues.isEmpty()) {
+      new TypeAnalysis(appView).narrowing(affectedValues);
+    }
     assert code.isConsistentSSA();
   }
 
@@ -2850,6 +2916,32 @@
     assert block.exit().asGoto().getTarget() == target;
   }
 
+  private void rewriteIfToRequireNonNull(
+      BasicBlock block,
+      InstructionListIterator iterator,
+      If theIf,
+      BasicBlock target,
+      BasicBlock deadTarget,
+      Position position) {
+    deadTarget.unlinkSinglePredecessorSiblingsAllowed();
+    assert theIf == block.exit();
+    iterator.previous();
+    Instruction instruction;
+    if (appView.options().canUseRequireNonNull()) {
+      DexMethod requireNonNullMethod = appView.dexItemFactory().objectsMethods.requireNonNull;
+      instruction = new InvokeStatic(requireNonNullMethod, null, ImmutableList.of(theIf.lhs()));
+    } else {
+      DexMethod getClassMethod = appView.dexItemFactory().objectMembers.getClass;
+      instruction = new InvokeVirtual(getClassMethod, null, ImmutableList.of(theIf.lhs()));
+    }
+    instruction.setPosition(position);
+    iterator.add(instruction);
+    iterator.next();
+    iterator.replaceCurrentInstruction(new Goto());
+    assert block.exit().isGoto();
+    assert block.exit().asGoto().getTarget() == target;
+  }
+
   private void rewriteIfWithConstZero(IRCode code, BasicBlock block) {
     If theIf = block.exit().asIf();
     if (theIf.isZeroTest()) {
@@ -3280,8 +3372,9 @@
         iterator = isNotNullBlock.listIterator(code);
         iterator.setInsertionPosition(position);
         value = code.createValue(TypeLatticeElement.classClassType(appView, definitelyNotNull()));
-        iterator.add(new InvokeVirtual(dexItemFactory.objectMethods.getClass, value,
-            ImmutableList.of(arguments.get(i))));
+        iterator.add(
+            new InvokeVirtual(
+                dexItemFactory.objectMembers.getClass, value, ImmutableList.of(arguments.get(i))));
         iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value)));
       }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
index 06c5dfe..0a314aa 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
@@ -4,9 +4,12 @@
 
 package com.android.tools.r8.ir.optimize;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
@@ -62,27 +65,35 @@
       ClassTypeLatticeElement dynamicLowerBoundType;
       if (current.isInvokeMethod()) {
         InvokeMethod invoke = current.asInvokeMethod();
+        DexMethod invokedMethod = invoke.getInvokedMethod();
 
-        DexType staticReturnTypeRaw = invoke.getInvokedMethod().proto.returnType;
+        DexType staticReturnTypeRaw = invokedMethod.proto.returnType;
         if (!staticReturnTypeRaw.isReferenceType()) {
           continue;
         }
 
-        DexEncodedMethod singleTarget =
-            invoke.lookupSingleTarget(appView, code.method.method.holder);
-        if (singleTarget == null) {
-          continue;
-        }
+        if (invokedMethod.holder.isArrayType()
+            && invokedMethod.match(appView.dexItemFactory().objectMembers.clone)) {
+          dynamicUpperBoundType =
+              TypeLatticeElement.fromDexType(invokedMethod.holder, definitelyNotNull(), appView);
+          dynamicLowerBoundType = null;
+        } else {
+          DexEncodedMethod singleTarget =
+              invoke.lookupSingleTarget(appView, code.method.method.holder);
+          if (singleTarget == null) {
+            continue;
+          }
 
-        MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
-        if (optimizationInfo.returnsArgument()) {
-          // Don't insert an assume-instruction since we will replace all usages of the out-value by
-          // the corresponding argument.
-          continue;
-        }
+          MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
+          if (optimizationInfo.returnsArgument()) {
+            // Don't insert an assume-instruction since we will replace all usages of the out-value
+            // by the corresponding argument.
+            continue;
+          }
 
-        dynamicUpperBoundType = optimizationInfo.getDynamicUpperBoundType();
-        dynamicLowerBoundType = optimizationInfo.getDynamicLowerBoundType();
+          dynamicUpperBoundType = optimizationInfo.getDynamicUpperBoundType();
+          dynamicLowerBoundType = optimizationInfo.getDynamicLowerBoundType();
+        }
       } else if (current.isStaticGet()) {
         StaticGet staticGet = current.asStaticGet();
         DexEncodedField encodedField = appView.appInfo().resolveField(staticGet.getField());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
index 303b4ea..9cbc694 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
@@ -270,8 +270,12 @@
     InstructionListIterator it = entryBlock.listIterator(code);
     while (it.hasNext()) {
       Instruction current = it.next();
-      if (current.hasOutValue() && canonicalizedInvoke.inValues().contains(current.outValue())) {
-        numberOfInValuePassed++;
+      if (current.hasOutValue()) {
+        for (Value inValue : canonicalizedInvoke.inValues()) {
+          if (inValue == current.outValue()) {
+            numberOfInValuePassed++;
+          }
+        }
       }
       if (numberOfInValuePassed == canonicalizedInvoke.inValues().size()) {
         // If this invocation uses arguments and this iteration ends in the middle of Arguments,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 0403117..c9bf058 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -757,7 +757,7 @@
           DexMethod requireNonNullMethod = appView.dexItemFactory().objectsMethods.requireNonNull;
           iterator.add(new InvokeStatic(requireNonNullMethod, null, ImmutableList.of(receiver)));
         } else {
-          DexMethod getClassMethod = appView.dexItemFactory().objectMethods.getClass;
+          DexMethod getClassMethod = appView.dexItemFactory().objectMembers.getClass;
           iterator.add(new InvokeVirtual(getClassMethod, null, ImmutableList.of(receiver)));
         }
       } else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index a28181f..783ed8a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -3,12 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
@@ -20,12 +23,17 @@
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.IRMetadata;
-import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.InstanceFieldInstruction;
+import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.StaticFieldInstruction;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
@@ -195,12 +203,19 @@
       iterator.replaceCurrentInstruction(replacement);
     } else {
       assert lookup.type == RuleType.ASSUME_VALUES;
-      if (current.outValue() != null) {
+      BasicBlock block = current.getBlock();
+      Position position = current.getPosition();
+      if (current.hasOutValue()) {
         assert replacement.outValue() != null;
         current.outValue().replaceUsers(replacement.outValue());
       }
-      replacement.setPosition(current.getPosition());
-      if (current.getBlock().hasCatchHandlers()) {
+      if (current.isStaticGet()) {
+        StaticGet staticGet = current.asStaticGet();
+        replaceStaticFieldInstructionByClinitAccessIfPossible(
+            staticGet, staticGet.getField().holder, code, iterator, code.method.holder());
+      }
+      replacement.setPosition(position);
+      if (block.hasCatchHandlers()) {
         iterator.split(code, blocks).listIterator(code).add(replacement);
       } else {
         iterator.add(replacement);
@@ -342,22 +357,25 @@
       affectedValues.addAll(current.outValue().affectedValues());
       DexType context = code.method.method.holder;
       if (current.instructionMayHaveSideEffects(appView, context)) {
+        BasicBlock block = current.getBlock();
+        Position position = current.getPosition();
+
         // All usages are replaced by the replacement value.
         current.outValue().replaceUsers(replacement.outValue());
 
         // To preserve side effects, original field-get is replaced by an explicit null-check, if
         // the field-get instruction may only fail with an NPE, or the field-get remains as-is.
-        Instruction currentOrNullCheck;
         if (current.isInstanceGet()) {
-          currentOrNullCheck =
-              replaceInstanceGetByNullCheckIfPossible(current.asInstanceGet(), iterator, context);
+          replaceInstanceFieldInstructionByNullCheckIfPossible(
+              current.asInstanceGet(), iterator, context);
         } else {
-          currentOrNullCheck = current;
+          replaceStaticFieldInstructionByClinitAccessIfPossible(
+              current.asStaticGet(), target.holder(), code, iterator, context);
         }
 
         // Insert the definition of the replacement.
-        replacement.setPosition(currentOrNullCheck.getPosition());
-        if (currentOrNullCheck.getBlock().hasCatchHandlers()) {
+        replacement.setPosition(position);
+        if (block.hasCatchHandlers()) {
           iterator.split(code, blocks).listIterator(code).add(replacement);
         } else {
           iterator.add(replacement);
@@ -369,24 +387,78 @@
     }
   }
 
-  private Instruction replaceInstanceGetByNullCheckIfPossible(
-      InstanceGet instruction, InstructionListIterator iterator, DexType context) {
-    assert !instruction.outValue().hasAnyUsers();
+  private void replaceInstanceFieldInstructionByNullCheckIfPossible(
+      InstanceFieldInstruction instruction, InstructionListIterator iterator, DexType context) {
+    assert !instruction.hasOutValue() || !instruction.outValue().hasAnyUsers();
     if (instruction.instructionMayHaveSideEffects(
         appView, context, FieldInstruction.Assumption.RECEIVER_NOT_NULL)) {
-      return instruction;
+      return;
     }
     Value receiver = instruction.object();
+    if (receiver.isNeverNull()) {
+      iterator.removeOrReplaceByDebugLocalRead();
+      return;
+    }
     InvokeMethod replacement;
     if (appView.options().canUseRequireNonNull()) {
       DexMethod requireNonNullMethod = appView.dexItemFactory().objectsMethods.requireNonNull;
       replacement = new InvokeStatic(requireNonNullMethod, null, ImmutableList.of(receiver));
     } else {
-      DexMethod getClassMethod = appView.dexItemFactory().objectMethods.getClass;
+      DexMethod getClassMethod = appView.dexItemFactory().objectMembers.getClass;
       replacement = new InvokeVirtual(getClassMethod, null, ImmutableList.of(receiver));
     }
     iterator.replaceCurrentInstruction(replacement);
-    return replacement;
+  }
+
+  private void replaceInstancePutByNullCheckIfNeverRead(
+      IRCode code, InstructionListIterator iterator, InstancePut current) {
+    DexEncodedField target = appView.appInfo().resolveField(current.getField());
+    if (target == null || appView.appInfo().isFieldRead(target)) {
+      return;
+    }
+
+    if (target.isStatic()) {
+      return;
+    }
+
+    replaceInstanceFieldInstructionByNullCheckIfPossible(current, iterator, code.method.holder());
+  }
+
+  private void replaceStaticPutByClinitAccessIfNeverRead(
+      IRCode code, InstructionListIterator iterator, StaticPut current) {
+    DexEncodedField field = appView.appInfo().resolveField(current.getField());
+    if (field == null || appView.appInfo().isFieldRead(field)) {
+      return;
+    }
+
+    if (!field.isStatic()) {
+      return;
+    }
+
+    replaceStaticFieldInstructionByClinitAccessIfPossible(
+        current, field.holder(), code, iterator, code.method.holder());
+  }
+
+  private void replaceStaticFieldInstructionByClinitAccessIfPossible(
+      StaticFieldInstruction instruction,
+      DexType holder,
+      IRCode code,
+      InstructionListIterator iterator,
+      DexType context) {
+    if (instruction.instructionMayHaveSideEffects(
+        appView, context, FieldInstruction.Assumption.CLASS_ALREADY_INITIALIZED)) {
+      return;
+    }
+    DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(holder));
+    if (clazz != null) {
+      DexEncodedField clinitField =
+          clazz.lookupStaticField(appView.dexItemFactory().objectMembers.clinitField);
+      if (clinitField != null) {
+        Value dest = code.createValue(TypeLatticeElement.getInt());
+        StaticGet replacement = new StaticGet(dest, clinitField.field);
+        iterator.replaceCurrentInstruction(replacement);
+      }
+    }
   }
 
   /**
@@ -396,7 +468,7 @@
    */
   public void rewriteWithConstantValues(IRCode code, DexType callingContext) {
     IRMetadata metadata = code.metadata();
-    if (!metadata.mayHaveFieldGet() && !metadata.mayHaveInvokeMethod()) {
+    if (!metadata.mayHaveFieldInstruction() && !metadata.mayHaveInvokeMethod()) {
       return;
     }
 
@@ -413,6 +485,10 @@
         } else if (current.isFieldGet()) {
           rewriteFieldGetWithConstantValues(
               code, affectedValues, blocks, iterator, current.asFieldInstruction());
+        } else if (current.isInstancePut()) {
+          replaceInstancePutByNullCheckIfNeverRead(code, iterator, current.asInstancePut());
+        } else if (current.isStaticPut()) {
+          replaceStaticPutByClinitAccessIfNeverRead(code, iterator, current.asStaticPut());
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 8c07d3d..4fa0312 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -127,7 +127,7 @@
           // Case (5), field-get instructions that are guaranteed to read a non-null value.
           FieldInstruction fieldInstruction = current.asFieldInstruction();
           DexField field = fieldInstruction.getField();
-          if (field.type.isClassType() && isNullableReferenceTypeWithUsers(outValue)) {
+          if (field.type.isReferenceType() && isNullableReferenceTypeWithUsers(outValue)) {
             DexEncodedField encodedField = appView.appInfo().resolveField(field);
             if (encodedField != null) {
               FieldOptimizationInfo optimizationInfo = encodedField.getOptimizationInfo();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index fa41f21..055eeca 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -83,7 +83,7 @@
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     DexMethod invokedMethod = invoke.getInvokedMethod();
     // Class<?> Object#getClass() is final and cannot be overridden.
-    if (invokedMethod != dexItemFactory.objectMethods.getClass) {
+    if (invokedMethod != dexItemFactory.objectMembers.getClass) {
       return null;
     }
     Value in = invoke.getReceiver();
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 291f4d5..6707fe1 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
@@ -343,7 +343,7 @@
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     for (DexEncodedMethod method : clazz.virtualMethods()) {
       if (method.method.name == dexItemFactory.finalizeMethodName
-          && method.method.proto == dexItemFactory.objectMethods.finalize.proto) {
+          && method.method.proto == dexItemFactory.objectMembers.finalize.proto) {
         return EligibilityStatus.HAS_FINALIZER;
       }
     }
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 5612322..22344de 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
@@ -446,7 +446,7 @@
             }
 
             DexMethod invokedMethod = invoke.getInvokedMethod();
-            if (invokedMethod == dexItemFactory.objectMethods.constructor) {
+            if (invokedMethod == dexItemFactory.objectMembers.constructor) {
               continue;
             }
 
@@ -498,7 +498,7 @@
         if (instruction.isInvokeMethodWithReceiver()) {
           InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
           DexMethod invokedMethod = invoke.getInvokedMethod();
-          if (invokedMethod == dexItemFactory.objectMethods.constructor) {
+          if (invokedMethod == dexItemFactory.objectMembers.constructor) {
             continue;
           }
 
@@ -557,7 +557,7 @@
         // Remove the call to java.lang.Object.<init>().
         if (user.isInvokeDirect()) {
           if (root.isNewInstance()
-              && invoke.getInvokedMethod() == dexItemFactory.objectMethods.constructor) {
+              && invoke.getInvokedMethod() == dexItemFactory.objectMembers.constructor) {
             removeInstruction(invoke);
             continue;
           }
@@ -743,7 +743,7 @@
 
     // Check that the entire constructor chain can be inlined into the current context.
     DexMethod parent = instanceInitializerInfo.getParent();
-    while (parent != dexItemFactory.objectMethods.constructor) {
+    while (parent != dexItemFactory.objectMembers.constructor) {
       if (parent == null) {
         return null;
       }
@@ -1177,7 +1177,7 @@
   }
 
   private boolean isInstanceInitializerEligibleForClassInlining(DexMethod method) {
-    if (method == dexItemFactory.objectMethods.constructor) {
+    if (method == dexItemFactory.objectMembers.constructor) {
       return true;
     }
     DexEncodedMethod encodedMethod = appView.definitionFor(method);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java b/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java
new file mode 100644
index 0000000..6c22a72
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.controlflow;
+
+import com.android.tools.r8.ir.code.IntSwitch;
+import com.android.tools.r8.ir.code.Value;
+
+public class SwitchCaseAnalyzer {
+
+  private static final SwitchCaseAnalyzer INSTANCE = new SwitchCaseAnalyzer();
+
+  public SwitchCaseAnalyzer() {}
+
+  public static SwitchCaseAnalyzer getInstance() {
+    return INSTANCE;
+  }
+
+  public boolean switchCaseIsUnreachable(IntSwitch theSwitch, int index) {
+    Value switchValue = theSwitch.value();
+    return switchValue.hasValueRange()
+        && !switchValue.getValueRange().containsValue(theSwitch.getKey(index));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index ee87965..e96b211 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -38,6 +38,10 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor;
 import com.android.tools.r8.ir.conversion.PostOptimization;
+import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.Reporter;
@@ -227,21 +231,56 @@
     return Reason.ELIGIBLE;
   }
 
-  public void unboxEnums(PostMethodProcessor.Builder postBuilder) {
+  public void unboxEnums(
+      PostMethodProcessor.Builder postBuilder,
+      ExecutorService executorService,
+      OptimizationFeedbackDelayed feedback)
+      throws ExecutionException {
     // At this point the enumsToUnbox are no longer candidates, they will all be unboxed.
     if (enumsUnboxingCandidates.isEmpty()) {
       return;
     }
     ImmutableSet<DexType> enumsToUnbox = ImmutableSet.copyOf(this.enumsUnboxingCandidates.keySet());
-    appView.setUnboxedEnums(enumsToUnbox);
     NestedGraphLense enumUnboxingLens = new TreeFixer(enumsToUnbox).fixupTypeReferences();
     enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumsToUnbox);
+    appView.setUnboxedEnums(enumUnboxerRewriter.getEnumsToUnbox());
     if (enumUnboxingLens != null) {
       appView.setGraphLense(enumUnboxingLens);
       appView.setAppInfo(
           appView
               .appInfo()
               .rewrittenWithLens(appView.appInfo().app().asDirect(), enumUnboxingLens));
+
+      // Update optimization info.
+      feedback.fixupOptimizationInfos(
+          appView,
+          executorService,
+          new OptimizationInfoFixer() {
+            @Override
+            public void fixup(DexEncodedField field) {
+              FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo();
+              if (optimizationInfo.isMutableFieldOptimizationInfo()) {
+                optimizationInfo
+                    .asMutableFieldOptimizationInfo()
+                    .fixupAbstractValue(appView, appView.graphLense());
+              } else {
+                assert optimizationInfo.isDefaultFieldOptimizationInfo();
+              }
+            }
+
+            @Override
+            public void fixup(DexEncodedMethod method) {
+              MethodOptimizationInfo optimizationInfo = method.getOptimizationInfo();
+              if (optimizationInfo.isUpdatableMethodOptimizationInfo()) {
+                optimizationInfo
+                    .asUpdatableMethodOptimizationInfo()
+                    .fixupAbstractReturnValue(appView, appView.graphLense())
+                    .fixupInstanceInitializerInfo(appView, appView.graphLense());
+              } else {
+                assert optimizationInfo.isDefaultMethodOptimizationInfo();
+              }
+            }
+          });
     }
     postBuilder.put(this);
     postBuilder.mapDexEncodedMethods(appView);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index ea26434..cbd3824 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -76,6 +76,10 @@
             "$enumboxing$ordinal");
   }
 
+  public EnumValueInfoMapCollection getEnumsToUnbox() {
+    return enumsToUnbox;
+  }
+
   void rewriteCode(IRCode code) {
     // We should not process the enum methods, they will be removed and they may contain invalid
     // rewriting rules.
@@ -117,7 +121,7 @@
           EnumValueInfo enumValueInfo = enumValueInfoMap.getEnumValueInfo(staticGet.getField());
           assert enumValueInfo != null
               : "Invalid read to " + staticGet.getField().name + ", error during enum analysis";
-          instruction = new ConstNumber(staticGet.outValue(), enumValueInfo.ordinal + 1);
+          instruction = new ConstNumber(staticGet.outValue(), enumValueInfo.convertToInt());
           staticGet
               .outValue()
               .setTypeLattice(PrimitiveTypeLatticeElement.fromNumericType(NumericType.INT));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
index ddf697d..30766c5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -10,6 +10,7 @@
 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.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
@@ -19,6 +20,7 @@
 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.IRMetadata;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.IntSwitch;
@@ -46,7 +48,9 @@
 
   @SuppressWarnings("ConstantConditions")
   public void rewriteConstantEnumMethodCalls(IRCode code) {
-    if (!code.metadata().mayHaveInvokeMethodWithReceiver()) {
+    IRMetadata metadata = code.metadata();
+    if (!metadata.mayHaveInvokeMethodWithReceiver()
+        && !(metadata.mayHaveInvokeStatic() && metadata.mayHaveArrayLength())) {
       return;
     }
 
@@ -54,65 +58,79 @@
     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();
-
-      EnumValueInfoMap valueInfoMap =
-          appView.appInfo().withLiveness().getEnumValueInfoMap(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.getEnumValueInfo(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()) {
+      if (current.isInvokeMethodWithReceiver()) {
+        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;
         }
-        DexEncodedMethod singleTarget =
-            appView
-                .appInfo()
-                .resolveMethodOnClass(valueInfo.type, factory.objectMethods.toString)
-                .getSingleTarget();
-        if (singleTarget != null && singleTarget.method != factory.enumMethods.toString) {
+
+        Value receiver = methodWithReceiver.getReceiver().getAliasedValue();
+        if (receiver.isPhi()) {
           continue;
         }
-        iterator.replaceCurrentInstruction(
-            new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
+        Instruction definition = receiver.getDefinition();
+        if (!definition.isStaticGet()) {
+          continue;
+        }
+        DexField enumField = definition.asStaticGet().getField();
+        EnumValueInfoMap valueInfoMap =
+            appView.appInfo().withLiveness().getEnumValueInfoMap(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.getEnumValueInfo(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.objectMembers.toString)
+                  .getSingleTarget();
+          if (singleTarget != null && singleTarget.method != factory.enumMethods.toString) {
+            continue;
+          }
+          iterator.replaceCurrentInstruction(
+              new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
+        }
+      } else if (current.isArrayLength()) {
+        // Rewrites MyEnum.values().length to a constant int.
+        Instruction arrayDefinition = current.asArrayLength().array().definition;
+        if (arrayDefinition != null && arrayDefinition.isInvokeStatic()) {
+          DexMethod invokedMethod = arrayDefinition.asInvokeStatic().getInvokedMethod();
+          DexProgramClass enumClass = appView.definitionForProgramType(invokedMethod.holder);
+          if (enumClass != null
+              && enumClass.isEnum()
+              && factory.enumMethods.isValuesMethod(invokedMethod, enumClass)) {
+            EnumValueInfoMap enumValueInfoMap =
+                appView.appInfo().withLiveness().getEnumValueInfoMap(invokedMethod.holder);
+            if (enumValueInfoMap != null) {
+              iterator.replaceCurrentInstructionWithConstInt(code, enumValueInfoMap.size());
+            }
+          }
+        }
       }
     }
     assert code.isConsistentSSA();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
index a30c24f..8248e52 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
@@ -50,6 +50,11 @@
   }
 
   @Override
+  public boolean isDead() {
+    return false;
+  }
+
+  @Override
   public boolean valueHasBeenPropagated() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
index 9103459..33a6e75 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
@@ -26,6 +26,8 @@
 
   public abstract TypeLatticeElement getDynamicUpperBoundType();
 
+  public abstract boolean isDead();
+
   public abstract boolean valueHasBeenPropagated();
 
   public boolean isDefaultFieldOptimizationInfo() {
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 b5215f6..84e4fdc 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
@@ -96,7 +96,6 @@
 import com.android.tools.r8.utils.Pair;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.Sets;
-import com.google.common.collect.Streams;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.BitSet;
@@ -412,6 +411,8 @@
       return null;
     }
 
+    AliasedValueConfiguration aliasesThroughAssumeAndCheckCasts =
+        AssumeAndCheckCastAliasedValueConfiguration.getInstance();
     Value receiver = code.getThis();
     boolean hasCatchHandler = false;
     for (BasicBlock block : code.blocks) {
@@ -487,12 +488,14 @@
               if (field == null) {
                 return null;
               }
-              if (instancePut.object().getAliasedValue() != receiver
+              Value object =
+                  instancePut.object().getAliasedValue(aliasesThroughAssumeAndCheckCasts);
+              if (object != receiver
                   || instancePut.instructionInstanceCanThrow(appView, clazz.type).isThrowing()) {
                 builder.setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
               }
 
-              Value value = instancePut.value().getAliasedValue();
+              Value value = instancePut.value().getAliasedValue(aliasesThroughAssumeAndCheckCasts);
               // TODO(b/142762134): Replace the use of onlyDependsOnArgument() by
               //  ValueMayDependOnEnvironmentAnalysis.
               if (!value.onlyDependsOnArgument()) {
@@ -518,13 +521,14 @@
                 }
                 // java.lang.Enum.<init>() and java.lang.Object.<init>() are considered trivial.
                 if (invokedMethod == dexItemFactory.enumMethods.constructor
-                    || invokedMethod == dexItemFactory.objectMethods.constructor) {
+                    || invokedMethod == dexItemFactory.objectMembers.constructor) {
                   builder.setParent(invokedMethod);
                   break;
                 }
                 builder.merge(singleTarget.getOptimizationInfo().getInstanceInitializerInfo());
                 for (int i = 1; i < invoke.arguments().size(); i++) {
-                  Value argument = invoke.arguments().get(i).getAliasedValue();
+                  Value argument =
+                      invoke.arguments().get(i).getAliasedValue(aliasesThroughAssumeAndCheckCasts);
                   if (argument == receiver) {
                     // In the analysis of the parent constructor, we don't consider the non-receiver
                     // arguments as being aliases of the receiver. Therefore, we explicitly mark
@@ -543,7 +547,7 @@
                     .markAllFieldsAsRead()
                     .setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
                 for (Value inValue : invoke.inValues()) {
-                  if (inValue.getAliasedValue() == receiver) {
+                  if (inValue.getAliasedValue(aliasesThroughAssumeAndCheckCasts) == receiver) {
                     builder.setReceiverMayEscapeOutsideConstructorChain();
                     break;
                   }
@@ -559,7 +563,7 @@
                 builder.setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
               }
               for (Value argument : invoke.arguments()) {
-                if (argument.getAliasedValue() == receiver) {
+                if (argument.getAliasedValue(aliasesThroughAssumeAndCheckCasts) == receiver) {
                   builder.setReceiverMayEscapeOutsideConstructorChain();
                   break;
                 }
@@ -576,7 +580,7 @@
                   .markAllFieldsAsRead()
                   .setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
               for (Value argument : invoke.arguments()) {
-                if (argument.getAliasedValue() == receiver) {
+                if (argument.getAliasedValue(aliasesThroughAssumeAndCheckCasts) == receiver) {
                   builder.setReceiverMayEscapeOutsideConstructorChain();
                   break;
                 }
@@ -783,7 +787,7 @@
           if (isInstantiationOfNullPointerException(instr, it, appView.dexItemFactory())) {
             it.next(); // Skip call to NullPointerException.<init>.
             return InstructionEffect.NO_EFFECT;
-          } else if (instr.throwsNpeIfValueIsNull(value, appView.dexItemFactory())) {
+          } else if (instr.throwsNpeIfValueIsNull(value, appView, code.method.holder())) {
             // In order to preserve NPE semantic, the exception must not be caught by any handler.
             // Therefore, we must ignore this instruction if it is covered by a catch handler.
             // Note: this is a conservative approach where we consider that any catch handler could
@@ -961,10 +965,14 @@
       // {v0}, T`.
       mayHaveSideEffects = true;
     } else {
+      mayHaveSideEffects = false;
       // Otherwise, check if there is an instruction that has side effects.
-      mayHaveSideEffects =
-          Streams.stream(code.instructions())
-              .anyMatch(instruction -> instruction.instructionMayHaveSideEffects(appView, context));
+      for (Instruction instruction : code.instructions()) {
+        if (instruction.instructionMayHaveSideEffects(appView, context)) {
+          mayHaveSideEffects = true;
+          break;
+        }
+      }
     }
     if (!mayHaveSideEffects) {
       feedback.methodMayNotHaveSideEffects(method);
@@ -981,13 +989,13 @@
         ResolutionResult resolutionResult =
             appView
                 .appInfo()
-                .resolveMethodOnClass(clazz, appView.dexItemFactory().objectMethods.finalize);
+                .resolveMethodOnClass(clazz, appView.dexItemFactory().objectMembers.finalize);
 
         DexEncodedMethod target = resolutionResult.getSingleTarget();
         if (target != null
             && target.method != dexItemFactory.enumMethods.finalize
-            && target.method != dexItemFactory.objectMethods.finalize) {
-            return true;
+            && target.method != dexItemFactory.objectMembers.finalize) {
+          return true;
         }
         return false;
       } else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
index 33c7b2a..a4917f2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
@@ -7,10 +7,12 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.function.Function;
 
 /**
@@ -22,10 +24,13 @@
  */
 public class MutableFieldOptimizationInfo extends FieldOptimizationInfo {
 
+  private static final int FLAGS_CANNOT_BE_KEPT = 1 << 0;
+  private static final int FLAGS_IS_DEAD = 1 << 1;
+  private static final int FLAGS_VALUE_HAS_BEEN_PROPAGATED = 1 << 2;
+
   private AbstractValue abstractValue = UnknownValue.getInstance();
+  private int flags;
   private int readBits = 0;
-  private boolean cannotBeKept = false;
-  private boolean valueHasBeenPropagated = false;
   private ClassTypeLatticeElement dynamicLowerBoundType = null;
   private TypeLatticeElement dynamicUpperBoundType = null;
 
@@ -50,8 +55,7 @@
   @Override
   public MutableFieldOptimizationInfo mutableCopy() {
     MutableFieldOptimizationInfo copy = new MutableFieldOptimizationInfo();
-    copy.cannotBeKept = cannotBeKept();
-    copy.valueHasBeenPropagated = valueHasBeenPropagated();
+    copy.flags = flags;
     return copy;
   }
 
@@ -60,10 +64,14 @@
     return abstractValue;
   }
 
-  public void setAbstractValue(AbstractValue abstractValue) {
+  void setAbstractValue(AbstractValue abstractValue) {
     this.abstractValue = abstractValue;
   }
 
+  public void fixupAbstractValue(AppView<AppInfoWithLiveness> appView, GraphLense lens) {
+    abstractValue = abstractValue.rewrittenWithLens(appView, lens);
+  }
+
   @Override
   public int getReadBits() {
     return readBits;
@@ -75,11 +83,11 @@
 
   @Override
   public boolean cannotBeKept() {
-    return cannotBeKept;
+    return (flags & FLAGS_CANNOT_BE_KEPT) != 0;
   }
 
   void markCannotBeKept() {
-    cannotBeKept = true;
+    flags |= FLAGS_CANNOT_BE_KEPT;
   }
 
   @Override
@@ -101,12 +109,21 @@
   }
 
   @Override
+  public boolean isDead() {
+    return (flags & FLAGS_IS_DEAD) != 0;
+  }
+
+  void markAsDead() {
+    flags |= FLAGS_IS_DEAD;
+  }
+
+  @Override
   public boolean valueHasBeenPropagated() {
-    return valueHasBeenPropagated;
+    return (flags & FLAGS_VALUE_HAS_BEEN_PROPAGATED) != 0;
   }
 
   void markAsPropagated() {
-    valueHasBeenPropagated = true;
+    flags |= FLAGS_VALUE_HAS_BEEN_PROPAGATED;
   }
 
   @Override
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 3c689a7..81d573b 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
@@ -116,6 +116,11 @@
   }
 
   @Override
+  public void markFieldAsDead(DexEncodedField field) {
+    getFieldOptimizationInfoForUpdating(field).markAsDead();
+  }
+
+  @Override
   public void markFieldAsPropagated(DexEncodedField field) {
     getFieldOptimizationInfoForUpdating(field).markAsPropagated();
   }
@@ -139,6 +144,8 @@
   @Override
   public void recordFieldHasAbstractValue(
       DexEncodedField field, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue) {
+    assert appView.appInfo().getFieldAccessInfoCollection().contains(field.field);
+    assert !appView.appInfo().getFieldAccessInfoCollection().get(field.field).hasReflectiveAccess();
     if (appView.appInfo().mayPropagateValueFor(field.field)) {
       getFieldOptimizationInfoForUpdating(field).setAbstractValue(abstractValue);
     }
@@ -177,13 +184,7 @@
   public synchronized void methodReturnsAbstractValue(
       DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue value) {
     if (appView.appInfo().mayPropagateValueFor(method.method)) {
-      UpdatableMethodOptimizationInfo info = getMethodOptimizationInfoForUpdating(method);
-      assert !info.getAbstractReturnValue().isSingleValue()
-              || info.getAbstractReturnValue().asSingleValue() == value
-              || appView.graphLense().lookupPrototypeChanges(method.method).getRewrittenReturnInfo()
-                  != null
-          : "return single value changed from " + info.getAbstractReturnValue() + " to " + value;
-      info.markReturnsAbstractValue(value);
+      getMethodOptimizationInfoForUpdating(method).markReturnsAbstractValue(value);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index ee5a745..66aefb4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -34,6 +34,9 @@
   public void markFieldCannotBeKept(DexEncodedField field) {}
 
   @Override
+  public void markFieldAsDead(DexEncodedField field) {}
+
+  @Override
   public void markFieldAsPropagated(DexEncodedField field) {}
 
   @Override
@@ -120,8 +123,7 @@
   }
 
   @Override
-  public void setNonNullParamOrThrow(DexEncodedMethod method, BitSet facts) {
-  }
+  public void setNonNullParamOrThrow(DexEncodedMethod method, BitSet facts) {}
 
   @Override
   public void setNonNullParamOnNormalExits(DexEncodedMethod method, BitSet facts) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index ad3080b..b258bdf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -36,6 +36,11 @@
   }
 
   @Override
+  public void markFieldAsDead(DexEncodedField field) {
+    field.getMutableOptimizationInfo().markAsDead();
+  }
+
+  @Override
   public void markFieldAsPropagated(DexEncodedField field) {
     field.getMutableOptimizationInfo().markAsPropagated();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index 3b2a85b..366b4d2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -14,6 +15,7 @@
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
 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.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.util.BitSet;
 import java.util.Set;
@@ -160,6 +162,20 @@
     }
   }
 
+  public UpdatableMethodOptimizationInfo fixupAbstractReturnValue(
+      AppView<AppInfoWithLiveness> appView, GraphLense lens) {
+    abstractReturnValue = abstractReturnValue.rewrittenWithLens(appView, lens);
+    return this;
+  }
+
+  public UpdatableMethodOptimizationInfo fixupInstanceInitializerInfo(
+      AppView<AppInfoWithLiveness> appView, GraphLense lens) {
+    if (instanceInitializerInfo != null) {
+      instanceInitializerInfo = instanceInitializerInfo.rewrittenWithLens(appView, lens);
+    }
+    return this;
+  }
+
   private void setFlag(int flag, boolean value) {
     if (value) {
       setFlag(flag);
@@ -384,6 +400,8 @@
   }
 
   void markReturnsAbstractValue(AbstractValue value) {
+    assert !abstractReturnValue.isSingleValue() || abstractReturnValue.asSingleValue() == value
+        : "return single value changed from " + abstractReturnValue + " to " + value;
     abstractReturnValue = value;
   }
 
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
index 6b3cf4c..fd7a84f 100644
--- 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
@@ -4,8 +4,11 @@
 
 package com.android.tools.r8.ir.optimize.info.field;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.function.BiConsumer;
 
 /**
@@ -40,4 +43,10 @@
   public boolean isEmpty() {
     return true;
   }
+
+  @Override
+  public InstanceFieldInitializationInfoCollection rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLense lens) {
+    return this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
index b9e7652..757eec8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
@@ -4,6 +4,10 @@
 
 package com.android.tools.r8.ir.optimize.info.field;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
 /**
  * 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.
@@ -30,4 +34,13 @@
   public InstanceFieldArgumentInitializationInfo asArgumentInitializationInfo() {
     return this;
   }
+
+  @Override
+  public InstanceFieldInitializationInfo rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLense lens) {
+    // We don't have the context here to determine what should happen. It is the responsibility of
+    // optimizations that change the proto of instance initializers to update the argument
+    // initialization info.
+    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
index 2e9285a..37757f1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java
@@ -4,7 +4,10 @@
 
 package com.android.tools.r8.ir.optimize.info.field;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 /**
  * Information about the way a constructor initializes an instance field on the newly created
@@ -42,4 +45,7 @@
   default boolean isUnknown() {
     return false;
   }
+
+  InstanceFieldInitializationInfo rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLense lens);
 }
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
index ed1c4d3..6bf4c74 100644
--- 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
@@ -4,9 +4,12 @@
 
 package com.android.tools.r8.ir.optimize.info.field;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.function.BiConsumer;
@@ -32,6 +35,9 @@
 
   public abstract boolean isEmpty();
 
+  public abstract InstanceFieldInitializationInfoCollection rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLense lens);
+
   public static class Builder {
 
     Map<DexField, InstanceFieldInitializationInfo> infos = new IdentityHashMap<>();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
index e6f84ff..91d1aef 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
@@ -4,8 +4,12 @@
 
 package com.android.tools.r8.ir.optimize.info.field;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Objects;
 
 /**
@@ -43,6 +47,30 @@
   }
 
   @Override
+  public InstanceFieldInitializationInfo rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLense lens) {
+    EnumValueInfoMapCollection unboxedEnums = appView.unboxedEnums();
+    if (dynamicLowerBoundType != null
+        && unboxedEnums.containsEnum(dynamicLowerBoundType.getClassType())) {
+      // No point in tracking the type of primitives.
+      return UnknownInstanceFieldInitializationInfo.getInstance();
+    }
+    if (dynamicUpperBoundType.isClassType()
+        && unboxedEnums.containsEnum(
+            dynamicUpperBoundType.asClassTypeLatticeElement().getClassType())) {
+      // No point in tracking the type of primitives.
+      return UnknownInstanceFieldInitializationInfo.getInstance();
+    }
+    return new InstanceFieldTypeInitializationInfo(
+        dynamicLowerBoundType != null
+            ? dynamicLowerBoundType
+                .fixupClassTypeReferences(lens::lookupType, appView.withSubtyping())
+                .asClassTypeLatticeElement()
+            : null,
+        dynamicUpperBoundType.fixupClassTypeReferences(lens::lookupType, appView.withSubtyping()));
+  }
+
+  @Override
   public int hashCode() {
     return Objects.hash(dynamicLowerBoundType, dynamicUpperBoundType);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
index 5883be6..7c7a563 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
@@ -4,9 +4,13 @@
 
 package com.android.tools.r8.ir.optimize.info.field;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.function.BiConsumer;
 
@@ -47,4 +51,17 @@
   public boolean isEmpty() {
     return false;
   }
+
+  @Override
+  public InstanceFieldInitializationInfoCollection rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLense lens) {
+    Map<DexField, InstanceFieldInitializationInfo> rewrittenInfos = new IdentityHashMap<>();
+    infos.forEach(
+        (field, info) -> {
+          DexField rewrittenField = lens.lookupField(field);
+          InstanceFieldInitializationInfo rewrittenInfo = info.rewrittenWithLens(appView, lens);
+          rewrittenInfos.put(rewrittenField, rewrittenInfo);
+        });
+    return new NonTrivialInstanceFieldInitializationInfoCollection(rewrittenInfos);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
index a10f045..b2b6ce9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
@@ -4,6 +4,10 @@
 
 package com.android.tools.r8.ir.optimize.info.field;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
 /**
  * Represents that no information is known about the way a particular constructor initializes an
  * instance field of the newly created instance.
@@ -23,4 +27,10 @@
   public boolean isUnknown() {
     return true;
   }
+
+  @Override
+  public InstanceFieldInitializationInfo rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLense lens) {
+    return this;
+  }
 }
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 20aff53..4065fb0 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
@@ -4,11 +4,14 @@
 
 package com.android.tools.r8.ir.optimize.info.initializer;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.GraphLense;
 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;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public class DefaultInstanceInitializerInfo extends InstanceInitializerInfo {
 
@@ -22,11 +25,6 @@
   }
 
   @Override
-  public boolean isDefaultInfo() {
-    return true;
-  }
-
-  @Override
   public DexMethod getParent() {
     return null;
   }
@@ -55,4 +53,10 @@
   public boolean receiverNeverEscapesOutsideConstructorChain() {
     return false;
   }
+
+  @Override
+  public InstanceInitializerInfo rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLense lens) {
+    return this;
+  }
 }
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 b27888e..1745e7d 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
@@ -4,9 +4,12 @@
 
 package com.android.tools.r8.ir.optimize.info.initializer;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public abstract class InstanceInitializerInfo {
 
@@ -53,7 +56,6 @@
     return !receiverNeverEscapesOutsideConstructorChain();
   }
 
-  public boolean isDefaultInfo() {
-    return false;
-  }
+  public abstract InstanceInitializerInfo rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLense lens);
 }
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 4e19cef..f549927 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
@@ -4,13 +4,16 @@
 
 package com.android.tools.r8.ir.optimize.info.initializer;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 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;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public final class NonTrivialInstanceInitializerInfo extends InstanceInitializerInfo {
 
@@ -79,6 +82,16 @@
     return (data & RECEIVER_NEVER_ESCAPE_OUTSIDE_CONSTRUCTOR_CHAIN) != 0;
   }
 
+  @Override
+  public NonTrivialInstanceInitializerInfo rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLense lens) {
+    return new NonTrivialInstanceInitializerInfo(
+        data,
+        fieldInitializationInfos.rewrittenWithLens(appView, lens),
+        readSet.rewrittenWithLens(appView, lens),
+        lens.getRenamedMethodSignature(parent));
+  }
+
   public static class Builder {
 
     private final InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index cdec947..176db10 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -13,8 +13,10 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
 import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
 import com.android.tools.r8.ir.code.IRCode;
@@ -150,9 +152,14 @@
 
       Map<InvokeVirtual, InliningInfo> invokesToInline = new IdentityHashMap<>();
       for (InvokeVirtual invoke : code.<InvokeVirtual>instructions(Instruction::isInvokeVirtual)) {
-        DexType holder = invoke.getInvokedMethod().holder;
+        DexMethod invokedMethod = invoke.getInvokedMethod();
+        DexType holder = invokedMethod.holder;
         if (lambdaGroup.containsLambda(holder)) {
-          DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.method.holder);
+          // TODO(b/150685763): Check if we can use simpler lookup.
+          ResolutionResult resolution =
+              appView.appInfo().resolveMethod(holder, invokedMethod, false);
+          assert resolution.isSingleResolution();
+          DexEncodedMethod singleTarget = resolution.getSingleTarget();
           assert singleTarget != null;
           invokesToInline.put(invoke, new InliningInfo(singleTarget, singleTarget.method.holder));
         }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
index df6f812..f868855 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
@@ -160,7 +160,7 @@
     }
 
     @Override
-    int getInstanceInitializerSize(List<DexEncodedField> captures) {
+    int getInstanceInitializerMaxSize(List<DexEncodedField> captures) {
       return captures.size() + 2;
     }
 
@@ -170,7 +170,7 @@
         throws LambdaStructureError {
       if (!(instructions[index] instanceof com.android.tools.r8.code.InvokeDirect
               || instructions[index] instanceof com.android.tools.r8.code.InvokeDirectRange)
-          || instructions[index].getMethod() != kotlin.factory.objectMethods.constructor) {
+          || instructions[index].getMethod() != kotlin.factory.objectMembers.constructor) {
         throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
       }
       index++;
@@ -218,7 +218,7 @@
         DexMethod method,
         Position callerPosition) {
       super(lambdaGroupType, idField, fieldGenerator, method, callerPosition);
-      this.objectInitializer = factory.objectMethods.constructor;
+      this.objectInitializer = factory.objectMembers.constructor;
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
index b5f0eaf..b510e88 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
@@ -166,7 +166,7 @@
     }
 
     @Override
-    int getInstanceInitializerSize(List<DexEncodedField> captures) {
+    int getInstanceInitializerMaxSize(List<DexEncodedField> captures) {
       return captures.size() + 3;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaClassValidator.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaClassValidator.java
index f500c06..3e38f67 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaClassValidator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaClassValidator.java
@@ -124,7 +124,7 @@
     }
   }
 
-  abstract int getInstanceInitializerSize(List<DexEncodedField> captures);
+  abstract int getInstanceInitializerMaxSize(List<DexEncodedField> captures);
 
   abstract int validateInstanceInitializerEpilogue(
       com.android.tools.r8.code.Instruction[] instructions, int index) throws LambdaStructureError;
@@ -135,7 +135,7 @@
     com.android.tools.r8.code.Instruction[] instructions = code.asDexCode().instructions;
     int index = 0;
 
-    if (instructions.length != getInstanceInitializerSize(captures)) {
+    if (instructions.length > getInstanceInitializerMaxSize(captures)) {
       throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
     }
 
@@ -148,15 +148,21 @@
     assert index == instructions.length;
   }
 
-  private int validateInstanceInitializerParameterMapping(List<DexEncodedField> captures,
-      Instruction[] instructions, int index) throws LambdaStructureError {
+  private int validateInstanceInitializerParameterMapping(
+      List<DexEncodedField> captures, Instruction[] instructions, int index)
+      throws LambdaStructureError {
+    int dead = 0;
     int wideFieldsSeen = 0;
     for (DexEncodedField field : captures) {
+      if (field.getOptimizationInfo().isDead()) {
+        dead++;
+        continue;
+      }
       switch (field.field.type.toShorty()) {
         case 'Z':
           if (!(instructions[index] instanceof IputBoolean)
               || (instructions[index].getField() != field.field)
-              || (((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
+              || (((Format22c) instructions[index]).A != (index + 1 + dead + wideFieldsSeen))) {
             throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
           }
           break;
@@ -164,7 +170,7 @@
         case 'B':
           if (!(instructions[index] instanceof IputByte)
               || (instructions[index].getField() != field.field)
-              || (((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
+              || (((Format22c) instructions[index]).A != (index + 1 + dead + wideFieldsSeen))) {
             throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
           }
           break;
@@ -172,7 +178,7 @@
         case 'S':
           if (!(instructions[index] instanceof IputShort)
               || (instructions[index].getField() != field.field)
-              || (((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
+              || (((Format22c) instructions[index]).A != (index + 1 + dead + wideFieldsSeen))) {
             throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
           }
           break;
@@ -180,7 +186,7 @@
         case 'C':
           if (!(instructions[index] instanceof IputChar)
               || (instructions[index].getField() != field.field)
-              || (((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
+              || (((Format22c) instructions[index]).A != (index + 1 + dead + wideFieldsSeen))) {
             throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
           }
           break;
@@ -189,7 +195,7 @@
         case 'F':
           if (!(instructions[index] instanceof Iput)
               || (instructions[index].getField() != field.field)
-              || (((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
+              || (((Format22c) instructions[index]).A != (index + 1 + dead + wideFieldsSeen))) {
             throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
           }
           break;
@@ -198,7 +204,7 @@
         case 'D':
           if (!(instructions[index] instanceof IputWide)
               || (instructions[index].getField() != field.field)
-              || (((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
+              || (((Format22c) instructions[index]).A != (index + 1 + dead + wideFieldsSeen))) {
             throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
           }
           wideFieldsSeen++;
@@ -207,7 +213,7 @@
         case 'L':
           if (!(instructions[index] instanceof IputObject)
               || (instructions[index].getField() != field.field)
-              || (((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
+              || (((Format22c) instructions[index]).A != (index + 1 + dead + wideFieldsSeen))) {
             throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
           }
           break;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
index d61b6ae..f3d7cc6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
@@ -34,6 +34,7 @@
   public LibraryMethodOptimizer(AppView<?> appView) {
     this.appView = appView;
     register(new BooleanMethodOptimizer(appView));
+    register(new ObjectMethodOptimizer(appView));
     register(new ObjectsMethodOptimizer(appView));
     register(new StringMethodOptimizer(appView));
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectMethodOptimizer.java
new file mode 100644
index 0000000..1674baf
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectMethodOptimizer.java
@@ -0,0 +1,48 @@
+// 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.library;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import java.util.Set;
+
+public class ObjectMethodOptimizer implements LibraryMethodModelCollection {
+
+  private final DexItemFactory dexItemFactory;
+
+  ObjectMethodOptimizer(AppView<?> appView) {
+    this.dexItemFactory = appView.dexItemFactory();
+  }
+
+  @Override
+  public DexType getType() {
+    return dexItemFactory.objectType;
+  }
+
+  @Override
+  public void optimize(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      DexEncodedMethod singleTarget,
+      Set<Value> affectedValues) {
+    if (singleTarget.method == dexItemFactory.objectMembers.getClass) {
+      optimizeGetClass(instructionIterator, invoke);
+    }
+  }
+
+  private void optimizeGetClass(InstructionListIterator instructionIterator, InvokeMethod invoke) {
+    if ((!invoke.hasOutValue() || !invoke.outValue().hasAnyUsers())
+        && invoke.inValues().get(0).isNeverNull()) {
+      instructionIterator.removeOrReplaceByDebugLocalRead();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index 5af6c6f..8978657 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -591,7 +591,7 @@
             assert user.isInvokeDirect();
             if (ignoreSuperClassInitInvoke
                 && ListUtils.lastIndexMatching(invoke.inValues(), isAliasedValue) == 0
-                && methodReferenced == factory.objectMethods.constructor) {
+                && methodReferenced == factory.objectMembers.constructor) {
               // If we are inside candidate constructor and analyzing usages
               // of the receiver, we want to ignore invocations of superclass
               // constructor which will be removed after staticizing.
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
index 422bc2f..50885dc 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
@@ -253,20 +253,18 @@
       instructions.add(new CfReturn(ValueType.OBJECT));
       instructions.add(nullDest);
 
-      // This part is skipped if there is no reverse wrapper.
       // if (arg instanceOf ReverseWrapper) { return ((ReverseWrapper) arg).wrapperField};
-      if (reverseWrapperField != null) {
-        CfLabel unwrapDest = new CfLabel();
-        instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
-        instructions.add(new CfInstanceOf(reverseWrapperField.holder));
-        instructions.add(new CfIf(If.Type.EQ, ValueType.INT, unwrapDest));
-        instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
-        instructions.add(new CfCheckCast(reverseWrapperField.holder));
-        instructions.add(
-            new CfFieldInstruction(Opcodes.GETFIELD, reverseWrapperField, reverseWrapperField));
-        instructions.add(new CfReturn(ValueType.fromDexType(reverseWrapperField.type)));
-        instructions.add(unwrapDest);
-      }
+      assert reverseWrapperField != null;
+      CfLabel unwrapDest = new CfLabel();
+      instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
+      instructions.add(new CfInstanceOf(reverseWrapperField.holder));
+      instructions.add(new CfIf(If.Type.EQ, ValueType.INT, unwrapDest));
+      instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
+      instructions.add(new CfCheckCast(reverseWrapperField.holder));
+      instructions.add(
+          new CfFieldInstruction(Opcodes.GETFIELD, reverseWrapperField, reverseWrapperField));
+      instructions.add(new CfReturn(ValueType.fromDexType(reverseWrapperField.type)));
+      instructions.add(unwrapDest);
 
       // return new Wrapper(wrappedValue);
       instructions.add(new CfNew(wrapperField.holder));
@@ -317,6 +315,7 @@
 
   public static class APIConverterThrowRuntimeExceptionCfCodeProvider
       extends SyntheticCfCodeProvider {
+
     DexString message;
 
     public APIConverterThrowRuntimeExceptionCfCodeProvider(
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index d458662..7822316 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -33,7 +33,6 @@
 import com.android.tools.r8.graph.DexValue.DexValueMethodType;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
-import com.android.tools.r8.graph.DexValue.UnknownDexValue;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
@@ -429,8 +428,6 @@
     } else if (value instanceof DexValueType) {
       DexValueType ty = (DexValueType) value;
       visitor.visit(name, Type.getType(namingLens.lookupDescriptor(ty.value).toString()));
-    } else if (value instanceof UnknownDexValue) {
-      throw new Unreachable("writeAnnotationElement of UnknownDexValue");
     } else {
       visitor.visit(name, value.getBoxedValue());
     }
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index 941d7e2..a1cdd66 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -83,7 +83,21 @@
             .put(factory.boxedNumberType, factory.createType(addKotlinPrefix("Number;")))
             .put(factory.comparableType, factory.createType(addKotlinPrefix("Comparable;")))
             .put(factory.enumType, factory.createType(addKotlinPrefix("Enum;")))
-            // TODO(b/70169921): (Mutable) Collections?
+            // Collections
+            .put(factory.iteratorType, factory.createType(addKotlinPrefix("collections/Iterator;")))
+            .put(
+                factory.collectionType,
+                factory.createType(addKotlinPrefix("collections/Collection;")))
+            .put(factory.listType, factory.createType(addKotlinPrefix("collections/List;")))
+            .put(factory.setType, factory.createType(addKotlinPrefix("collections/Set;")))
+            .put(factory.mapType, factory.createType(addKotlinPrefix("collections/Map;")))
+            .put(
+                factory.listIteratorType,
+                factory.createType(addKotlinPrefix("collections/ListIterator;")))
+            .put(factory.iterableType, factory.createType(addKotlinPrefix("collections/Iterable;")))
+            .put(
+                factory.mapEntryType,
+                factory.createType(addKotlinPrefix("collections/Map$Entry;")))
             // .../jvm/functions/FunctionN -> .../FunctionN
             .putAll(
                 IntStream.rangeClosed(0, 22).boxed().collect(Collectors.toMap(
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 6085350..4a65b7c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -61,14 +61,16 @@
     List<KmType> superTypes = kmClass.getSupertypes();
     superTypes.clear();
     for (DexType itfType : clazz.interfaces.values) {
-      KmType kmType = toRenamedKmType(itfType, appView, lens);
+      // TODO(b/129925954): Use GenericSignature.ClassSignature#superInterfaceSignatures
+      KmType kmType = toRenamedKmType(itfType, null, appView, lens);
       if (kmType != null) {
         superTypes.add(kmType);
       }
     }
     assert clazz.superType != null;
     if (clazz.superType != appView.dexItemFactory().objectType) {
-      KmType kmTypeForSupertype = toRenamedKmType(clazz.superType, appView, lens);
+      // TODO(b/129925954): Use GenericSignature.ClassSignature#superClassSignature
+      KmType kmTypeForSupertype = toRenamedKmType(clazz.superType, null, appView, lens);
       if (kmTypeForSupertype != null) {
         superTypes.add(kmTypeForSupertype);
       }
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 b16d90a..ce685b2 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -3,12 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.kotlin.Kotlin.NAME;
 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.descriptorToKotlinClassifier;
 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;
 import static kotlinx.metadata.FlagsKt.flagsOf;
 
 import com.android.tools.r8.graph.AppView;
@@ -18,15 +18,24 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.TypeSignature;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.List;
+import java.util.function.Consumer;
 import kotlinx.metadata.KmConstructor;
 import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmProperty;
 import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeProjection;
 import kotlinx.metadata.KmValueParameter;
+import kotlinx.metadata.KmVariance;
 import kotlinx.metadata.jvm.JvmExtensionsKt;
 
 class KotlinMetadataSynthesizer {
@@ -88,8 +97,20 @@
     return descriptorToKotlinClassifier(renamedType.toDescriptorString());
   }
 
+  // TODO(b/148654451): Canonicalization?
   static KmType toRenamedKmType(
-      DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+      DexType type,
+      TypeSignature typeSignature,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens lens) {
+    if (typeSignature != null
+        && typeSignature.isFieldTypeSignature()
+        && typeSignature.asFieldTypeSignature().isClassTypeSignature()
+        && typeSignature.asFieldTypeSignature().asClassTypeSignature().type() == type) {
+      return toRenamedKmType(
+          typeSignature.asFieldTypeSignature().asClassTypeSignature(), appView, lens);
+    }
+
     String classifier = toRenamedClassifier(type, appView, lens);
     if (classifier == null) {
       return null;
@@ -98,12 +119,87 @@
     //   and/or why wiping out flags works for KmType but not KmFunction?!
     KmType kmType = new KmType(flagsOf());
     kmType.visitClass(classifier);
-    // TODO(b/70169921): Need to set arguments as type parameter.
-    //  E.g., for kotlin/Function1<P1, R>, P1 and R are recorded inside KmType.arguments, which
-    //  enables kotlinc to resolve `this` type and return type of the lambda.
+    // TODO(b/70169921): Can be generalized too, like ArrayTypeSignature.Converter ?
+    // E.g., java.lang.String[] -> KmType(kotlin/Array, KmTypeProjection(OUT, kotlin/String))
+    if (type.isArrayType() && !type.isPrimitiveArrayType()) {
+      DexType elementType = type.toArrayElementType(appView.dexItemFactory());
+      KmType argumentType = toRenamedKmType(elementType, null, appView, lens);
+      kmType.getArguments().add(new KmTypeProjection(KmVariance.OUT, argumentType));
+    }
     return kmType;
   }
 
+  static KmType toRenamedKmType(
+      ClassTypeSignature classTypeSignature,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens lens) {
+    return classTypeSignature.convert(
+        new ClassTypeSignatureToRenamedKmTypeConverter(appView, lens));
+  }
+
+  static class ClassTypeSignatureToRenamedKmTypeConverter
+      implements ClassTypeSignature.Converter<KmType> {
+
+    private final AppView<AppInfoWithLiveness> appView;
+    private final NamingLens lens;
+
+    ClassTypeSignatureToRenamedKmTypeConverter(
+        AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+      this.appView = appView;
+      this.lens = lens;
+    }
+
+    @Override
+    public KmType init() {
+      return new KmType(flagsOf());
+    }
+
+    @Override
+    public KmType visitType(DexType type, KmType result) {
+      if (result == null) {
+        return result;
+      }
+      String classifier = toRenamedClassifier(type, appView, lens);
+      if (classifier == null) {
+        return null;
+      }
+      result.visitClass(classifier);
+      return result;
+    }
+
+    @Override
+    public KmType visitTypeArgument(FieldTypeSignature typeArgument, KmType result) {
+      if (result == null) {
+        return result;
+      }
+      if (typeArgument.isClassTypeSignature()) {
+        KmType argumentType = typeArgument.asClassTypeSignature().convert(this);
+        result.getArguments().add(new KmTypeProjection(KmVariance.INVARIANT, argumentType));
+      }
+      // TODO(b/70169921): for TypeVariableSignature, there is KmType::visitTypeParameter.
+      return result;
+    }
+
+    @Override
+    public KmType visitInnerTypeSignature(ClassTypeSignature innerTypeSignature, KmType result) {
+      // Do nothing
+      return result;
+    }
+  }
+
+  private static KmType setRenamedKmType(
+      DexType type,
+      TypeSignature signature,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens lens,
+      Consumer<KmType> consumer) {
+    KmType renamedKmType = toRenamedKmType(type, signature, appView, lens);
+    if (renamedKmType != null) {
+      consumer.accept(renamedKmType);
+    }
+    return renamedKmType;
+  }
+
   static KmConstructor toRenamedKmConstructor(
       DexEncodedMethod method,
       AppView<AppInfoWithLiveness> appView,
@@ -115,8 +211,9 @@
     }
     KmConstructor kmConstructor = new KmConstructor(method.accessFlags.getAsKotlinFlags());
     JvmExtensionsKt.setSignature(kmConstructor, toJvmMethodSignature(method.method));
+    MethodTypeSignature signature = GenericSignature.Parser.toMethodTypeSignature(method, appView);
     List<KmValueParameter> parameters = kmConstructor.getValueParameters();
-    if (!populateKmValueParameters(parameters, method, appView, lens)) {
+    if (!populateKmValueParameters(parameters, method, signature, appView, lens)) {
       return null;
     }
     return kmConstructor;
@@ -145,23 +242,35 @@
             : method.accessFlags.getAsKotlinFlags();
     KmFunction kmFunction = new KmFunction(flag, renamedMethod.name.toString());
     JvmExtensionsKt.setSignature(kmFunction, toJvmMethodSignature(renamedMethod));
-    KmType kmReturnType = toRenamedKmType(method.method.proto.returnType, appView, lens);
+
+    // TODO(b/129925954): Should this be integrated as part of DexDefinition instead of parsing it
+    //  on demand? That may require utils to map internal encoding of signature back to
+    //  corresponding backend definitions, like DexAnnotation (DEX) or Signature attribute (CF).
+    MethodTypeSignature signature = GenericSignature.Parser.toMethodTypeSignature(method, appView);
+
+    DexProto proto = method.method.proto;
+    DexType returnType = proto.returnType;
+    TypeSignature returnSignature = signature.returnType().typeSignature();
+    KmType kmReturnType = setRenamedKmType(
+        returnType, returnSignature, appView, lens, kmFunction::setReturnType);
     if (kmReturnType == null) {
       return null;
     }
-    kmFunction.setReturnType(kmReturnType);
+
     if (method.isKotlinExtensionFunction()) {
-      assert method.method.proto.parameters.values.length > 0
+      assert proto.parameters.values.length > 0
           : method.method.toSourceString();
-      KmType kmReceiverType =
-          toRenamedKmType(method.method.proto.parameters.values[0], appView, lens);
+      DexType receiverType = proto.parameters.values[0];
+      TypeSignature receiverSignature = signature.getParameterTypeSignature(0);
+      KmType kmReceiverType = setRenamedKmType(
+          receiverType, receiverSignature, appView, lens, kmFunction::setReceiverParameterType);
       if (kmReceiverType == null) {
         return null;
       }
-      kmFunction.setReceiverParameterType(kmReceiverType);
     }
+
     List<KmValueParameter> parameters = kmFunction.getValueParameters();
-    if (!populateKmValueParameters(parameters, method, appView, lens)) {
+    if (!populateKmValueParameters(parameters, method, signature, appView, lens)) {
       return null;
     }
     return kmFunction;
@@ -170,6 +279,7 @@
   private static boolean populateKmValueParameters(
       List<KmValueParameter> parameters,
       DexEncodedMethod method,
+      MethodTypeSignature signature,
       AppView<AppInfoWithLiveness> appView,
       NamingLens lens) {
     boolean isExtension = method.isKotlinExtensionFunction();
@@ -179,9 +289,15 @@
       String parameterName = debugLocalInfo != null ? debugLocalInfo.name.toString() : ("p" + i);
       KotlinValueParameterInfo valueParameterInfo =
           method.getKotlinMemberInfo().getValueParameterInfo(isExtension ? i - 1 : i);
+      TypeSignature parameterTypeSignature = signature.getParameterTypeSignature(i);
       KmValueParameter kmValueParameter =
           toRewrittenKmValueParameter(
-              valueParameterInfo, parameterType, parameterName, appView, lens);
+              valueParameterInfo,
+              parameterType,
+              parameterTypeSignature,
+              parameterName,
+              appView,
+              lens);
       if (kmValueParameter == null) {
         return false;
       }
@@ -193,28 +309,37 @@
   private static KmValueParameter toRewrittenKmValueParameter(
       KotlinValueParameterInfo valueParameterInfo,
       DexType parameterType,
+      TypeSignature parameterTypeSignature,
       String candidateParameterName,
       AppView<AppInfoWithLiveness> appView,
       NamingLens lens) {
-    KmType kmParamType = toRenamedKmType(parameterType, appView, lens);
-    if (kmParamType == null) {
-      return null;
-    }
     int flag = valueParameterInfo != null ? valueParameterInfo.flag : flagsOf();
     String name = valueParameterInfo != null ? valueParameterInfo.name : candidateParameterName;
     KmValueParameter kmValueParameter = new KmValueParameter(flag, name);
-    kmValueParameter.setType(kmParamType);
+    KmType kmParamType = setRenamedKmType(
+        parameterType, parameterTypeSignature, appView, lens, kmValueParameter::setType);
+    if (kmParamType == null) {
+      return null;
+    }
+    if (valueParameterInfo != null) {
+      JvmExtensionsKt.getAnnotations(kmParamType).addAll(valueParameterInfo.annotations);
+    }
+
     if (valueParameterInfo != null && valueParameterInfo.isVararg) {
       if (!parameterType.isArrayType()) {
         return null;
       }
-      DexType elementType = parameterType.toBaseType(appView.dexItemFactory());
-      KmType kmElementType = toRenamedKmType(elementType, appView, lens);
+      DexType elementType = parameterType.toArrayElementType(appView.dexItemFactory());
+      TypeSignature elementSignature =
+          parameterTypeSignature != null
+              ? parameterTypeSignature.toArrayElementTypeSignature(appView) : null;
+      KmType kmElementType = setRenamedKmType(
+          elementType, elementSignature, appView, lens, kmValueParameter::setVarargElementType);
       if (kmElementType == null) {
         return null;
       }
-      kmValueParameter.setVarargElementType(kmElementType);
     }
+
     return kmValueParameter;
   }
 
@@ -363,10 +488,10 @@
         if (canChangePropertyName && renamedField.name != field.field.name) {
           renamedPropertyName = renamedField.name.toString();
         }
-        kmPropertyType = toRenamedKmType(field.field.type, appView, lens);
-        if (kmPropertyType != null) {
-          kmProperty.setReturnType(kmPropertyType);
-        }
+        FieldTypeSignature signature =
+            GenericSignature.Parser.toFieldTypeSignature(field, appView);
+        kmPropertyType =
+            setRenamedKmType(field.field.type, signature, appView, lens, kmProperty::setReturnType);
         JvmExtensionsKt.setFieldSignature(kmProperty, toJvmFieldSignature(renamedField));
       }
 
@@ -376,26 +501,31 @@
       }
 
       if (criteria == GetterSetterCriteria.MET) {
-        assert getter != null
+        assert getter != null && getter.method.proto.parameters.size() == (isExtension ? 1 : 0)
             : "checkGetterCriteria: " + this.toString();
+        MethodTypeSignature signature =
+            GenericSignature.Parser.toMethodTypeSignature(getter, appView);
         if (isExtension) {
-          assert getter.method.proto.parameters.size() == 1
-              : "checkGetterCriteria: " + this.toString();
-          kmReceiverType = toRenamedKmType(getter.method.proto.parameters.values[0], appView, lens);
-          if (kmReceiverType != null) {
-            kmProperty.setReceiverParameterType(kmReceiverType);
-          }
+          TypeSignature receiverSignature = signature.getParameterTypeSignature(0);
+          kmReceiverType =
+              setRenamedKmType(
+                  getter.method.proto.parameters.values[0],
+                  receiverSignature,
+                  appView,
+                  lens,
+                  kmProperty::setReceiverParameterType);
         }
+
+        DexType returnType = getter.method.proto.returnType;
+        TypeSignature returnSignature = signature.returnType().typeSignature();
         if (kmPropertyType == null) {
           // The property type is not set yet.
-          kmPropertyType = toRenamedKmType(getter.method.proto.returnType, appView, lens);
-          if (kmPropertyType != null) {
-            kmProperty.setReturnType(kmPropertyType);
-          }
+          kmPropertyType = setRenamedKmType(
+              returnType, returnSignature, appView, lens, kmProperty::setReturnType);
         } else {
           // If property type is set already (via backing field), make sure it's consistent.
           KmType kmPropertyTypeFromGetter =
-              toRenamedKmType(getter.method.proto.returnType, appView, lens);
+              toRenamedKmType(returnType, returnSignature, appView, lens);
           if (!getDescriptorFromKmType(kmPropertyType)
               .equals(getDescriptorFromKmType(kmPropertyTypeFromGetter))) {
             return null;
@@ -420,40 +550,45 @@
       }
 
       if (criteria == GetterSetterCriteria.MET) {
-        assert setter != null && setter.method.proto.parameters.size() >= 1
+        assert setter != null && setter.method.proto.parameters.size() == (isExtension ? 2 : 1)
             : "checkSetterCriteria: " + this.toString();
+        MethodTypeSignature signature =
+            GenericSignature.Parser.toMethodTypeSignature(setter, appView);
         if (isExtension) {
-          assert setter.method.proto.parameters.size() == 2
-              : "checkSetterCriteria: " + this.toString();
+          DexType receiverType = setter.method.proto.parameters.values[0];
+          TypeSignature receiverSignature = signature.getParameterTypeSignature(0);
           if (kmReceiverType == null) {
             kmReceiverType =
-                toRenamedKmType(setter.method.proto.parameters.values[0], appView, lens);
-            if (kmReceiverType != null) {
-              kmProperty.setReceiverParameterType(kmReceiverType);
-            }
+                setRenamedKmType(
+                    receiverType,
+                    receiverSignature,
+                    appView,
+                    lens,
+                    kmProperty::setReceiverParameterType);
           } else {
             // If the receiver type for the extension property is set already (via getter),
             // make sure it's consistent.
             KmType kmReceiverTypeFromSetter =
-                toRenamedKmType(setter.method.proto.parameters.values[0], appView, lens);
+                toRenamedKmType(receiverType, receiverSignature, appView, lens);
             if (!getDescriptorFromKmType(kmReceiverType)
                 .equals(getDescriptorFromKmType(kmReceiverTypeFromSetter))) {
               return null;
             }
           }
         }
+
         int valueIndex = isExtension ? 1 : 0;
         DexType valueType = setter.method.proto.parameters.values[valueIndex];
+        TypeSignature valueSignature = signature.getParameterTypeSignature(valueIndex);
         if (kmPropertyType == null) {
           // The property type is not set yet.
-          kmPropertyType = toRenamedKmType(valueType, appView, lens);
-          if (kmPropertyType != null) {
-            kmProperty.setReturnType(kmPropertyType);
-          }
+          kmPropertyType =
+              setRenamedKmType(valueType, valueSignature, appView, lens, kmProperty::setReturnType);
         } else {
           // If property type is set already (via either backing field or getter),
           // make sure it's consistent.
-          KmType kmPropertyTypeFromSetter = toRenamedKmType(valueType, appView, lens);
+          KmType kmPropertyTypeFromSetter =
+              toRenamedKmType(valueType, valueSignature, appView, lens);
           if (!getDescriptorFromKmType(kmPropertyType)
               .equals(getDescriptorFromKmType(kmPropertyTypeFromSetter))) {
             return null;
@@ -461,8 +596,8 @@
         }
         KotlinValueParameterInfo valueParameterInfo =
             setter.getKotlinMemberInfo().getValueParameterInfo(valueIndex);
-        KmValueParameter kmValueParameter =
-            toRewrittenKmValueParameter(valueParameterInfo, valueType, "value", appView, lens);
+        KmValueParameter kmValueParameter = toRewrittenKmValueParameter(
+            valueParameterInfo, valueType, valueSignature, "value", appView, lens);
         if (kmValueParameter != null) {
           kmProperty.setSetterParameter(kmValueParameter);
         }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
index 95d91f8..0fa9f39 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
@@ -3,7 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import kotlinx.metadata.KmAnnotation;
+import kotlinx.metadata.KmType;
 import kotlinx.metadata.KmValueParameter;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
 
 // Provides access to Kotlin information about value parameter.
 class KotlinValueParameterInfo {
@@ -14,17 +19,24 @@
   final int flag;
   // Indicates whether the formal parameter is originally `vararg`.
   final boolean isVararg;
+  // TODO(b/70169921): Should we treat them as normal annotations? E.g., shrinking and renaming?
+  // Annotations on the type of value parameter.
+  final List<KmAnnotation> annotations;
 
-  private KotlinValueParameterInfo(String name, int flag, boolean isVararg) {
+  private KotlinValueParameterInfo(
+      String name, int flag, boolean isVararg, List<KmAnnotation> annotations) {
     this.name = name;
     this.flag = flag;
     this.isVararg = isVararg;
+    this.annotations = annotations;
   }
 
   static KotlinValueParameterInfo fromKmValueParameter(KmValueParameter kmValueParameter) {
+    KmType kmType = kmValueParameter.getType();
     return new KotlinValueParameterInfo(
         kmValueParameter.getName(),
         kmValueParameter.getFlags(),
-        kmValueParameter.getVarargElementType() != null);
+        kmValueParameter.getVarargElementType() != null,
+        kmType != null ? JvmExtensionsKt.getAnnotations(kmType) : ImmutableList.of());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/ApplyMappingError.java b/src/main/java/com/android/tools/r8/naming/ApplyMappingError.java
index 7b76acb..2ef940e 100644
--- a/src/main/java/com/android/tools/r8/naming/ApplyMappingError.java
+++ b/src/main/java/com/android/tools/r8/naming/ApplyMappingError.java
@@ -14,8 +14,9 @@
       "'%s' cannot be mapped to '%s' because it is in conflict with an existing ";
   private static final String EXISTING_MESSAGE_END =
       ". This usually happens when compiling a test application against a source application and "
-          + "having short generic names in the test application. Try giving '%s' a more specific "
-          + "name or add a keep rule to keep '%s'.";
+          + "there are used classes in the test that was not given a -keep rule when compiling the "
+          + "app. Try either renaming '%s' such that it will not collide or add a keep rule to "
+          + "keep '%s'.";
 
   protected static final String EXISTING_CLASS_MESSAGE =
       EXISTING_MESSAGE_START + "class with the same name" + EXISTING_MESSAGE_END;
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 2e68b4f..9f72270 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -296,7 +296,9 @@
           parentState = getOrAllocateMethodNamingStates(holder.superType);
         }
       }
-      MethodReservationState<?> reservationState = reservationStates.get(type);
+      // There can be gaps in the reservation states if a library class extends a program class.
+      // See b/150325706 for more information.
+      MethodReservationState<?> reservationState = findReservationStateInHierarchy(type);
       assert reservationState != null : "Could not find reservation state for " + type.toString();
       namingState = parentState.createChild(reservationState);
       namingStates.put(type, namingState);
@@ -304,6 +306,21 @@
     return namingState;
   }
 
+  private MethodReservationState<?> findReservationStateInHierarchy(DexType type) {
+    MethodReservationState<?> reservationState = reservationStates.get(type);
+    if (reservationState != null) {
+      return reservationState;
+    }
+    // If we cannot find the reservation state, which is a result from a library class extending
+    // a program class. The gap is tracked in the frontier state.
+    assert frontiers.containsKey(type);
+    DexType frontierType = frontiers.get(type);
+    reservationState = reservationStates.get(frontierType);
+    assert reservationState != null
+        : "Could not find reservation state for frontier type " + frontierType.toString();
+    return reservationState;
+  }
+
   // Shuffles the given methods if assertions are enabled and deterministic debugging is disabled.
   // Used to ensure that the generated output is deterministic.
   private static Iterable<DexEncodedMethod> shuffleMethods(
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index 734c0b0..609feaa 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -34,7 +34,7 @@
 
 class MinifiedRenaming extends NamingLens {
 
-  private final AppView<?> appView;
+  final AppView<?> appView;
   private final Map<String, String> packageRenaming;
   private final Map<DexItem, DexString> renaming = new IdentityHashMap<>();
 
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index f1a7d84..d1c195f 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -41,6 +41,8 @@
  */
 public abstract class NamingLens {
 
+  protected boolean isSortingBeforeWriting;
+
   public abstract String lookupPackageName(String packageName);
 
   public abstract DexString lookupDescriptor(DexType type);
@@ -163,6 +165,10 @@
     return true;
   }
 
+  public void setIsSortingBeforeWriting(boolean isSorting) {
+    isSortingBeforeWriting = isSorting;
+  }
+
   private static class IdentityLens extends NamingLens {
 
     private IdentityLens() {
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 471d66f..9f32739 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -36,6 +36,8 @@
 import com.google.common.collect.Maps;
 import java.util.ArrayDeque;
 import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
@@ -87,6 +89,7 @@
   public NamingLens run(ExecutorService executorService, Timing timing) throws ExecutionException {
 
     ArrayDeque<Map<DexReference, MemberNaming>> nonPrivateMembers = new ArrayDeque<>();
+    Set<DexReference> notMappedReferences = new HashSet<>();
 
     timing.begin("MappingInterfaces");
     Set<DexClass> interfaces = new TreeSet<>((a, b) -> a.type.slowCompareTo(b.type));
@@ -96,7 +99,7 @@
           if (dexClass.isInterface()) {
             // Only visit top level interfaces because computeMapping will visit the hierarchy.
             if (dexClass.interfaces.isEmpty()) {
-              computeMapping(dexClass.type, nonPrivateMembers);
+              computeMapping(dexClass.type, nonPrivateMembers, notMappedReferences);
             }
             interfaces.add(dexClass);
           }
@@ -113,7 +116,7 @@
         subType -> {
           DexClass dexClass = appView.definitionFor(subType);
           if (dexClass != null && !dexClass.isInterface()) {
-            computeMapping(subType, nonPrivateMembers);
+            computeMapping(subType, nonPrivateMembers, notMappedReferences);
           }
         });
     assert nonPrivateMembers.isEmpty();
@@ -158,7 +161,9 @@
 
     appView.options().reporter.failIfPendingErrors();
 
-    NamingLens lens = new MinifiedRenaming(appView, classRenaming, methodRenaming, fieldRenaming);
+    NamingLens lens =
+        new ProguardMapMinifiedRenaming(
+            appView, classRenaming, methodRenaming, fieldRenaming, notMappedReferences);
 
     timing.begin("MinifyIdentifiers");
     new IdentifierMinifier(appView, lens).run(executorService);
@@ -171,7 +176,10 @@
     return lens;
   }
 
-  private void computeMapping(DexType type, Deque<Map<DexReference, MemberNaming>> buildUpNames) {
+  private void computeMapping(
+      DexType type,
+      Deque<Map<DexReference, MemberNaming>> buildUpNames,
+      Set<DexReference> notMappedReferences) {
     ClassNamingForMapApplier classNaming = seedMapper.getClassNaming(type);
     DexClass dexClass = appView.definitionFor(type);
 
@@ -189,16 +197,15 @@
       if (dexClass != null) {
         KotlinMetadataRewriter.removeKotlinMetadataFromRenamedClass(appView, dexClass);
       }
-
       classNaming.forAllMemberNaming(
           memberNaming -> addMemberNamings(type, memberNaming, nonPrivateMembers, false));
     } else {
       // We have to ensure we do not rename to an existing member, that cannot be renamed.
       if (dexClass == null || !appView.options().isMinifying()) {
-        checkAndAddMappedNames(type, type.descriptor, Position.UNKNOWN);
+        notMappedReferences.add(type);
       } else if (appView.options().isMinifying()
           && appView.rootSet().mayNotBeMinified(type, appView)) {
-        checkAndAddMappedNames(type, type.descriptor, Position.UNKNOWN);
+        notMappedReferences.add(type);
       }
     }
 
@@ -247,12 +254,14 @@
       buildUpNames.addLast(nonPrivateMembers);
       appView
           .appInfo()
-          .forAllImmediateExtendsSubtypes(type, subType -> computeMapping(subType, buildUpNames));
+          .forAllImmediateExtendsSubtypes(
+              type, subType -> computeMapping(subType, buildUpNames, notMappedReferences));
       buildUpNames.removeLast();
     } else {
       appView
           .appInfo()
-          .forAllImmediateExtendsSubtypes(type, subType -> computeMapping(subType, buildUpNames));
+          .forAllImmediateExtendsSubtypes(
+              type, subType -> computeMapping(subType, buildUpNames, notMappedReferences));
     }
   }
 
@@ -553,4 +562,52 @@
       // reporter.error(applyMappingError);
     }
   }
+
+  public static class ProguardMapMinifiedRenaming extends MinifiedRenaming {
+
+    private final Set<DexReference> unmappedReferences;
+    private final Map<DexString, DexType> classRenamingsMappingToDifferentName;
+
+    ProguardMapMinifiedRenaming(
+        AppView<?> appView,
+        ClassRenaming classRenaming,
+        MethodRenaming methodRenaming,
+        FieldRenaming fieldRenaming,
+        Set<DexReference> unmappedReferences) {
+      super(appView, classRenaming, methodRenaming, fieldRenaming);
+      this.unmappedReferences = unmappedReferences;
+      classRenamingsMappingToDifferentName = new HashMap<>();
+      classRenaming.classRenaming.forEach(
+          (type, dexString) -> {
+            if (type.descriptor != dexString) {
+              classRenamingsMappingToDifferentName.put(dexString, type);
+            }
+          });
+    }
+
+    @Override
+    public DexString lookupDescriptor(DexType type) {
+      if (!isSortingBeforeWriting) {
+        checkForUseOfNotMappedReference(type);
+      }
+      return super.lookupDescriptor(type);
+    }
+
+    private void checkForUseOfNotMappedReference(DexType type) {
+      if (unmappedReferences.contains(type)
+          && classRenamingsMappingToDifferentName.containsKey(type.descriptor)) {
+        // Type is an unmapped reference and there is a mapping from some other type to this one.
+        // We are emitting a warning here, since this will generally be undesired behavior.
+        DexType mappedType = classRenamingsMappingToDifferentName.get(type.descriptor);
+        appView
+            .options()
+            .reporter
+            .error(
+                ApplyMappingError.mapToExistingClass(
+                    mappedType.toString(), type.toSourceString(), Position.UNKNOWN));
+        // Remove the type to ensure us only reporting the error once.
+        unmappedReferences.remove(type);
+      }
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index 317cb3b..b319c6d 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.ir.optimize.MethodPoolCollection;
 import com.android.tools.r8.optimize.PublicizerLense.PublicizedLenseBuilder;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Timing;
 import java.util.LinkedHashSet;
 import java.util.Set;
@@ -110,6 +111,8 @@
       return false;
     }
     if (!accessFlags.isPrivate() || appView.dexItemFactory().isConstructor(encodedMethod.method)) {
+      // TODO(b/150589374): This should check for dispatch targets or just abandon in
+      //  package-private.
       accessFlags.promoteToPublic();
       return false;
     }
@@ -142,6 +145,7 @@
       // Although the current method became public, it surely has the single virtual target.
       encodedMethod.method.setSingleVirtualMethodCache(
           encodedMethod.method.holder, encodedMethod);
+      encodedMethod.setLibraryMethodOverride(OptionalBool.FALSE);
       return true;
     }
 
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 a1277ef..44367e0 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -5,11 +5,13 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.graph.GraphLense.rewriteReferenceKeys;
+import static com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult.isOverriding;
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -18,7 +20,6 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
@@ -28,10 +29,14 @@
 import com.android.tools.r8.graph.FieldAccessInfoImpl;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.graph.InstantiatedSubTypeInfo;
+import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
+import com.android.tools.r8.graph.LookupTarget;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
 import com.android.tools.r8.graph.PresortedComparable;
 import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
@@ -39,11 +44,10 @@
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.PredicateSet;
 import com.android.tools.r8.utils.SetUtils;
-import com.google.common.collect.ImmutableList;
+import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.ImmutableSortedSet.Builder;
-import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
@@ -64,7 +68,7 @@
 import java.util.stream.Collectors;
 
 /** Encapsulates liveness and reachability information for an application. */
-public class AppInfoWithLiveness extends AppInfoWithSubtyping {
+public class AppInfoWithLiveness extends AppInfoWithSubtyping implements InstantiatedSubTypeInfo {
 
   /** Set of types that are mentioned in the program, but for which no definition exists. */
   private final Set<DexType> missingTypes;
@@ -695,6 +699,11 @@
     return result;
   }
 
+  public EnumValueInfoMapCollection getEnumValueInfoMapCollection() {
+    assert checkIfObsolete();
+    return enumValueInfoMaps;
+  }
+
   public EnumValueInfoMap getEnumValueInfoMap(DexType enumType) {
     assert checkIfObsolete();
     return enumValueInfoMaps.getEnumValueInfoMap(enumType);
@@ -850,7 +859,9 @@
 
   public boolean mayPropagateValueFor(DexReference reference) {
     assert checkIfObsolete();
-    return !isPinned(reference) && !neverPropagateValue.contains(reference);
+    return options().enableValuePropagation
+        && !isPinned(reference)
+        && !neverPropagateValue.contains(reference);
   }
 
   private boolean isLibraryOrClasspathField(DexEncodedField field) {
@@ -883,7 +894,7 @@
   }
 
   @Override
-  protected boolean hasAnyInstantiatedLambdas(DexProgramClass clazz) {
+  public boolean hasAnyInstantiatedLambdas(DexProgramClass clazz) {
     assert checkIfObsolete();
     return instantiatedLambdas.contains(clazz.type);
   }
@@ -1076,7 +1087,10 @@
   }
 
   public DexEncodedMethod lookupSingleTarget(
-      Type type, DexMethod target, DexType invocationContext) {
+      Type type,
+      DexMethod target,
+      DexType invocationContext,
+      LibraryModeledPredicate modeledPredicate) {
     assert checkIfObsolete();
     DexType holder = target.holder;
     if (!holder.isClassType()) {
@@ -1084,9 +1098,9 @@
     }
     switch (type) {
       case VIRTUAL:
-        return lookupSingleVirtualTarget(target, invocationContext);
+        return lookupSingleVirtualTarget(target, invocationContext, false, modeledPredicate);
       case INTERFACE:
-        return lookupSingleInterfaceTarget(target, invocationContext);
+        return lookupSingleVirtualTarget(target, invocationContext, true, modeledPredicate);
       case DIRECT:
         return lookupDirectTarget(target, invocationContext);
       case STATIC:
@@ -1124,318 +1138,129 @@
   }
 
   /** For mapping invoke virtual instruction to single target method. */
-  public DexEncodedMethod lookupSingleVirtualTarget(DexMethod method, DexType invocationContext) {
+  public DexEncodedMethod lookupSingleVirtualTarget(
+      DexMethod method, DexType invocationContext, boolean isInterface) {
     assert checkIfObsolete();
-    return lookupSingleVirtualTarget(method, invocationContext, method.holder, null);
+    return lookupSingleVirtualTarget(
+        method, invocationContext, isInterface, type -> false, method.holder, null);
+  }
+
+  /** For mapping invoke virtual instruction to single target method. */
+  public DexEncodedMethod lookupSingleVirtualTarget(
+      DexMethod method,
+      DexType invocationContext,
+      boolean isInterface,
+      LibraryModeledPredicate modeledPredicate) {
+    assert checkIfObsolete();
+    return lookupSingleVirtualTarget(
+        method, invocationContext, isInterface, modeledPredicate, method.holder, null);
   }
 
   public DexEncodedMethod lookupSingleVirtualTarget(
       DexMethod method,
       DexType invocationContext,
+      boolean isInterface,
+      LibraryModeledPredicate modeledPredicate,
       DexType refinedReceiverType,
       ClassTypeLatticeElement receiverLowerBoundType) {
     assert checkIfObsolete();
-    // TODO: replace invocationContext by a DexProgramClass typed formal.
+    assert refinedReceiverType != null;
+
     DexProgramClass invocationClass = asProgramClassOrNull(definitionFor(invocationContext));
     assert invocationClass != null;
 
-    ResolutionResult resolutionResult = resolveMethodOnClass(method.holder, method);
-    if (!resolutionResult.isAccessibleForVirtualDispatchFrom(invocationClass, this)) {
+    if (!refinedReceiverType.isClassType()) {
+      // The refined receiver is not of class type and we will not be able to find a single target
+      // (it is either primitive or array).
       return null;
     }
-
-    DexEncodedMethod topTarget = resolutionResult.getSingleTarget();
-    if (topTarget == null) {
-      // A null target represents a valid target without a known defintion, ie, array clone().
+    DexClass refinedReceiverClass = definitionFor(refinedReceiverType);
+    if (refinedReceiverClass == null) {
+      // The refined receiver is not defined in the program and we cannot determine the target.
       return null;
     }
-
-    // If the target is a private method, then the invocation is a direct access to a nest member.
-    if (topTarget.isPrivateMethod()) {
-      return topTarget;
+    SingleResolutionResult resolution =
+        resolveMethod(method.holder, method, isInterface).asSingleResolution();
+    if (resolution == null
+        || !resolution.isAccessibleForVirtualDispatchFrom(invocationClass, this)) {
+      return null;
     }
-
+    // If the method is modeled, return the resolution.
+    if (modeledPredicate.isModeled(resolution.getResolvedHolder().type)) {
+      if (resolution.getResolvedHolder().isFinal()
+          || (resolution.getResolvedMethod().isFinal()
+              && resolution.getResolvedMethod().accessFlags.isPublic())) {
+        return resolution.getResolvedMethod();
+      }
+    }
     // If the lower-bound on the receiver type is the same as the upper-bound, then we have exact
     // runtime type information. In this case, the invoke will dispatch to the resolution result
     // from the runtime type of the receiver.
-    if (receiverLowerBoundType != null) {
-      if (receiverLowerBoundType.getClassType() == refinedReceiverType) {
-        if (resolutionResult.isSingleResolution() && resolutionResult.isVirtualTarget()) {
-          ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method);
-          if (refinedResolutionResult.isSingleResolution()
-              && refinedResolutionResult.isVirtualTarget()) {
-            return validateSingleVirtualTarget(
-                refinedResolutionResult.getSingleTarget(), resolutionResult.getSingleTarget());
-          }
+    if (receiverLowerBoundType != null
+        && receiverLowerBoundType.getClassType() == refinedReceiverType) {
+      if (refinedReceiverClass.isProgramClass()) {
+        DexClassAndMethod clazzAndMethod =
+            resolution.lookupVirtualDispatchTarget(refinedReceiverClass.asProgramClass(), this);
+        if (clazzAndMethod == null || isPinned(clazzAndMethod.getMethod().method)) {
+          // TODO(b/150640456): We should maybe only consider program methods.
+          return null;
+        }
+        return clazzAndMethod.getMethod();
+      } else {
+        // TODO(b/150640456): We should maybe only consider program methods.
+        // If we resolved to a method on the refined receiver in the library, then we report the
+        // method as a single target as well. This is a bit iffy since the library could change
+        // implementation, but we use this for library modelling.
+        DexEncodedMethod targetOnReceiver = refinedReceiverClass.lookupVirtualMethod(method);
+        if (targetOnReceiver != null
+            && isOverriding(resolution.getResolvedMethod(), targetOnReceiver)) {
+          return targetOnReceiver;
         }
         return null;
-      } else {
-        // We should never hit the case at the moment, but if we start tracking more precise lower-
-        // bound type information, we should handle this case as well.
       }
     }
+    if (refinedReceiverClass.isNotProgramClass()) {
+      // The refined receiver is not defined in the program and we cannot determine the target.
+      return null;
+    }
+    DexClass resolvedHolder = resolution.getResolvedHolder();
+    // TODO(b/148769279): Disable lookup single target on lambda's for now.
+    if (resolvedHolder.isInterface()
+        && resolvedHolder.isProgramClass()
+        && hasAnyInstantiatedLambdas(resolvedHolder.asProgramClass())) {
+      return null;
+    }
 
-    // This implements the logic from
-    // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html#jvms-6.5.invokevirtual
-    assert method != null;
-    assert isSubtype(refinedReceiverType, method.holder);
-    if (method.holder.isArrayType()) {
-      return null;
-    }
-    DexClass holder = definitionFor(method.holder);
-    if (holder == null || holder.isNotProgramClass()) {
-      return null;
-    }
-    assert !holder.isInterface();
-    boolean refinedReceiverIsStrictSubType = refinedReceiverType != method.holder;
-    DexProgramClass refinedHolder =
-        (refinedReceiverIsStrictSubType ? definitionFor(refinedReceiverType) : holder)
-            .asProgramClass();
-    if (refinedHolder == null) {
-      return null;
-    }
-    assert !refinedHolder.isInterface();
     if (method.isSingleVirtualMethodCached(refinedReceiverType)) {
       return method.getSingleVirtualMethodCache(refinedReceiverType);
     }
-    // First get the target for the holder type.
-    ResolutionResult topMethod = resolveMethodOnClass(holder, method);
-    // We might hit none or multiple targets. Both make this fail at runtime.
-    if (!topMethod.isSingleResolution() || !topMethod.isVirtualTarget()) {
-      method.setSingleVirtualMethodCache(refinedReceiverType, null);
-      return null;
-    }
-    // Now, resolve the target with the refined receiver type.
-    ResolutionResult refinedResolutionResult =
-        refinedReceiverIsStrictSubType ? resolveMethodOnClass(refinedHolder, method) : topMethod;
-    DexEncodedMethod topSingleTarget = refinedResolutionResult.getSingleTarget();
-    DexClass topHolder = definitionFor(topSingleTarget.method.holder);
-    // We need to know whether the top method is from an interface, as that would allow it to be
-    // shadowed by a default method from an interface further down.
-    boolean topIsFromInterface = topHolder.isInterface();
-    // Now look at all subtypes and search for overrides.
-    DexEncodedMethod result =
-        validateSingleVirtualTarget(
-            findSingleTargetFromSubtypes(
-                refinedHolder,
-                method,
-                topSingleTarget,
-                !refinedHolder.accessFlags.isAbstract(),
-                topIsFromInterface),
-            topMethod.getSingleTarget());
-    assert result != DexEncodedMethod.SENTINEL;
-    method.setSingleVirtualMethodCache(refinedReceiverType, result);
-    return result;
-  }
 
-  /**
-   * Computes which methods overriding <code>method</code> are visible for the subtypes of type.
-   *
-   * <p><code>candidate</code> is the definition further up the hierarchy that is visible from the
-   * subtypes. If <code>candidateIsReachable</code> is true, the provided candidate is already a
-   * target for a type further up the chain, so anything found in subtypes is a conflict. If it is
-   * false, the target exists but is not reachable from a live type.
-   *
-   * <p>Returns <code>null</code> if the given type has no subtypes or all subtypes are abstract.
-   * Returns {@link DexEncodedMethod#SENTINEL} if multiple live overrides were found. Returns the
-   * single virtual target otherwise.
-   */
-  private DexEncodedMethod findSingleTargetFromSubtypes(
-      DexProgramClass clazz,
-      DexMethod method,
-      DexEncodedMethod candidate,
-      boolean candidateIsReachable,
-      boolean checkForInterfaceConflicts) {
-    // If the invoke could target a method in a class that is not visible to R8, then give up.
-    if (canVirtualMethodBeImplementedInExtraSubclass(clazz, method)) {
-      return DexEncodedMethod.SENTINEL;
-    }
-    // If the candidate is reachable, we already have a previous result.
-    DexEncodedMethod result = candidateIsReachable ? candidate : null;
-    for (DexType subtype : allImmediateExtendsSubtypes(clazz.type)) {
-      DexProgramClass subclass = asProgramClassOrNull(definitionFor(subtype));
-      if (subclass == null) {
-        // Can't guarantee a single target.
-        return DexEncodedMethod.SENTINEL;
-      }
-      DexEncodedMethod target = subclass.lookupVirtualMethod(method);
-      if (target != null && !target.isPrivateMethod()) {
-        // We found a method on this class. If this class is not abstract it is a runtime
-        // reachable override and hence a conflict.
-        if (!subclass.accessFlags.isAbstract()) {
-          if (result != null && result != target) {
-            // We found a new target on this subtype that does not match the previous one. Fail.
-            return DexEncodedMethod.SENTINEL;
-          }
-          // Add the first or matching target.
-          result = target;
-        }
-      }
-      if (checkForInterfaceConflicts) {
-        // We have to check whether there are any default methods in implemented interfaces.
-        if (interfacesMayHaveDefaultFor(subclass.interfaces, method)) {
-          return DexEncodedMethod.SENTINEL;
-        }
-      }
-      DexEncodedMethod newCandidate = target == null ? candidate : target;
-      // If we have a new target and did not fail, it is not an override of a reachable method.
-      // Whether the target is actually reachable depends on whether this class is abstract.
-      // If we did not find a new target, the candidate is reachable if it was before, or if this
-      // class is not abstract.
-      boolean newCandidateIsReachable =
-          !subclass.accessFlags.isAbstract() || ((target == null) && candidateIsReachable);
-      DexEncodedMethod subtypeTarget =
-          findSingleTargetFromSubtypes(
-              subclass, method, newCandidate, newCandidateIsReachable, checkForInterfaceConflicts);
-      if (subtypeTarget != null) {
-        // We found a target in the subclasses. If we already have a different result, fail.
-        if (result != null && result != subtypeTarget) {
-          return DexEncodedMethod.SENTINEL;
-        }
-        // Remember this new result.
-        result = subtypeTarget;
-      }
-    }
-    return result;
-  }
-
-  /**
-   * Checks whether any interface in the given list or their super interfaces implement a default
-   * method.
-   *
-   * <p>This method is conservative for unknown interfaces and interfaces from the library.
-   */
-  private boolean interfacesMayHaveDefaultFor(DexTypeList ifaces, DexMethod method) {
-    for (DexType iface : ifaces.values) {
-      DexClass clazz = definitionFor(iface);
-      if (clazz == null || clazz.isNotProgramClass()) {
-        return true;
-      }
-      DexEncodedMethod candidate = clazz.lookupMethod(method);
-      if (candidate != null && !candidate.accessFlags.isAbstract()) {
-        return true;
-      }
-      if (interfacesMayHaveDefaultFor(clazz.interfaces, method)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  public DexEncodedMethod lookupSingleInterfaceTarget(DexMethod method, DexType invocationContext) {
-    assert checkIfObsolete();
-    return lookupSingleInterfaceTarget(method, invocationContext, method.holder, null);
-  }
-
-  public DexEncodedMethod lookupSingleInterfaceTarget(
-      DexMethod method,
-      DexType invocationContext,
-      DexType refinedReceiverType,
-      ClassTypeLatticeElement receiverLowerBoundType) {
-    assert checkIfObsolete();
-    // Replace DexType invocationContext by DexProgramClass throughout.
-    DexProgramClass invocationClass = asProgramClassOrNull(definitionFor(invocationContext));
-    assert invocationClass != null;
-
-    // If the lower-bound on the receiver type is the same as the upper-bound, then we have exact
-    // runtime type information. In this case, the invoke will dispatch to the resolution result
-    // from the runtime type of the receiver.
+    DexProgramClass refinedLowerBound = null;
     if (receiverLowerBoundType != null) {
-      if (receiverLowerBoundType.getClassType() == refinedReceiverType) {
-        ResolutionResult resolutionResult = resolveMethod(method.holder, method, true);
-        if (resolutionResult.isSingleResolution() && resolutionResult.isVirtualTarget()) {
-          ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method);
-          if (refinedResolutionResult.isSingleResolution()
-              && refinedResolutionResult.isVirtualTarget()) {
-            return validateSingleVirtualTarget(
-                refinedResolutionResult.getSingleTarget(), resolutionResult.getSingleTarget());
-          }
-        }
-        return null;
-      } else {
-        // We should never hit the case at the moment, but if we start tracking more precise lower-
-        // bound type information, we should handle this case as well.
+      assert receiverLowerBoundType.isClassType();
+      DexClass refinedLowerBoundClass = definitionFor(receiverLowerBoundType.getClassType());
+      if (refinedLowerBoundClass != null) {
+        refinedLowerBound = refinedLowerBoundClass.asProgramClass();
       }
     }
 
-    DexProgramClass holder = asProgramClassOrNull(definitionFor(method.holder));
-    if (holder == null || !holder.accessFlags.isInterface()) {
-      return null;
-    }
-    // First check that there is a visible and valid target for this invoke-interface to hit.
-    // If there is none, this will fail at runtime.
-    ResolutionResult topResolution = resolveMethodOnInterface(holder, method);
-    if (!topResolution.isAccessibleForVirtualDispatchFrom(invocationClass, this)) {
+    LookupResultSuccess lookupResult =
+        resolution
+            .lookupVirtualDispatchTargets(
+                invocationClass, this, refinedReceiverClass.asProgramClass(), refinedLowerBound)
+            .asLookupResultSuccess();
+
+    if (lookupResult == null || lookupResult.isIncomplete()) {
       return null;
     }
 
-    DexEncodedMethod topTarget = topResolution.getSingleTarget();
-    if (topTarget == null) {
-      // An null target represents a valid target with no known defintion, eg, array clone().
-      return null;
+    LookupTarget singleTarget = lookupResult.getSingleLookupTarget();
+    DexEncodedMethod singleMethodTarget = null;
+    if (singleTarget != null && singleTarget.isMethodTarget()) {
+      singleMethodTarget = singleTarget.asMethodTarget().getMethod();
     }
-
-    // If the target is a private method, then the invocation is a direct access to a nest member.
-    if (topTarget.isPrivateMethod()) {
-      return topTarget;
-    }
-
-    // If the invoke could target a method in a class that is not visible to R8, then give up.
-    if (canVirtualMethodBeImplementedInExtraSubclass(holder, method)) {
-      return null;
-    }
-
-    DexProgramClass refinedReceiverClass = definitionFor(refinedReceiverType).asProgramClass();
-    if (refinedReceiverClass == null) {
-      return null;
-    }
-
-    // For functional interfaces that are instantiated by lambdas, we may not have synthesized all
-    // the lambda classes yet, and therefore the set of subtypes for the holder may still be
-    // incomplete.
-    if (hasAnyInstantiatedLambdas(refinedReceiverClass)) {
-      return null;
-    }
-
-    Iterable<DexType> subtypesToExplore =
-        isInstantiatedDirectly(refinedReceiverClass)
-            ? Iterables.concat(ImmutableList.of(refinedReceiverType), subtypes(refinedReceiverType))
-            : subtypes(refinedReceiverType);
-
-    // The loop will ignore uninstantiated classes as they will not be a target at runtime.
-    DexEncodedMethod result = null;
-    for (DexType type : subtypesToExplore) {
-      DexProgramClass clazz = asProgramClassOrNull(definitionFor(type));
-      if (clazz == null) {
-        // Cannot guarantee a single target.
-        return null;
-      }
-
-      // If the invoke could target a method in a class that is not visible to R8, then give up.
-      if (canVirtualMethodBeImplementedInExtraSubclass(clazz, method)) {
-        return null;
-      }
-
-      if (!isInstantiatedDirectly(clazz)) {
-        // This is not a possible receiver at runtime.
-        continue;
-      }
-
-      // TODO(b/145344105): Abstract classes should never be considered instantiated.
-      // assert (!clazz.isAbstract() && !clazz.isInterface()) || clazz.isAnnotation();
-
-      DexEncodedMethod resolutionResult = resolveMethod(clazz, method).getSingleTarget();
-      if (resolutionResult == null || isInvalidSingleVirtualTarget(resolutionResult, topTarget)) {
-        // This will fail at runtime.
-        return null;
-      }
-      if (result != null && result != resolutionResult) {
-        return null;
-      }
-      result = resolutionResult;
-    }
-    assert result == null || !isInvalidSingleVirtualTarget(result, topTarget);
-    return result == null || !result.isVirtualMethod() ? null : result;
+    method.setSingleVirtualMethodCache(refinedReceiverType, singleMethodTarget);
+    return singleMethodTarget;
   }
 
   public AppInfoWithLiveness withSwitchMaps(Map<DexField, Int2ReferenceMap<DexField>> switchMaps) {
@@ -1523,4 +1348,44 @@
       }
     }
   }
+
+  @Override
+  public void forEachInstantiatedSubType(
+      DexType type,
+      Consumer<DexProgramClass> subTypeConsumer,
+      Consumer<LambdaDescriptor> callSiteConsumer) {
+    WorkList<DexType> workList = WorkList.newIdentityWorkList();
+    workList.addIfNotSeen(type);
+    while (workList.hasNext()) {
+      DexType subType = workList.next();
+      DexProgramClass clazz = definitionForProgramType(subType);
+      workList.addIfNotSeen(allImmediateSubtypes(subType));
+      if (clazz == null) {
+        continue;
+      }
+      if (isInstantiatedDirectly(clazz)
+          || isPinned(clazz.type)
+          || hasAnyInstantiatedLambdas(clazz)) {
+        subTypeConsumer.accept(clazz);
+      }
+    }
+  }
+
+  public boolean isPinnedNotProgramOrLibraryOverride(DexReference reference) {
+    if (isPinned(reference)) {
+      return true;
+    }
+    if (reference.isDexMethod()) {
+      DexEncodedMethod method = definitionFor(reference.asDexMethod());
+      return method == null
+          || !method.isProgramMethod(this)
+          || method.isLibraryMethodOverride().isPossiblyTrue();
+    } else {
+      assert reference.isDexType();
+      DexClass clazz = definitionFor(reference.asDexType());
+      return clazz == null
+          || clazz.isNotProgramClass()
+          || hasAnyInstantiatedLambdas(clazz.asProgramClass());
+    }
+  }
 }
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 dfcee69..f35fb94 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -65,13 +65,23 @@
   }
 
   @Override
+  public boolean registerInstanceFieldRead(DexField field) {
+    return enqueuer.traceInstanceFieldRead(field, context.getMethod());
+  }
+
+  @Override
+  public boolean registerInstanceFieldReadFromMethodHandle(DexField field) {
+    return enqueuer.traceInstanceFieldReadFromMethodHandle(field, context.getMethod());
+  }
+
+  @Override
   public boolean registerInstanceFieldWrite(DexField field) {
     return enqueuer.traceInstanceFieldWrite(field, context.getMethod());
   }
 
   @Override
-  public boolean registerInstanceFieldRead(DexField field) {
-    return enqueuer.traceInstanceFieldRead(field, context.getMethod());
+  public boolean registerInstanceFieldWriteFromMethodHandle(DexField field) {
+    return enqueuer.traceInstanceFieldWriteFromMethodHandle(field, context.getMethod());
   }
 
   @Override
@@ -85,11 +95,21 @@
   }
 
   @Override
+  public boolean registerStaticFieldReadFromMethodHandle(DexField field) {
+    return enqueuer.traceStaticFieldReadFromMethodHandle(field, context.getMethod());
+  }
+
+  @Override
   public boolean registerStaticFieldWrite(DexField field) {
     return enqueuer.traceStaticFieldWrite(field, context.getMethod());
   }
 
   @Override
+  public boolean registerStaticFieldWriteFromMethodHandle(DexField field) {
+    return enqueuer.traceStaticFieldWriteFromMethodHandle(field, context.getMethod());
+  }
+
+  @Override
   public boolean registerConstClass(DexType type) {
     return enqueuer.traceConstClass(type, context.getMethod());
   }
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 36afff6..200562d 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -23,7 +23,6 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
-import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -55,8 +54,8 @@
 import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
 import com.android.tools.r8.graph.FieldAccessInfoImpl;
 import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
+import com.android.tools.r8.graph.LookupLambdaTarget;
+import com.android.tools.r8.graph.LookupTarget;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
 import com.android.tools.r8.graph.PresortedComparable;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -177,9 +176,6 @@
   private final EnqueuerUseRegistryFactory useRegistryFactory;
   private AnnotationRemover.Builder annotationRemoverBuilder;
 
-  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<>();
@@ -225,10 +221,6 @@
   private final Map<DexProgramClass, Set<DexProgramClass>> unusedInterfaceTypes =
       new IdentityHashMap<>();
 
-  /** Set of all types that are instantiated, directly or indirectly, thus may be abstract. */
-  private final Set<DexProgramClass> directAndIndirectlyInstantiatedTypes =
-      Sets.newIdentityHashSet();
-
   /**
    * Set of methods that are the immediate target of an invoke. They might not actually be live but
    * are required so that invokes can find the method. If a method is only a target but not live,
@@ -270,14 +262,6 @@
    */
   private final Set<DexType> instantiatedAppServices = Sets.newIdentityHashSet();
 
-  /**
-   * Set of interface types for which there may be instantiations, such as lambda expressions or
-   * explicit keep rules.
-   */
-  private final Set<DexProgramClass> instantiatedInterfaceTypes;
-  /** Subset of the above that are marked instantiated by usages that are not desugared lambdas. */
-  private final SetWithReason<DexProgramClass> unknownInstantiatedInterfaceTypes;
-
   /** A queue of items that need processing. Different items trigger different actions. */
   private final EnqueuerWorklist workList;
 
@@ -286,11 +270,16 @@
    */
   private final Set<DexEncodedMethod> pendingReflectiveUses = Sets.newLinkedHashSet();
 
-  /** A cache for DexMethod that have been marked reachable. */
-  private final Map<DexProgramClass, Set<DexEncodedMethod>> reachableVirtualResolutions =
-      new IdentityHashMap<>();
-
-  private final Map<DexMethod, MarkedResolutionTarget> virtualTargetsMarkedAsReachable =
+  /**
+   * Mapping of types to the reachable method resolutions.
+   *
+   * <p>Primary map key is the initial/static/symbolic type specified by a live invoke.
+   *
+   * <p>The keys of the reachable resolutions are again the static reference, thus methodKey.holder
+   * will be the same as the classKey.type. The value of the reachable resolution is the resolution
+   * target.
+   */
+  private final Map<DexProgramClass, Map<DexMethod, ProgramMethod>> reachableVirtualResolutions =
       new IdentityHashMap<>();
 
   /**
@@ -365,8 +354,6 @@
     failedResolutionTargets = SetUtils.newIdentityHashSet(2);
     liveMethods = new LiveMethodsSet(graphReporter::registerMethod);
     liveFields = new SetWithReason<>(graphReporter::registerField);
-    unknownInstantiatedInterfaceTypes = new SetWithReason<>(graphReporter::registerInterface);
-    instantiatedInterfaceTypes = Sets.newIdentityHashSet();
     lambdaRewriter = options.desugarState == DesugarState.ON ? new LambdaRewriter(appView) : null;
 
     objectAllocationInfoCollection =
@@ -518,18 +505,6 @@
     pinnedItems.add(item.toReference());
   }
 
-  void markInterfaceAsInstantiated(DexProgramClass clazz, KeepReasonWitness witness) {
-    assert !clazz.isAnnotation();
-    assert clazz.isInterface();
-    unknownInstantiatedInterfaceTypes.add(clazz, witness);
-    if (!instantiatedInterfaceTypes.add(clazz)) {
-      return;
-    }
-    populateInstantiatedTypesCache(clazz);
-    markTypeAsLive(clazz, witness);
-    transitionDependentItemsForInstantiatedInterface(clazz);
-  }
-
   private void enqueueFirstNonSerializableClassInitializer(
       DexProgramClass clazz, KeepReason reason) {
     assert clazz.isSerializable(appView);
@@ -663,10 +638,8 @@
     }
 
     DexEncodedMethod contextMethod = context.getMethod();
-    for (DexType lambdaInstantiatedInterface : descriptor.interfaces) {
-      markLambdaInstantiated(lambdaInstantiatedInterface, contextMethod);
-    }
-
+    markLambdaAsInstantiated(descriptor, contextMethod);
+    transitionMethodsForInstantiatedLambda(descriptor);
     if (lambdaRewriter != null) {
       assert contextMethod.getCode().isCfCode() : "Unexpected input type with lambdas";
       CfCode code = contextMethod.getCode().asCfCode();
@@ -718,8 +691,6 @@
       default:
         throw new Unreachable();
     }
-
-    transitionMethodsForInstantiatedLambda(descriptor);
   }
 
   boolean traceCheckCast(DexType type, DexEncodedMethod currentMethod) {
@@ -992,6 +963,15 @@
   }
 
   boolean traceInstanceFieldRead(DexField field, DexEncodedMethod currentMethod) {
+    return traceInstanceFieldRead(field, currentMethod, false);
+  }
+
+  boolean traceInstanceFieldReadFromMethodHandle(DexField field, DexEncodedMethod currentMethod) {
+    return traceInstanceFieldRead(field, currentMethod, true);
+  }
+
+  private boolean traceInstanceFieldRead(
+      DexField field, DexEncodedMethod currentMethod, boolean fromMethodHandle) {
     if (!registerFieldRead(field, currentMethod)) {
       return false;
     }
@@ -1004,6 +984,10 @@
       return false;
     }
 
+    if (fromMethodHandle) {
+      fieldAccessInfoCollection.get(encodedField.field).setReadFromMethodHandle();
+    }
+
     DexProgramClass clazz = getProgramClassOrNull(encodedField.field.holder);
     if (clazz == null) {
       return false;
@@ -1028,6 +1012,15 @@
   }
 
   boolean traceInstanceFieldWrite(DexField field, DexEncodedMethod currentMethod) {
+    return traceInstanceFieldWrite(field, currentMethod, false);
+  }
+
+  boolean traceInstanceFieldWriteFromMethodHandle(DexField field, DexEncodedMethod currentMethod) {
+    return traceInstanceFieldWrite(field, currentMethod, true);
+  }
+
+  private boolean traceInstanceFieldWrite(
+      DexField field, DexEncodedMethod currentMethod, boolean fromMethodHandle) {
     if (!registerFieldWrite(field, currentMethod)) {
       return false;
     }
@@ -1040,6 +1033,10 @@
       return false;
     }
 
+    if (fromMethodHandle) {
+      fieldAccessInfoCollection.get(encodedField.field).setWrittenFromMethodHandle();
+    }
+
     DexProgramClass clazz = getProgramClassOrNull(encodedField.field.holder);
     if (clazz == null) {
       return false;
@@ -1064,6 +1061,15 @@
   }
 
   boolean traceStaticFieldRead(DexField field, DexEncodedMethod currentMethod) {
+    return traceStaticFieldRead(field, currentMethod, false);
+  }
+
+  boolean traceStaticFieldReadFromMethodHandle(DexField field, DexEncodedMethod currentMethod) {
+    return traceStaticFieldRead(field, currentMethod, true);
+  }
+
+  private boolean traceStaticFieldRead(
+      DexField field, DexEncodedMethod currentMethod, boolean fromMethodHandle) {
     if (!registerFieldRead(field, currentMethod)) {
       return false;
     }
@@ -1075,6 +1081,10 @@
       return false;
     }
 
+    if (fromMethodHandle) {
+      fieldAccessInfoCollection.get(encodedField.field).setReadFromMethodHandle();
+    }
+
     if (!isProgramClass(encodedField.field.holder)) {
       // No need to trace into the non-program code.
       return false;
@@ -1108,6 +1118,15 @@
   }
 
   boolean traceStaticFieldWrite(DexField field, DexEncodedMethod currentMethod) {
+    return traceStaticFieldWrite(field, currentMethod, false);
+  }
+
+  boolean traceStaticFieldWriteFromMethodHandle(DexField field, DexEncodedMethod currentMethod) {
+    return traceStaticFieldWrite(field, currentMethod, true);
+  }
+
+  private boolean traceStaticFieldWrite(
+      DexField field, DexEncodedMethod currentMethod, boolean fromMethodHandle) {
     if (!registerFieldWrite(field, currentMethod)) {
       return false;
     }
@@ -1119,6 +1138,10 @@
       return false;
     }
 
+    if (fromMethodHandle) {
+      fieldAccessInfoCollection.get(encodedField.field).setWrittenFromMethodHandle();
+    }
+
     if (!isProgramClass(encodedField.field.holder)) {
       // No need to trace into the non-program code.
       return false;
@@ -1230,19 +1253,6 @@
         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)) {
@@ -1272,10 +1282,6 @@
               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
@@ -1319,6 +1325,8 @@
     rootSet.forEachDependentStaticMember(holder, appView, this::enqueueDependentItem);
     compatEnqueueHolderIfDependentNonStaticMember(
         holder, rootSet.getDependentKeepClassCompatRule(holder.getType()));
+
+    analyses.forEach(analysis -> analysis.processNewlyLiveClass(holder, workList));
   }
 
   private void ensureMethodsContinueToWidenAccess(DexClass clazz) {
@@ -1346,8 +1354,6 @@
       return;
     }
 
-    addImmediateSubtype(clazz, implementer);
-
     if (!appView.options().enableUnusedInterfaceRemoval || mode.isTracingMainDex()) {
       markTypeAsLive(clazz, graphReporter.reportClassReferencedFrom(clazz, implementer));
     } else {
@@ -1446,7 +1452,7 @@
     return encodedField;
   }
 
-  private ResolutionResult resolveMethod(DexMethod method, KeepReason reason) {
+  private SingleResolutionResult resolveMethod(DexMethod method, KeepReason reason) {
     // Record the references in case they are not program types.
     recordTypeReference(method.holder);
     recordTypeReference(method.proto.returnType);
@@ -1458,11 +1464,28 @@
       reportMissingMethod(method);
       markFailedResolutionTargets(method, resolutionResult.asFailedResolution(), reason);
     }
-    return resolutionResult;
+    return resolutionResult.asSingleResolution();
+  }
+
+  private SingleResolutionResult resolveMethod(
+      DexMethod method, KeepReason reason, boolean interfaceInvoke) {
+    // Record the references in case they are not program types.
+    recordTypeReference(method.holder);
+    recordTypeReference(method.proto.returnType);
+    for (DexType param : method.proto.parameters.values) {
+      recordTypeReference(param);
+    }
+    ResolutionResult resolutionResult =
+        appInfo.resolveMethod(method.holder, method, interfaceInvoke);
+    if (resolutionResult.isFailedResolution()) {
+      reportMissingMethod(method);
+      markFailedResolutionTargets(method, resolutionResult.asFailedResolution(), reason);
+    }
+    return resolutionResult.asSingleResolution();
   }
 
   private void handleInvokeOfStaticTarget(DexMethod method, KeepReason reason) {
-    SingleResolutionResult resolution = resolveMethod(method, reason).asSingleResolution();
+    SingleResolutionResult resolution = resolveMethod(method, reason);
     if (resolution == null || resolution.getResolvedHolder().isNotProgramClass()) {
       return;
     }
@@ -1563,14 +1586,30 @@
       // main dex lists we allow this.
       return;
     }
-
-    if (dontWarnPatterns.matches(context.type)) {
-      // Ignore.
-      return;
-    }
-
     DexClass holder = appView.definitionFor(type);
     if (holder != null && !holder.isLibraryClass()) {
+      if (forceProguardCompatibility) {
+        // To ensure that the program works correctly we have to pin all super types and members
+        // in the tree.
+        appInfo.forEachSuperType(
+            holder,
+            (dexType, ignored) -> {
+              if (holder.isProgramClass()) {
+                DexReference holderReference = holder.toReference();
+                pinnedItems.add(holderReference);
+                rootSet.shouldNotBeMinified(holderReference);
+                for (DexEncodedMember<?, ?> member : holder.members()) {
+                  DexMember<?, ?> memberReference = member.toReference();
+                  pinnedItems.add(memberReference);
+                  rootSet.shouldNotBeMinified(memberReference);
+                }
+              }
+            });
+      }
+      if (dontWarnPatterns.matches(context.type)) {
+        // Ignore.
+        return;
+      }
       Diagnostic message =
           new StringDiagnostic(
               "Library class "
@@ -1648,13 +1687,10 @@
     analyses.forEach(
         analysis -> analysis.processNewlyInstantiatedClass(clazz.asProgramClass(), context));
 
-    if (!objectAllocationInfoCollection.recordDirectAllocationSite(
-        clazz, context, instantiationReason, keepReason)) {
+    if (!markInstantiatedClass(clazz, context, instantiationReason, keepReason)) {
       return;
     }
 
-    populateInstantiatedTypesCache(clazz);
-
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Class `%s` is instantiated, processing...", clazz);
     }
@@ -1671,21 +1707,59 @@
     transitionDependentItemsForInstantiatedClass(clazz);
   }
 
-  private void populateInstantiatedTypesCache(DexProgramClass clazz) {
-    if (!directAndIndirectlyInstantiatedTypes.add(clazz)) {
+  // TODO(b/146016987): Make this the single instantiation entry rather than the worklist action.
+  private boolean markInstantiatedClass(
+      DexProgramClass clazz,
+      DexEncodedMethod context,
+      InstantiationReason instantiationReason,
+      KeepReason keepReason) {
+    assert !clazz.isInterface();
+    return objectAllocationInfoCollection.recordDirectAllocationSite(
+        clazz, context, instantiationReason, keepReason, appInfo);
+  }
+
+  void markInterfaceAsInstantiated(DexProgramClass clazz, KeepReasonWitness witness) {
+    assert !clazz.isAnnotation();
+    assert clazz.isInterface();
+    if (!objectAllocationInfoCollection.recordInstantiatedInterface(clazz)) {
       return;
     }
-    if (clazz.superType != null) {
-      DexProgramClass superClass = getProgramClassOrNull(clazz.superType);
-      if (superClass != null) {
-        populateInstantiatedTypesCache(superClass);
+    markTypeAsLive(clazz, witness);
+    transitionDependentItemsForInstantiatedInterface(clazz);
+  }
+
+  private void markLambdaAsInstantiated(LambdaDescriptor descriptor, DexEncodedMethod context) {
+    // Each descriptor is unique, so there is no check for already marking the lambda.
+    for (DexType iface : descriptor.interfaces) {
+      checkLambdaInterface(iface, context);
+      objectAllocationInfoCollection.recordInstantiatedLambdaInterface(iface, descriptor, appInfo);
+      // TODO(b/150277553): Lambdas should be accurately traces and thus not be added here.
+      if (lambdaRewriter == null) {
+        DexProgramClass clazz = getProgramClassOrNull(iface);
+        if (clazz != null) {
+          objectAllocationInfoCollection.recordInstantiatedInterface(clazz);
+        }
       }
     }
-    for (DexType iface : clazz.interfaces.values) {
-      DexProgramClass ifaceClass = getProgramClassOrNull(iface);
-      if (ifaceClass != null) {
-        populateInstantiatedTypesCache(ifaceClass);
-      }
+  }
+
+  private void checkLambdaInterface(DexType itf, DexEncodedMethod context) {
+    DexClass clazz = definitionFor(itf);
+    if (clazz == null) {
+      StringDiagnostic message =
+          new StringDiagnostic(
+              "Lambda expression implements missing interface `" + itf.toSourceString() + "`",
+              appInfo.originFor(context.method.holder));
+      options.reporter.warning(message);
+    } else if (!clazz.isInterface()) {
+      StringDiagnostic message =
+          new StringDiagnostic(
+              "Lambda expression expected to implement an interface, but found "
+                  + "`"
+                  + itf.toSourceString()
+                  + "`",
+              appInfo.originFor(context.method.holder));
+      options.reporter.warning(message);
     }
   }
 
@@ -1746,50 +1820,52 @@
     }
   }
 
-  private Set<DexEncodedMethod> getReachableVirtualResolutions(DexProgramClass clazz) {
-    return reachableVirtualResolutions.getOrDefault(clazz, Collections.emptySet());
+  private Map<DexMethod, ProgramMethod> getReachableVirtualResolutions(DexProgramClass clazz) {
+    return reachableVirtualResolutions.getOrDefault(clazz, Collections.emptyMap());
   }
 
   private void markProgramMethodOverridesAsLive(
       InstantiatedObject instantiation,
       DexProgramClass superClass,
       ScopedDexMethodSet seenMethods) {
-    for (DexEncodedMethod resolution : getReachableVirtualResolutions(superClass)) {
-      if (seenMethods.addMethod(resolution)) {
-        markLiveOverrides(instantiation, superClass, resolution);
-      }
-    }
+    Map<DexMethod, ProgramMethod> reachableResolution = getReachableVirtualResolutions(superClass);
+    reachableResolution.forEach(
+        (method, resolution) -> {
+          assert method.holder == superClass.type;
+          if (seenMethods.addMethod(resolution.getMethod())) {
+            markLiveOverrides(instantiation, superClass, resolution);
+          }
+        });
   }
 
   private void markLiveOverrides(
       InstantiatedObject instantiation,
-      DexProgramClass reachableHolder,
-      DexEncodedMethod reachableMethod) {
-    assert reachableHolder.type == reachableMethod.method.holder;
+      DexProgramClass initialHolder,
+      ProgramMethod resolutionMethod) {
     // The validity of the reachable method is checked at the point it becomes "reachable" and is
     // resolved. If the method is private, then the dispatch is not "virtual" and the method is
     // simply marked live on its holder.
-    if (reachableMethod.isPrivateMethod()) {
+    if (resolutionMethod.getMethod().isPrivateMethod()) {
       markVirtualMethodAsLive(
-          reachableHolder,
-          reachableMethod,
+          resolutionMethod.getHolder(),
+          resolutionMethod.getMethod(),
           graphReporter.reportReachableMethodAsLive(
-              reachableMethod.method, new ProgramMethod(reachableHolder, reachableMethod)));
+              resolutionMethod.getMethod().method, resolutionMethod));
       return;
     }
     // Otherwise, we set the initial holder type to be the holder of the reachable method, which
     // ensures that access will be generally valid.
-    SingleResolutionResult result =
-        new SingleResolutionResult(reachableHolder, reachableHolder, reachableMethod);
-    DexClassAndMethod lookup = result.lookupVirtualDispatchTarget(instantiation, appView);
-    if (lookup == null || !lookup.isProgramMethod() || lookup.getMethod().isAbstract()) {
-      return;
+    SingleResolutionResult resolution =
+        new SingleResolutionResult(
+            initialHolder, resolutionMethod.getHolder(), resolutionMethod.getMethod());
+    LookupTarget lookup = resolution.lookupVirtualDispatchTarget(instantiation, appInfo);
+    if (lookup != null) {
+      markVirtualDispatchTargetAsLive(
+          lookup,
+          programMethod ->
+              graphReporter.reportReachableMethodAsLive(
+                  resolutionMethod.getMethod().method, programMethod));
     }
-    ProgramMethod method = lookup.asProgramMethod();
-    markVirtualMethodAsLive(
-        method.getHolder(),
-        method.getMethod(),
-        graphReporter.reportReachableMethodAsLive(reachableMethod.method, method));
   }
 
   private void markLibraryAndClasspathMethodOverridesAsLive(
@@ -1831,26 +1907,22 @@
       InstantiatedObject instantiation,
       DexClass libraryOrClasspathClass,
       ResolutionResult resolution) {
-    DexClassAndMethod lookup = resolution.lookupVirtualDispatchTarget(instantiation, appView);
-    if (lookup == null || !lookup.isProgramMethod() || lookup.getMethod().isAbstract()) {
+    LookupTarget lookup = resolution.lookupVirtualDispatchTarget(instantiation, appInfo);
+    if (lookup == null) {
       return;
     }
-    DexProgramClass clazz = lookup.asProgramMethod().getHolder();
-    DexEncodedMethod target = lookup.getMethod();
-    if (!lookup.getMethod().method.match(resolution.getSingleTarget())) {
-      // If the resolution signature does not match the lookup signature, then the lookup must be
-      // to the method implemented by a lambda that targets an actual method of another name.
-      assert instantiation.isLambda();
-      // TODO(b/120959039): Report a clear reason for indirect keep of the lambda target.
-      markVirtualMethodAsLive(
-          clazz, target, KeepReason.isLibraryMethod(clazz, libraryOrClasspathClass.type));
-    } else if (shouldMarkLibraryMethodOverrideAsReachable(clazz, target)) {
-      markVirtualMethodAsLive(
-          clazz, target, KeepReason.isLibraryMethod(clazz, libraryOrClasspathClass.type));
+    if (!shouldMarkLibraryMethodOverrideAsReachable(lookup)) {
+      return;
     }
+    markVirtualDispatchTargetAsLive(
+        lookup,
+        method ->
+            graphReporter.reportLibraryMethodAsLive(
+                instantiation, method, libraryOrClasspathClass));
     if (instantiation.isClass()) {
       // TODO(b/149976493): We need to mark these for lambdas too!
-      markOverridesAsLibraryMethodOverrides(instantiation.asClass(), target.method);
+      markOverridesAsLibraryMethodOverrides(
+          instantiation.asClass(), lookup.asMethodTarget().getMethod().method);
     }
   }
 
@@ -1991,39 +2063,6 @@
     analyses.forEach(analysis -> analysis.processNewlyLiveField(field));
   }
 
-  private void markLambdaInstantiated(DexType itf, DexEncodedMethod method) {
-    DexClass clazz = appView.definitionFor(itf);
-    if (clazz == null) {
-      StringDiagnostic message =
-          new StringDiagnostic(
-              "Lambda expression implements missing interface `" + itf.toSourceString() + "`",
-              appInfo.originFor(method.method.holder));
-      options.reporter.warning(message);
-      return;
-    }
-    if (!clazz.isInterface()) {
-      StringDiagnostic message =
-          new StringDiagnostic(
-              "Lambda expression expected to implement an interface, but found "
-                  + "`"
-                  + itf.toSourceString()
-                  + "`",
-              appInfo.originFor(method.method.holder));
-      options.reporter.warning(message);
-      return;
-    }
-    DexProgramClass programClass = clazz.asProgramClass();
-    if (programClass != null) {
-      // When not desugaring, we need to mark the instantiation point as unknown for now.
-      if (lambdaRewriter == null) {
-        unknownInstantiatedInterfaceTypes.add(programClass, KeepReason.instantiatedIn(method));
-      }
-      if (instantiatedInterfaceTypes.add(programClass)) {
-        populateInstantiatedTypesCache(programClass);
-      }
-    }
-  }
-
   private void markDirectStaticOrConstructorMethodAsLive(
       DexProgramClass clazz, DexEncodedMethod encodedMethod, KeepReason reason) {
     assert encodedMethod.method.holder == clazz.type;
@@ -2088,10 +2127,6 @@
         : info.isWritten();
   }
 
-  private boolean isInstantiatedOrHasInstantiatedSubtype(DexProgramClass clazz) {
-    return directAndIndirectlyInstantiatedTypes.contains(clazz);
-  }
-
   public boolean isMethodLive(DexEncodedMethod method) {
     return liveMethods.contains(method);
   }
@@ -2128,7 +2163,7 @@
     if (encodedField.accessFlags.isStatic()) {
       markStaticFieldAsLive(encodedField, reason);
     } else {
-      if (isInstantiatedOrHasInstantiatedSubtype(clazz)) {
+      if (objectAllocationInfoCollection.isInstantiatedDirectlyOrHasInstantiatedSubtype(clazz)) {
         markInstanceFieldAsLive(clazz, encodedField, reason);
       } else {
         // Add the field to the reachable set if the type later becomes instantiated.
@@ -2177,12 +2212,11 @@
       return;
     }
 
+    ProgramMethod resolutionMethod = getReachableVirtualResolutions(holder).get(method);
+
     // If the method has already been marked, just report the new reason for the resolved target.
-    MarkedResolutionTarget resolution = virtualTargetsMarkedAsReachable.get(method);
-    if (resolution != null) {
-      if (!resolution.isUnresolved()) {
-        graphReporter.registerMethod(resolution.method, reason);
-      }
+    if (resolutionMethod != null) {
+      graphReporter.registerMethod(resolutionMethod.getMethod(), reason);
       return;
     }
 
@@ -2190,131 +2224,88 @@
       Log.verbose(getClass(), "Marking virtual method `%s` as reachable.", method);
     }
 
-    // Otherwise, the resolution target is marked and cached, and all possible targets identified.
-    resolution = findAndMarkResolutionTarget(method, interfaceInvoke, reason);
-    if (contextOrNull != null
-        && !resolution.isUnresolved()
-        && !AccessControl.isMethodAccessible(
-            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.
+    SingleResolutionResult resolution = resolveMethod(method, reason, interfaceInvoke);
+    if (resolution == null) {
       return;
     }
 
-    // The resolution is unresolved or accessible, both are context independent, so cache it.
-    virtualTargetsMarkedAsReachable.put(method, resolution);
-    if (resolution.isUnresolved() || !resolution.method.isVirtualMethod()) {
-      // There is no valid resolution, so any call will lead to a runtime exception.
+    if (resolution.getResolvedHolder().isNotProgramClass()) {
+      // TODO(b/70160030): If the resolution is on a library method, then the keep edge needs to go
+      // directly to the target method in the program. Thus this method will need to ensure that
+      // 'reason' is not already reported (eg, must be delayed / non-witness) and report that for
+      // each possible target edge below.
       return;
     }
 
-    // TODO(b/70160030): If the resolution is on a library method, then the keep edge needs to go
-    // directly to the target method in the program. Thus this method will need to ensure that
-    // 'reason' is not already reported (eg, must be delayed / non-witness) and report that for
-    // each possible target edge below.
-    assert resolution.holder.isProgramClass();
+    // We have to mark the resolution targeted, even if it does not become live, we
+    // need at least an abstract version of it so that it can be targeted.
+    DexProgramClass resolvedHolder = resolution.getResolvedHolder().asProgramClass();
+    DexEncodedMethod resolvedMethod = resolution.getResolvedMethod();
+    markMethodAsTargeted(resolvedHolder, resolvedMethod, reason);
 
-    reachableVirtualResolutions
-        .computeIfAbsent(resolution.holder.asProgramClass(), k -> Sets.newIdentityHashSet())
-        .add(resolution.method);
-
-    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(context, appView, appInfo, pinnedItems::contains);
-    if (!lookupResult.isLookupResultSuccess()) {
+    if (contextOrNull != null && !resolution.isAccessibleForVirtualDispatchFrom(context, appInfo)) {
+      // Not accessible from this context, so this call will cause a runtime exception.
       return;
     }
-    LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
-    for (DexEncodedMethod encodedPossibleTarget : lookupResultSuccess.getMethodTargets()) {
-      if (encodedPossibleTarget.isAbstract()) {
-        continue;
-      }
-      // TODO(b/139464956): Replace this downwards search once targets are found for live types.
-      markPossibleTargetsAsReachable(resolution, encodedPossibleTarget);
+
+    // If the resolved method is not a virtual target, eg, is static, dispatch will fail too.
+    if (!resolvedMethod.isVirtualMethod()) {
+      // This can only happen when context is null, otherwise the access check above will fail.
+      assert context == null;
+      return;
     }
+
+    // The method resolved and is accessible, so currently live overrides become live.
+    reachableVirtualResolutions
+        .computeIfAbsent(holder, k -> new IdentityHashMap<>())
+        .put(method, new ProgramMethod(resolvedHolder, resolvedMethod));
+
+    resolution
+        .lookupVirtualDispatchTargets(
+            context,
+            appInfo,
+            (type, subTypeConsumer, lambdaConsumer) ->
+                objectAllocationInfoCollection.forEachInstantiatedSubType(
+                    type, subTypeConsumer, lambdaConsumer, appInfo),
+            pinnedItems::contains)
+        .forEach(
+            target ->
+                markVirtualDispatchTargetAsLive(
+                    target,
+                    programMethod ->
+                        graphReporter.reportReachableMethodAsLive(
+                            resolvedMethod.method, programMethod)));
   }
 
-  private void markPossibleTargetsAsReachable(
-      MarkedResolutionTarget reason,
-      DexEncodedMethod encodedPossibleTarget) {
-    assert encodedPossibleTarget.isVirtualMethod();
-    assert !encodedPossibleTarget.isAbstract();
-    DexMethod possibleTarget = encodedPossibleTarget.method;
-    DexProgramClass clazz = getProgramClassOrNull(possibleTarget.holder);
-    // If the holder type is uninstantiated (directly or indirectly) the method is not live yet.
-    if (clazz == null || !isInstantiatedOrHasInstantiatedSubtype(clazz)) {
-      return;
-    }
-
-    if (objectAllocationInfoCollection.isInstantiatedDirectly(clazz)
-        || instantiatedInterfaceTypes.contains(clazz)) {
-      markVirtualMethodAsLive(
-          clazz,
-          encodedPossibleTarget,
-          graphReporter.reportReachableMethodAsLive(
-              reason.method.method, new ProgramMethod(clazz, encodedPossibleTarget)));
+  private void markVirtualDispatchTargetAsLive(
+      LookupTarget target, Function<ProgramMethod, KeepReasonWitness> reason) {
+    if (target.isMethodTarget()) {
+      markVirtualDispatchTargetAsLive(target.asMethodTarget(), reason);
     } else {
-      Deque<DexType> worklist =
-          new ArrayDeque<>(appInfo.allImmediateSubtypes(possibleTarget.holder));
-      while (!worklist.isEmpty()) {
-        DexType current = worklist.pollFirst();
-        DexProgramClass currentClass = getProgramClassOrNull(current);
-        // If this class overrides the virtual, abort the search. Note that, according to
-        // the JVM spec, private methods cannot override a virtual method.
-        if (currentClass == null || currentClass.lookupVirtualMethod(possibleTarget) != null) {
-          continue;
-        }
-        if (objectAllocationInfoCollection.isInstantiatedDirectly(currentClass)
-            || instantiatedInterfaceTypes.contains(currentClass)) {
-          markVirtualMethodAsLive(
-              clazz,
-              encodedPossibleTarget,
-              graphReporter.reportReachableMethodAsLive(encodedPossibleTarget, reason));
-          break;
-        }
-        appInfo.allImmediateSubtypes(current).forEach(worklist::addLast);
-      }
+      assert target.isLambdaTarget();
+      markVirtualDispatchTargetAsLive(target.asLambdaTarget(), reason);
     }
   }
 
-  private MarkedResolutionTarget findAndMarkResolutionTarget(
-      DexMethod method, boolean interfaceInvoke, KeepReason reason) {
-    ResolutionResult resolutionResult =
-        appInfo.resolveMethod(method.holder, method, interfaceInvoke);
-    if (resolutionResult.isFailedResolution()) {
-      // If the resolution fails, mark each dependency causing a failure.
-      markFailedResolutionTargets(method, resolutionResult.asFailedResolution(), reason);
-      return MarkedResolutionTarget.unresolved();
+  private void markVirtualDispatchTargetAsLive(
+      DexClassAndMethod target, Function<ProgramMethod, KeepReasonWitness> reason) {
+    ProgramMethod programMethod = target.asProgramMethod();
+    if (programMethod != null && !programMethod.getMethod().isAbstract()) {
+      markVirtualMethodAsLive(
+          programMethod.getHolder(), programMethod.getMethod(), reason.apply(programMethod));
     }
+  }
 
-    DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
-    if (resolutionTarget == null) {
-      reportMissingMethod(method);
-      return MarkedResolutionTarget.unresolved();
+  private void markVirtualDispatchTargetAsLive(
+      LookupLambdaTarget target, Function<ProgramMethod, KeepReasonWitness> reason) {
+    ProgramMethod implementationMethod = target.getImplementationMethod().asProgramMethod();
+    if (implementationMethod != null) {
+      enqueueMarkMethodLiveAction(
+          implementationMethod.getHolder(),
+          implementationMethod.getMethod(),
+          reason.apply(implementationMethod));
     }
-
-    DexClass resolutionTargetClass = appInfo.definitionFor(resolutionTarget.method.holder);
-    if (resolutionTargetClass == null) {
-      reportMissingClass(resolutionTarget.method.holder);
-      return MarkedResolutionTarget.unresolved();
-    }
-
-    if (!options.enableTreeShakingOfLibraryMethodOverrides
-        && resolutionTargetClass.isNotProgramClass()) {
-      return MarkedResolutionTarget.unresolved();
-    }
-
-    // We have to mark this as targeted, as even if this specific instance never becomes live, we
-    // need at least an abstract version of it so that we have a target for the corresponding
-    // invoke. This also ensures preserving the errors detailed below.
-    if (resolutionTargetClass.isProgramClass()) {
-      markMethodAsTargeted(resolutionTargetClass.asProgramClass(), resolutionTarget, reason);
-    }
-
-    return new MarkedResolutionTarget(resolutionTargetClass, resolutionTarget);
   }
 
   private void markFailedResolutionTargets(
@@ -2356,7 +2347,7 @@
   // Package protected due to entry point from worklist.
   void markSuperMethodAsReachable(DexMethod method, DexEncodedMethod from) {
     KeepReason reason = KeepReason.targetedBySuperFrom(from);
-    SingleResolutionResult resolution = resolveMethod(method, reason).asSingleResolution();
+    SingleResolutionResult resolution = resolveMethod(method, reason);
     if (resolution == null) {
       return;
     }
@@ -2471,11 +2462,13 @@
     assert fieldAccessInfoCollection.verifyMappingIsOneToOne();
 
     for (ProgramMethod bridge : syntheticInterfaceMethodBridges.values()) {
-      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());
+      DexProgramClass holder = bridge.getHolder();
+      DexEncodedMethod method = bridge.getMethod();
+      appView.appInfo().invalidateTypeCacheFor(holder.type);
+      holder.appendVirtualMethod(method);
+      targetedMethods.add(method, graphReporter.fakeReportShouldNotBeUsed());
+      liveMethods.add(holder, method, graphReporter.fakeReportShouldNotBeUsed());
+      pinnedItems.add(method.method);
     }
 
     // Ensure references from various root set collections.
@@ -2558,8 +2551,10 @@
             Collections.emptySet(),
             Collections.emptyMap(),
             EnumValueInfoMapCollection.empty(),
+            // TODO(b/150277553): Remove this once object allocation contains the information.
             SetUtils.mapIdentityHashSet(
-                unknownInstantiatedInterfaceTypes.getItems(), DexProgramClass::getType),
+                objectAllocationInfoCollection.unknownInstantiatedInterfaceTypes,
+                DexProgramClass::getType),
             constClassReferences);
     appInfo.markObsolete();
     return appInfoWithLiveness;
@@ -2627,7 +2622,8 @@
           wrapper,
           null,
           InstantiationReason.SYNTHESIZED_CLASS,
-          graphReporter.fakeReportShouldNotBeUsed());
+          graphReporter.fakeReportShouldNotBeUsed(),
+          appInfo);
       // Mark all methods on the wrapper as live and targeted.
       for (DexEncodedMethod method : wrapper.methods()) {
         targetedMethods.add(method, graphReporter.fakeReportShouldNotBeUsed());
@@ -2666,7 +2662,8 @@
           programClass,
           null,
           InstantiationReason.SYNTHESIZED_CLASS,
-          graphReporter.fakeReportShouldNotBeUsed());
+          graphReporter.fakeReportShouldNotBeUsed(),
+          appInfo);
 
       // Register all of the field writes in the lambda constructors.
       // This is needed to ensure that the initializers can be optimized.
@@ -2987,8 +2984,16 @@
     }
   }
 
-  private boolean shouldMarkLibraryMethodOverrideAsReachable(
-      DexProgramClass clazz, DexEncodedMethod method) {
+  private boolean shouldMarkLibraryMethodOverrideAsReachable(LookupTarget override) {
+    if (override.isLambdaTarget()) {
+      return true;
+    }
+    ProgramMethod programMethod = override.asMethodTarget().asProgramMethod();
+    if (programMethod == null) {
+      return false;
+    }
+    DexProgramClass clazz = programMethod.getHolder();
+    DexEncodedMethod method = programMethod.getMethod();
     assert method.isVirtualMethod();
 
     if (method.isAbstract() || method.isPrivateMethod()) {
@@ -2999,9 +3004,9 @@
       return true;
     }
 
-    // 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.
-    Set<DexProgramClass> immediateSubtypes = getImmediateLiveSubtypes(clazz);
+    // If there is an instantiated subtype of `clazz` that escapes into the library and does not
+    // override `method` then we need to mark the method as being reachable.
+    Set<DexProgramClass> immediateSubtypes = getImmediateSubtypesInInstantiatedHierarchy(clazz);
     if (immediateSubtypes.isEmpty()) {
       return false;
     }
@@ -3020,7 +3025,7 @@
         return true;
       }
 
-      for (DexProgramClass subtype : getImmediateLiveSubtypes(current)) {
+      for (DexProgramClass subtype : getImmediateSubtypesInInstantiatedHierarchy(current)) {
         if (visited.add(subtype)) {
           worklist.add(subtype);
         }
@@ -3030,6 +3035,21 @@
     return false;
   }
 
+  private Set<DexProgramClass> getImmediateSubtypesInInstantiatedHierarchy(DexProgramClass clazz) {
+    Set<DexClass> subtypes =
+        objectAllocationInfoCollection.getImmediateSubtypesInInstantiatedHierarchy(clazz.type);
+    if (subtypes == null) {
+      return Collections.emptySet();
+    }
+    Set<DexProgramClass> programClasses = SetUtils.newIdentityHashSet(subtypes.size());
+    for (DexClass subtype : subtypes) {
+      if (subtype.isProgramClass()) {
+        programClasses.add(subtype.asProgramClass());
+      }
+    }
+    return programClasses;
+  }
+
   // Package protected due to entry point from worklist.
   void markMethodAsLive(DexEncodedMethod method, KeepReason reason) {
     assert liveMethods.contains(method);
@@ -3543,100 +3563,6 @@
     }
   }
 
-  public static class MarkedResolutionTarget {
-
-    private static final MarkedResolutionTarget UNRESOLVED = new MarkedResolutionTarget(null, null);
-
-    final DexClass holder;
-    final DexEncodedMethod method;
-
-    public static MarkedResolutionTarget unresolved() {
-      return UNRESOLVED;
-    }
-
-    public MarkedResolutionTarget(DexClass holder, DexEncodedMethod method) {
-      assert (holder == null && method == null) || holder.type == method.method.holder;
-      this.holder = holder;
-      this.method = method;
-    }
-
-    public boolean isUnresolved() {
-      return this == unresolved();
-    }
-
-    @Override
-    public int hashCode() {
-      // The encoded method already encodes information of the holder.
-      return method.hashCode();
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-      // The encoded method already encodes information of the holder.
-      return obj instanceof MarkedResolutionTarget
-          && ((MarkedResolutionTarget) obj).method.equals(method);
-    }
-  }
-
-  private static class ReachableVirtualMethodsSet {
-
-    private final Map<DexEncodedMethod, Set<MarkedResolutionTarget>> methods =
-        Maps.newIdentityHashMap();
-
-    public Set<DexEncodedMethod> getMethods() {
-      return methods.keySet();
-    }
-
-    public Set<MarkedResolutionTarget> getReasons(DexEncodedMethod method) {
-      return methods.get(method);
-    }
-
-    public boolean add(DexEncodedMethod method, MarkedResolutionTarget reason) {
-      Set<MarkedResolutionTarget> reasons = getReasons(method);
-      if (reasons == null) {
-        reasons = new HashSet<>();
-        reasons.add(reason);
-        methods.put(method, reasons);
-        return true;
-      }
-      reasons.add(reason);
-      return false;
-    }
-  }
-
-  private static final class TargetWithContext<R extends DexMember<?, R>> {
-
-    private final R target;
-    private final DexEncodedMethod context;
-
-    private TargetWithContext(R target, DexEncodedMethod context) {
-      this.target = target;
-      this.context = context;
-    }
-
-    public R getTarget() {
-      return target;
-    }
-
-    public DexEncodedMethod getContext() {
-      return context;
-    }
-
-    @Override
-    public int hashCode() {
-      return target.hashCode() * 31 + context.hashCode();
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-      if (!(obj instanceof TargetWithContext)) {
-        return false;
-      }
-      TargetWithContext other = (TargetWithContext) obj;
-      return (this.target == other.target) && (this.context == other.context);
-    }
-  }
-
   private class AnnotationReferenceMarker implements IndexedItemCollection {
 
     private final DexItem annotationHolder;
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index 45e61a4..c795bcc 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -270,7 +270,7 @@
 
   // TODO(b/142378367): Context is the containing method that is cause of the instantiation.
   // Consider updating call sites with the context information to increase precision where possible.
-  void enqueueMarkInstantiatedAction(
+  public void enqueueMarkInstantiatedAction(
       DexProgramClass clazz,
       DexEncodedMethod context,
       InstantiationReason instantiationReason,
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
index 37be4b3..cf863de 100644
--- a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -30,7 +30,6 @@
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
-import com.android.tools.r8.shaking.Enqueuer.MarkedResolutionTarget;
 import com.android.tools.r8.utils.DequeUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableList.Builder;
@@ -259,25 +258,16 @@
     return KeepReasonWitness.INSTANCE;
   }
 
-  public KeepReasonWitness reportReachableMethodAsLive(
-      DexEncodedMethod encodedMethod, MarkedResolutionTarget reason) {
-    if (keptGraphConsumer != null) {
+  public KeepReasonWitness reportLibraryMethodAsLive(
+      InstantiatedObject instantiation,
+      ProgramMethod derivedMethod,
+      DexClass libraryOrClasspathClass) {
+    // TODO(b/120959039): Report a clear reason for indirect keep of the lambda target.
+    if (keptGraphConsumer != null && instantiation.isClass()) {
       return reportEdge(
-          getMethodGraphNode(reason.method.method),
-          getMethodGraphNode(encodedMethod.method),
-          EdgeKind.OverridingMethod);
-    }
-    return KeepReasonWitness.INSTANCE;
-  }
-
-  public KeepReasonWitness reportReachableMethodAsLive(
-      DexEncodedMethod encodedMethod, Set<MarkedResolutionTarget> reasons) {
-    assert !reasons.isEmpty();
-    if (keptGraphConsumer != null) {
-      MethodGraphNode target = getMethodGraphNode(encodedMethod.method);
-      for (MarkedResolutionTarget reason : reasons) {
-        reportEdge(getMethodGraphNode(reason.method.method), target, EdgeKind.OverridingMethod);
-      }
+          getClassGraphNode(instantiation.asClass().type),
+          getMethodGraphNode(derivedMethod.getMethod().method),
+          EdgeKind.IsLibraryMethod);
     }
     return KeepReasonWitness.INSTANCE;
   }
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 cc86522..c66d4a4 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -49,10 +49,6 @@
     return new InvokedFromLambdaCreatedIn(method);
   }
 
-  public static KeepReason isLibraryMethod(DexProgramClass implementer, DexType libraryType) {
-    return new IsLibraryMethod(implementer.type, libraryType);
-  }
-
   public static KeepReason fieldReferencedIn(DexEncodedMethod method) {
     return new ReferencedFrom(method);
   }
@@ -223,27 +219,6 @@
     }
   }
 
-  public static class IsLibraryMethod extends KeepReason {
-
-    private final DexType implementer;
-    private final DexType libraryType;
-
-    private IsLibraryMethod(DexType implementer, DexType libraryType) {
-      this.implementer = implementer;
-      this.libraryType = libraryType;
-    }
-
-    @Override
-    public EdgeKind edgeKind() {
-      return EdgeKind.IsLibraryMethod;
-    }
-
-    @Override
-    public GraphNode getSourceNode(GraphReporter graphReporter) {
-      return graphReporter.getClassGraphNode(implementer);
-    }
-  }
-
   private static class ReferencedInAnnotation extends KeepReason {
 
     private final DexItem holder;
diff --git a/src/main/java/com/android/tools/r8/shaking/LibraryModeledPredicate.java b/src/main/java/com/android/tools/r8/shaking/LibraryModeledPredicate.java
new file mode 100644
index 0000000..39ee67c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/LibraryModeledPredicate.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.graph.DexType;
+
+@FunctionalInterface
+public interface LibraryModeledPredicate {
+
+  boolean isModeled(DexType type);
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 8b86596..3598fd8 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -70,6 +70,7 @@
     private boolean keepRuleSynthesisForRecompilation = false;
     private boolean configurationDebugging = false;
     private boolean dontUseMixedCaseClassnames = false;
+    private boolean protoShrinking = false;
     private int maxRemovedAndroidLogLevel = 1;
     private ProguardKeepRule keepAllRule;
 
@@ -290,6 +291,10 @@
       this.dontUseMixedCaseClassnames = dontUseMixedCaseClassnames;
     }
 
+    public void enableProtoShrinking() {
+      protoShrinking = true;
+    }
+
     public int getMaxRemovedAndroidLogLevel() {
       return maxRemovedAndroidLogLevel;
     }
@@ -357,6 +362,7 @@
               keepDirectories.build(),
               configurationDebugging,
               dontUseMixedCaseClassnames,
+              protoShrinking,
               maxRemovedAndroidLogLevel,
               keepAllRule);
 
@@ -431,6 +437,7 @@
   private final ProguardPathFilter keepDirectories;
   private final boolean configurationDebugging;
   private final boolean dontUseMixedCaseClassnames;
+  private final boolean protoShrinking;
   private final int maxRemovedAndroidLogLevel;
   private final ProguardKeepRule keepAllRule;
 
@@ -473,6 +480,7 @@
       ProguardPathFilter keepDirectories,
       boolean configurationDebugging,
       boolean dontUseMixedCaseClassnames,
+      boolean protoShrinking,
       int maxRemovedAndroidLogLevel,
       ProguardKeepRule keepAllRule) {
     this.parsedConfiguration = parsedConfiguration;
@@ -513,6 +521,7 @@
     this.keepDirectories = keepDirectories;
     this.configurationDebugging = configurationDebugging;
     this.dontUseMixedCaseClassnames = dontUseMixedCaseClassnames;
+    this.protoShrinking = protoShrinking;
     this.maxRemovedAndroidLogLevel = maxRemovedAndroidLogLevel;
     this.keepAllRule = keepAllRule;
   }
@@ -681,6 +690,10 @@
     return dontUseMixedCaseClassnames;
   }
 
+  public boolean isProtoShrinkingEnabled() {
+    return protoShrinking;
+  }
+
   public int getMaxRemovedAndroidLogLevel() {
     return maxRemovedAndroidLogLevel;
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index f1a2b0f..7e9ed91 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -71,7 +71,6 @@
       // TODO(b/62524562): we may support this later.
       "mergeinterfacesaggressively",
       "android",
-      "shrinkunusedprotofields",
       "allowruntypeandignoreoptimizationpasses",
       "dontshrinkduringoptimization",
       "convert_proto_enum_to_string");
@@ -286,6 +285,8 @@
         if (isOptionalArgumentGiven()) {
           configurationBuilder.setPrintUsageFile(parseFileName(false));
         }
+      } else if (acceptString("shrinkunusedprotofields")) {
+        configurationBuilder.enableProtoShrinking();
       } else if (acceptString("verbose")) {
         configurationBuilder.setVerbose(true);
       } else if (acceptString("ignorewarnings")) {
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 2189be7..928de3a 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -77,7 +77,6 @@
   private final DirectMappedDexApplication application;
   private final Iterable<? extends ProguardConfigurationRule> rules;
   private final Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking = new IdentityHashMap<>();
-  private final Set<DexReference> noOptimization = Sets.newIdentityHashSet();
   private final Set<DexReference> noObfuscation = Sets.newIdentityHashSet();
   private final LinkedHashMap<DexReference, DexReference> reasonAsked = new LinkedHashMap<>();
   private final LinkedHashMap<DexReference, DexReference> checkDiscarded = new LinkedHashMap<>();
@@ -313,7 +312,6 @@
           alwaysClassInline,
           neverMerge,
           alwaysInline,
-          neverInline,
           bypassClinitforInlining);
     }
     assert Sets.intersection(neverInline, alwaysInline).isEmpty()
@@ -321,7 +319,6 @@
         : "A method cannot be marked as both -neverinline and -forceinline/-alwaysinline.";
     return new RootSet(
         noShrinking,
-        noOptimization,
         noObfuscation,
         ImmutableList.copyOf(reasonAsked.values()),
         ImmutableList.copyOf(checkDiscarded.values()),
@@ -410,7 +407,6 @@
         neverInline,
         neverClassInline,
         noShrinking,
-        noOptimization,
         noObfuscation,
         dependentNoShrinking,
         dependentKeepClassCompatRule,
@@ -1101,9 +1097,11 @@
         context.markAsUsed();
       }
       if (!modifiers.allowsOptimization) {
-        noOptimization.add(item.toReference());
+        // The -dontoptimize flag has only effect through the keep all rule, but we still
+        // need to mark the rule as used.
         context.markAsUsed();
       }
+
       if (!modifiers.allowsObfuscation) {
         noObfuscation.add(item.toReference());
         context.markAsUsed();
@@ -1252,7 +1250,6 @@
   public static class RootSet {
 
     public final Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking;
-    public final Set<DexReference> noOptimization;
     private final Set<DexReference> noObfuscation;
     public final ImmutableList<DexReference> reasonAsked;
     public final ImmutableList<DexReference> checkDiscarded;
@@ -1281,7 +1278,6 @@
 
     private RootSet(
         Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking,
-        Set<DexReference> noOptimization,
         Set<DexReference> noObfuscation,
         ImmutableList<DexReference> reasonAsked,
         ImmutableList<DexReference> checkDiscarded,
@@ -1307,7 +1303,6 @@
         Set<ProguardIfRule> ifRules,
         List<DelayedRootSetActionItem> delayedRootSetActionItems) {
       this.noShrinking = noShrinking;
-      this.noOptimization = noOptimization;
       this.noObfuscation = noObfuscation;
       this.reasonAsked = reasonAsked;
       this.checkDiscarded = checkDiscarded;
@@ -1353,7 +1348,6 @@
     void addConsequentRootSet(ConsequentRootSet consequentRootSet, boolean addNoShrinking) {
       neverInline.addAll(consequentRootSet.neverInline);
       neverClassInline.addAll(consequentRootSet.neverClassInline);
-      noOptimization.addAll(consequentRootSet.noOptimization);
       noObfuscation.addAll(consequentRootSet.noObfuscation);
       if (addNoShrinking) {
         consequentRootSet.noShrinking.forEach(
@@ -1436,9 +1430,6 @@
       if (noShrinking.containsKey(original)) {
         noShrinking.put(rewritten, noShrinking.get(original));
       }
-      if (noOptimization.contains(original)) {
-        noOptimization.add(rewritten);
-      }
       if (noObfuscation.contains(original)) {
         noObfuscation.add(rewritten);
       }
@@ -1452,7 +1443,6 @@
 
     public void prune(DexReference reference) {
       noShrinking.remove(reference);
-      noOptimization.remove(reference);
       noObfuscation.remove(reference);
       noSideEffects.remove(reference);
       assumedValues.remove(reference);
@@ -1632,7 +1622,6 @@
       builder.append("RootSet");
 
       builder.append("\nnoShrinking: " + noShrinking.size());
-      builder.append("\nnoOptimization: " + noOptimization.size());
       builder.append("\nnoObfuscation: " + noObfuscation.size());
       builder.append("\nreasonAsked: " + reasonAsked.size());
       builder.append("\ncheckDiscarded: " + checkDiscarded.size());
@@ -1658,7 +1647,6 @@
     final Set<DexMethod> neverInline;
     final Set<DexType> neverClassInline;
     final Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking;
-    final Set<DexReference> noOptimization;
     final Set<DexReference> noObfuscation;
     final Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking;
     final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
@@ -1668,7 +1656,6 @@
         Set<DexMethod> neverInline,
         Set<DexType> neverClassInline,
         Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking,
-        Set<DexReference> noOptimization,
         Set<DexReference> noObfuscation,
         Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking,
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
@@ -1676,7 +1663,6 @@
       this.neverInline = Collections.unmodifiableSet(neverInline);
       this.neverClassInline = Collections.unmodifiableSet(neverClassInline);
       this.noShrinking = Collections.unmodifiableMap(noShrinking);
-      this.noOptimization = Collections.unmodifiableSet(noOptimization);
       this.noObfuscation = Collections.unmodifiableSet(noObfuscation);
       this.dependentNoShrinking = Collections.unmodifiableMap(dependentNoShrinking);
       this.dependentKeepClassCompatRule = Collections.unmodifiableMap(dependentKeepClassCompatRule);
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 05d281b..1fb713e 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -175,6 +175,7 @@
     clazz.removeEnclosingMethod(this::isAttributeReferencingPrunedItem);
     rewriteNestAttributes(clazz);
     usagePrinter.visited();
+    assert verifyNoDeadFields(clazz);
   }
 
   private void rewriteNestAttributes(DexProgramClass clazz) {
@@ -355,4 +356,12 @@
   public Collection<DexReference> getMethodsToKeepForConfigurationDebugging() {
     return Collections.unmodifiableCollection(methodsToKeepForConfigurationDebugging);
   }
+
+  private boolean verifyNoDeadFields(DexProgramClass clazz) {
+    for (DexEncodedField field : clazz.fields()) {
+      assert !field.getOptimizationInfo().isDead()
+          : "Expected field `" + field.field.toSourceString() + "` to be absent";
+    }
+    return true;
+  }
 }
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 3ba6b3a..43110c2 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -28,7 +28,7 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
-import com.android.tools.r8.graph.LookupResult;
+import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ResolutionResult;
@@ -45,6 +45,7 @@
 import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.FieldSignatureEquivalence;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.Timing;
@@ -499,8 +500,14 @@
       if (!(method.accessFlags.isPublic() || method.accessFlags.isPrivate())) {
         return true;
       }
+      // Check if the target is overriding and narrowing the access.
+      if (method.accessFlags.isPublic()) {
+        DexEncodedMethod targetOverride = target.lookupVirtualMethod(method.method);
+        if (targetOverride != null && !targetOverride.accessFlags.isPublic()) {
+          return true;
+        }
+      }
     }
-
     // Check that all accesses from [source] to classes or members from the current package of
     // [source] will continue to work. This is guaranteed if the methods of [source] do not access
     // any private or protected classes or members from the current package of [source].
@@ -736,26 +743,34 @@
       //   }
       for (DexEncodedMethod method : defaultMethods) {
         // Conservatively find all possible targets for this method.
-        LookupResult lookupResult =
+        LookupResultSuccess lookupResult =
             appInfo
                 .resolveMethodOnInterface(method.method.holder, method.method)
-                .lookupVirtualDispatchTargets(target, appView);
-        assert lookupResult.isLookupResultSuccess();
-        if (lookupResult.isLookupResultFailure()) {
+                .lookupVirtualDispatchTargets(target, appInfo)
+                .asLookupResultSuccess();
+        assert lookupResult != null;
+        if (lookupResult == null) {
           return true;
         }
-        assert lookupResult.isLookupResultSuccess();
-        Set<DexEncodedMethod> interfaceTargets =
-            lookupResult.asLookupResultSuccess().getMethodTargets();
-        // If [method] is not even an interface-target, then we can safely merge it. Otherwise we
-        // need to check for a conflict.
-        if (interfaceTargets.remove(method)) {
-          for (DexEncodedMethod interfaceTarget : interfaceTargets) {
-            DexClass enclosingClass = appInfo.definitionFor(interfaceTarget.method.holder);
-            if (enclosingClass != null && enclosingClass.isInterface()) {
-              // Found another default method that is different from the one in [source], aborting.
-              return true;
-            }
+        if (lookupResult.contains(method)) {
+          Box<Boolean> found = new Box<>(false);
+          lookupResult.forEach(
+              interfaceTarget -> {
+                if (interfaceTarget.getMethod() == method) {
+                  return;
+                }
+                DexClass enclosingClass = interfaceTarget.getHolder();
+                if (enclosingClass != null && enclosingClass.isInterface()) {
+                  // Found a default method that is different from the one in [source], aborting.
+                  found.set(true);
+                }
+              },
+              lambdaTarget -> {
+                // The merger should already have excluded lambda implemented interfaces.
+                assert false;
+              });
+          if (found.get()) {
+            return true;
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
index 8c5c3b2..60b6f7d 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -12,36 +12,36 @@
  * Android API level description
  */
 public enum AndroidApiLevel {
-  R(30),  // Speculative, this can change.
-  Q(29),
-  P(28),
-  O_MR1(27),
-  O(26),
-  N_MR1(25),
-  N(24),
-  M(23),
-  L_MR1(22),
-  L(21),
-  K_WATCH(20),
-  K(19),
-  J_MR2(18),
-  J_MR1(17),
-  J(16),
-  I_MR1(15),
-  I(14),
-  H_MR2(13),
-  H_MR1(12),
-  H(11),
-  G_MR1(10),
-  G(9),
-  F(8),
-  E_MR1(7),
-  E_0_1(6),
-  E(5),
-  D(4),
-  C(3),
+  B(1),
   B_1_1(2),
-  B(1);
+  C(3),
+  D(4),
+  E(5),
+  E_0_1(6),
+  E_MR1(7),
+  F(8),
+  G(9),
+  G_MR1(10),
+  H(11),
+  H_MR1(12),
+  H_MR2(13),
+  I(14),
+  I_MR1(15),
+  J(16),
+  J_MR1(17),
+  J_MR2(18),
+  K(19),
+  K_WATCH(20),
+  L(21),
+  L_MR1(22),
+  M(23),
+  N(24),
+  N_MR1(25),
+  O(26),
+  O_MR1(27),
+  P(28),
+  Q(29),
+  R(30);  // Speculative, this can change.
 
   public static final AndroidApiLevel LATEST = Q;
 
diff --git a/src/main/java/com/android/tools/r8/utils/BooleanUtils.java b/src/main/java/com/android/tools/r8/utils/BooleanUtils.java
index cc94778..75d47f6 100644
--- a/src/main/java/com/android/tools/r8/utils/BooleanUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/BooleanUtils.java
@@ -11,6 +11,10 @@
     return value ? 1 : 0;
   }
 
+  public static long longValue(boolean value) {
+    return value ? 1L : 0L;
+  }
+
   public static Boolean[] values() {
     return VALUES;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java b/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
index 78d47c2..4dc6d48 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
@@ -18,10 +18,16 @@
 public class ExceptionDiagnostic extends DiagnosticWithThrowable {
 
   private final Origin origin;
+  private final Position position;
 
-  public ExceptionDiagnostic(Throwable e, Origin origin) {
+  public ExceptionDiagnostic(Throwable e, Origin origin, Position position) {
     super(e);
     this.origin = origin;
+    this.position = position;
+  }
+
+  public ExceptionDiagnostic(Throwable e, Origin origin) {
+    this(e, origin, Position.UNKNOWN);
   }
 
   public ExceptionDiagnostic(ResourceException e) {
@@ -35,7 +41,7 @@
 
   @Override
   public Position getPosition() {
-    return Position.UNKNOWN;
+    return position;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
index d12c2a4..79e3818 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
@@ -11,12 +11,14 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.position.Position;
 import com.google.common.collect.ObjectArrays;
 import java.io.IOException;
 import java.nio.file.FileSystemException;
 import java.nio.file.Paths;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
 public abstract class ExceptionUtils {
 
@@ -68,7 +70,7 @@
       } catch (IOException e) {
         throw reporter.fatalError(new ExceptionDiagnostic(e, extractIOExceptionOrigin(e)));
       } catch (CompilationError e) {
-        throw reporter.fatalError(e.toStringDiagnostic());
+        throw reporter.fatalError(new ExceptionDiagnostic(e, e.getOrigin(), e.getPosition()));
       } catch (ResourceException e) {
         throw reporter.fatalError(new ExceptionDiagnostic(e, e.getOrigin()));
       } catch (AssertionError e) {
@@ -132,4 +134,15 @@
       throw new RuntimeException(executionException.getMessage(), cause);
     }
   }
+
+  public static <T> T withOriginAttachmentHandler(
+      Origin origin, Position position, Supplier<T> action) {
+    try {
+      return action.get();
+    } catch (CompilationError e) {
+      throw e.withAdditionalOriginAndPositionInfo(origin, position);
+    } catch (RuntimeException e) {
+      throw new CompilationError(e.getMessage(), e, origin, position);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 8d04c47..c3df10f 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -148,6 +148,18 @@
       disableAllOptimizations();
     }
     configurationDebugging = proguardConfiguration.isConfigurationDebugging();
+    if (proguardConfiguration.isProtoShrinkingEnabled()) {
+      enableProtoShrinking();
+    }
+  }
+
+  void enableProtoShrinking() {
+    applyInliningToInlinee = true;
+    enableFieldBitAccessAnalysis = true;
+    enableStringSwitchConversion = true;
+    protoShrinking.enableGeneratedMessageLiteShrinking = true;
+    protoShrinking.enableGeneratedMessageLiteBuilderShrinking = true;
+    protoShrinking.enableGeneratedExtensionRegistryShrinking = true;
   }
 
   void disableAllOptimizations() {
@@ -256,6 +268,7 @@
   public boolean enableStringSwitchConversion =
       System.getProperty("com.android.tools.r8.stringSwitchConversion") != null;
   public boolean enableEnumValueOptimization = true;
+  public boolean enableEnumSwitchMapRemoval = true;
   public final OutlineOptions outline = new OutlineOptions();
   public boolean enableInitializedClassesInInstanceMethodsAnalysis = true;
   public boolean enableRedundantFieldLoadElimination = true;
@@ -996,17 +1009,10 @@
 
   public static class ProtoShrinkingOptions {
 
-    public boolean enableGeneratedExtensionRegistryShrinking =
-        System.getProperty("com.android.tools.r8.generatedExtensionRegistryShrinking") != null;
-
-    public boolean enableGeneratedMessageLiteShrinking =
-        System.getProperty("com.android.tools.r8.generatedMessageLiteShrinking") != null;
-
-    public boolean enableGeneratedMessageLiteBuilderShrinking =
-        System.getProperty("com.android.tools.r8.generatedMessageLiteBuilderShrinking") != null;
-
-    public boolean traverseOneOfAndRepeatedProtoFields =
-        System.getProperty("com.android.tools.r8.traverseOneOfAndRepeatedProtoFields") == null;
+    public boolean enableGeneratedExtensionRegistryShrinking = false;
+    public boolean enableGeneratedMessageLiteShrinking = false;
+    public boolean enableGeneratedMessageLiteBuilderShrinking = false;
+    public boolean traverseOneOfAndRepeatedProtoFields = false;
 
     public boolean isProtoShrinkingEnabled() {
       return enableGeneratedExtensionRegistryShrinking
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
index d602a51..3681a77 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
@@ -4,13 +4,10 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.dex.ApplicationReader.ProgramClassConflictResolver;
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.DuplicateTypesDiagnostic;
 import com.android.tools.r8.graph.ClassKind;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
 import com.android.tools.r8.references.Reference;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -85,9 +82,6 @@
 
   private static DexProgramClass mergeClasses(
       Reporter reporter, DexProgramClass a, DexProgramClass b) {
-    if (DesugaredLibraryWrapperSynthesizer.isSynthesizedWrapper(a.type)) {
-      return mergeWrappers(a, b);
-    }
     if (a.type.isD8R8SynthesizedClassType()) {
       assert assertEqualClasses(a, b);
       return a;
@@ -100,22 +94,4 @@
     assert a.directMethods().size() == b.directMethods().size();
     return true;
   }
-
-  private static DexProgramClass mergeWrappers(DexProgramClass a, DexProgramClass b) {
-    DexEncodedMethod aMethod = findConversionMethod(a);
-    DexEncodedMethod bMethod = findConversionMethod(b);
-    return aMethod.getCode().estimatedSizeForInlining()
-            > bMethod.getCode().estimatedSizeForInlining()
-        ? a
-        : b;
-  }
-
-  private static DexEncodedMethod findConversionMethod(DexProgramClass clazz) {
-    for (DexEncodedMethod dexEncodedMethod : clazz.directMethods()) {
-      if (!dexEncodedMethod.isInstanceInitializer()) {
-        return dexEncodedMethod;
-      }
-    }
-    throw new CompilationError("A wrapper should have a conversion method.");
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
index d389826..3e35802 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -15,6 +15,16 @@
   private final Deque<T> workingList = new ArrayDeque<>();
   private final Set<T> seen;
 
+  public static <T> WorkList<T> newEqualityWorkList() {
+    return new WorkList<T>(EqualityTest.HASH);
+  }
+
+  public static <T> WorkList<T> newEqualityWorkList(Iterable<T> items) {
+    WorkList<T> workList = new WorkList<>(EqualityTest.HASH);
+    workList.addIfNotSeen(items);
+    return workList;
+  }
+
   public static <T> WorkList<T> newIdentityWorkList() {
     return new WorkList<T>(EqualityTest.IDENTITY);
   }
@@ -43,10 +53,12 @@
     }
   }
 
-  public void addIfNotSeen(T item) {
+  public boolean addIfNotSeen(T item) {
     if (seen.add(item)) {
       workingList.addLast(item);
+      return true;
     }
+    return false;
   }
 
   public boolean hasNext() {
diff --git a/src/test/examples/shaking1/print-mapping.ref b/src/test/examples/shaking1/print-mapping-cf.ref
similarity index 64%
rename from src/test/examples/shaking1/print-mapping.ref
rename to src/test/examples/shaking1/print-mapping-cf.ref
index d346c04..87a7bc0 100644
--- a/src/test/examples/shaking1/print-mapping.ref
+++ b/src/test/examples/shaking1/print-mapping-cf.ref
@@ -1,6 +1,5 @@
 shaking1.Shaking -> shaking1.Shaking:
 shaking1.Used -> a.a:
-    java.lang.String name -> a
     1:1:java.lang.String method():17:17 -> a
     1:1:void main(java.lang.String[]):8:8 -> main
-    1:2:void <init>(java.lang.String):12:13 -> <init>
+    1:1:void <init>(java.lang.String):12:12 -> <init>
diff --git a/src/test/examples/shaking1/print-mapping-dex.ref b/src/test/examples/shaking1/print-mapping-dex.ref
new file mode 100644
index 0000000..602e5f8
--- /dev/null
+++ b/src/test/examples/shaking1/print-mapping-dex.ref
@@ -0,0 +1,5 @@
+shaking1.Shaking -> shaking1.Shaking:
+shaking1.Used -> a.a:
+    java.lang.String method() -> a
+    1:1:void main(java.lang.String[]):8:8 -> main
+    1:1:void <init>(java.lang.String):12:12 -> <init>
diff --git a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
index 1defe48..ce6cbcc 100644
--- a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
+++ b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
@@ -132,7 +132,6 @@
           // java.util.concurrent.Flow$Subscriber present in JDK9+.
           testBuilder
               .run(parameters.getRuntime(), "MySubscriber")
-              .disassemble()
               .assertSuccessWithOutputLines("Got : 1", "Got : 2", "Got : 3", "Done");
         }
       }
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 14d460e..57fc2ec 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -81,11 +81,6 @@
             instanceof DexFilePerClassFileConsumer);
   }
 
-  @Test(expected = CompilationFailedException.class)
-  public void disallowClassFileConsumer() throws Throwable {
-    D8Command.builder().setProgramConsumer(ClassFileConsumer.emptyConsumer()).build();
-  }
-
   @Test
   public void desugaredLibraryKeepRuleConsumer() throws Exception {
     StringConsumer stringConsumer = StringConsumer.emptyConsumer();
diff --git a/src/test/java/com/android/tools/r8/D8TestCompileResult.java b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
index 80b23ac..8b91d57 100644
--- a/src/test/java/com/android/tools/r8/D8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
@@ -3,14 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.AndroidApp;
 
 public class D8TestCompileResult extends TestCompileResult<D8TestCompileResult, D8TestRunResult> {
   D8TestCompileResult(TestState state, AndroidApp app, OutputMode outputMode) {
     super(state, app, outputMode);
-    assert ToolHelper.verifyValidOutputMode(Backend.DEX, outputMode);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index f847f34..569c222 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -427,6 +427,18 @@
     return self();
   }
 
+  public T enableProtoShrinking() {
+    return enableProtoShrinking(true);
+  }
+
+  public T enableProtoShrinking(boolean traverseOneOfAndRepeatedProtoFields) {
+    if (traverseOneOfAndRepeatedProtoFields) {
+      addOptionsModification(
+          options -> options.protoShrinking().traverseOneOfAndRepeatedProtoFields = true);
+    }
+    return addKeepRules("-shrinkunusedprotofields");
+  }
+
   public T enableSideEffectAnnotations() {
     if (!enableSideEffectAnnotations) {
       enableSideEffectAnnotations = true;
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 4f64c5b..78dee67 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -237,6 +237,11 @@
     return self();
   }
 
+  public T setEnableDesugaring(boolean enableDesugaring) {
+    builder.setEnableDesugaring(enableDesugaring);
+    return self();
+  }
+
   public OutputMode getOutputMode() {
     if (programConsumer instanceof DexIndexedConsumer) {
       return OutputMode.DexIndexed;
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index b29d851..8c6b864 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -7,9 +7,12 @@
 import com.android.tools.r8.TestRuntime.NoneRuntime;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -147,8 +150,10 @@
   private static final AndroidApiLevel lowestCompilerApiLevel = AndroidApiLevel.B;
 
   private boolean enableApiLevels = false;
+  private boolean enableApiLevelsForCf = false;
 
   private Predicate<AndroidApiLevel> apiLevelFilter = param -> false;
+  private List<AndroidApiLevel> explicitApiLevels = new ArrayList<>();
 
   private TestParametersBuilder withApiFilter(Predicate<AndroidApiLevel> filter) {
     enableApiLevels = true;
@@ -160,7 +165,13 @@
     return withApiFilter(api -> true);
   }
 
+  public TestParametersBuilder withAllApiLevelsAlsoForCf() {
+    enableApiLevelsForCf = true;
+    return withAllApiLevels();
+  }
+
   public TestParametersBuilder withApiLevel(AndroidApiLevel api) {
+    explicitApiLevels.add(api);
     return withApiFilter(api::equals);
   }
 
@@ -190,9 +201,17 @@
   }
 
   public Stream<TestParameters> createParameters(TestRuntime runtime) {
-    if (!enableApiLevels || !runtime.isDex()) {
+    if (!enableApiLevels) {
       return Stream.of(new TestParameters(runtime));
     }
+    if (!runtime.isDex()) {
+      if (!enableApiLevelsForCf) {
+        return Stream.of(new TestParameters(runtime));
+      }
+      return Stream.of(
+          new TestParameters(runtime, AndroidApiLevel.B),
+          new TestParameters(runtime, AndroidApiLevel.LATEST));
+    }
     List<AndroidApiLevel> sortedApiLevels =
         AndroidApiLevel.getAndroidApiLevelsSorted().stream()
             .filter(apiLevelFilter)
@@ -210,9 +229,15 @@
         AndroidApiLevel highestApplicable = sortedApiLevels.get(i);
         if (highestApplicable.getLevel() <= vmLevel.getLevel()
             && lowestApplicable != highestApplicable) {
-          return Stream.of(
-              new TestParameters(runtime, lowestApplicable),
-              new TestParameters(runtime, highestApplicable));
+          Set<AndroidApiLevel> set = new TreeSet<>();
+          set.add(lowestApplicable);
+          set.add(highestApplicable);
+          for (AndroidApiLevel explicitApiLevel : explicitApiLevels) {
+            if (explicitApiLevel.getLevel() <= vmLevel.getLevel()) {
+              set.add(explicitApiLevel);
+            }
+          }
+          return set.stream().map(api -> new TestParameters(runtime, api));
         }
       }
     }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index f9c1086..9c144c8 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -184,8 +184,7 @@
   }
 
   public static boolean verifyValidOutputMode(Backend backend, OutputMode outputMode) {
-    return (backend == Backend.CF && outputMode == OutputMode.ClassFile)
-        || (backend == Backend.DEX && outputMode != OutputMode.ClassFile);
+    return (backend == Backend.CF && outputMode == OutputMode.ClassFile) || backend == Backend.DEX;
   }
 
   public static StringConsumer consumeString(Consumer<String> consumer) {
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java
index b9883ef..2cb3e95 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -32,7 +33,8 @@
 
 @NeverMerge
 class MySerializable implements Serializable {
-  transient int value;
+
+  @NeverPropagateValue transient int value;
 
   MySerializable(int value) {
     this.value = value;
@@ -169,6 +171,7 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(CLASSES)
             .enableInliningAnnotations()
+            .enableMemberValuePropagationAnnotations()
             .addKeepRuleFiles(configuration)
             .setMinApi(parameters.getApiLevel())
             .compile();
diff --git a/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java b/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java
new file mode 100644
index 0000000..6f2e729
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.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.classmerging;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.LookupResult;
+import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KeptTargetsIncompleteDiamondTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public KeptTargetsIncompleteDiamondTest(TestParameters parameters) {
+    // Empty to satisfy construction of none-runtime.
+  }
+
+  private AppView<AppInfoWithLiveness> computeAppViewWithLiveness(
+      Class<?> methodToBeKept, Class<?> classToBeKept) throws Exception {
+    return computeAppViewWithLiveness(
+        buildClasses(I.class, J.class, K.class, L.class, A.class).build(),
+        factory -> {
+          List<ProguardConfigurationRule> rules = new ArrayList<>();
+          rules.addAll(buildKeepRuleForClassAndMethods(methodToBeKept, factory));
+          rules.addAll(buildKeepRuleForClass(classToBeKept, factory));
+          return rules;
+        });
+  }
+
+  @Test
+  public void testRefinedReceiverOnStaticTarget() throws Exception {
+    // I { <-- kept
+    //   foo() <-- kept, initial
+    // }
+    //
+    // J {
+    //   default foo() { ... } <-- kept, resolved
+    // }
+    //
+    // K {  }
+    //
+    // L extends I, K { } <-- upperbound
+    //
+    // A implements L { <-- lowerbound
+    //
+    // }
+    AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(I.class, I.class);
+    DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory());
+    ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
+    DexType typeI = buildType(I.class, appView.dexItemFactory());
+    DexType typeL = buildType(L.class, appView.dexItemFactory());
+    DexType typeA = buildType(A.class, appView.dexItemFactory());
+    DexProgramClass classI = appView.definitionForProgramType(typeI);
+    DexProgramClass classL = appView.definitionForProgramType(typeL);
+    DexProgramClass classA = appView.definitionForProgramType(typeA);
+    LookupResult lookupResult =
+        resolutionResult.lookupVirtualDispatchTargets(classI, appView.appInfo(), classL, classA);
+    assertTrue(lookupResult.isLookupResultSuccess());
+    LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
+    Set<String> targets = new HashSet<>();
+    lookupResultSuccess.forEach(
+        methodTarget -> targets.add(methodTarget.asMethodTarget().getMethod().qualifiedName()),
+        lambdaTarget -> {
+          assert false;
+        });
+    Set<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+    assertTrue(lookupResultSuccess.isComplete());
+  }
+
+  @Test
+  public void testRefinedReceiverOnStaticTargetInRange() throws Exception {
+    // I { <-- kept
+    //   foo() <-- kept, initial, upperbound
+    // }
+    //
+    // J {
+    //   default foo() { ... } <-- kept, resolved
+    // }
+    //
+    // K {  }
+    //
+    // L extends I, K { }
+    //
+    // A implements L { <-- lowerbound
+    //
+    // }
+    AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(I.class, I.class);
+    DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory());
+    ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
+    DexType typeI = buildType(I.class, appView.dexItemFactory());
+    DexType typeL = buildType(L.class, appView.dexItemFactory());
+    DexType typeA = buildType(A.class, appView.dexItemFactory());
+    DexProgramClass classI = appView.definitionForProgramType(typeI);
+    DexProgramClass classA = appView.definitionForProgramType(typeA);
+    LookupResult lookupResult =
+        resolutionResult.lookupVirtualDispatchTargets(classI, appView.appInfo(), classI, classA);
+    assertTrue(lookupResult.isLookupResultSuccess());
+    LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
+    Set<String> targets = new HashSet<>();
+    lookupResultSuccess.forEach(
+        methodTarget -> targets.add(methodTarget.asMethodTarget().getMethod().qualifiedName()),
+        lambdaTarget -> {
+          assert false;
+        });
+    Set<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+    assertTrue(lookupResultSuccess.isComplete());
+  }
+
+  @Test
+  public void testRefinedReceiverWithKeptDispatchTarget() throws Exception {
+    // I {
+    //   foo() <-- initial
+    // }
+    //
+    // J { <-- kept
+    //   default foo() { ... } <-- kept, resolved
+    // }
+    //
+    // K {  }
+    //
+    // L extends I, K { }
+    //
+    // A implements L { <-- lowerbound, upperbound
+    //
+    // }
+    AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(J.class, J.class);
+    DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory());
+    ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
+    DexType typeI = buildType(I.class, appView.dexItemFactory());
+    DexType typeB = buildType(A.class, appView.dexItemFactory());
+    DexProgramClass classI = appView.definitionForProgramType(typeI);
+    DexProgramClass classA = appView.definitionForProgramType(typeB);
+    LookupResult lookupResult =
+        resolutionResult.lookupVirtualDispatchTargets(classI, appView.appInfo(), classA, classA);
+    assertTrue(lookupResult.isLookupResultSuccess());
+    LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
+    Set<String> targets = new HashSet<>();
+    lookupResultSuccess.forEach(
+        methodTarget -> targets.add(methodTarget.asMethodTarget().getMethod().qualifiedName()),
+        lambdaTarget -> {
+          assert false;
+        });
+    Set<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+    assertTrue(lookupResultSuccess.isIncomplete());
+  }
+
+  @Test
+  public void testRefinedReceiverWithKeptRefinedReceiver() throws Exception {
+    // I {
+    //   foo() <-- initial
+    // }
+    //
+    // J {
+    //   default foo() { ... } <-- kept, resolved
+    // }
+    //
+    // K {  }
+    //
+    // A implements I, K { <-- kept, lowerbound, upperbound
+    //
+    // }
+    AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(J.class, A.class);
+    DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory());
+    ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
+    DexType typeI = buildType(I.class, appView.dexItemFactory());
+    DexType typeB = buildType(A.class, appView.dexItemFactory());
+    DexProgramClass classI = appView.definitionForProgramType(typeI);
+    DexProgramClass classA = appView.definitionForProgramType(typeB);
+    LookupResult lookupResult =
+        resolutionResult.lookupVirtualDispatchTargets(classI, appView.appInfo(), classA, classA);
+    assertTrue(lookupResult.isLookupResultSuccess());
+    LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
+    Set<String> targets = new HashSet<>();
+    lookupResultSuccess.forEach(
+        methodTarget -> targets.add(methodTarget.asMethodTarget().getMethod().qualifiedName()),
+        lambdaTarget -> {
+          assert false;
+        });
+    Set<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+    assertTrue(lookupResultSuccess.isIncomplete());
+  }
+
+  @Test
+  public void testRefinedReceiverWithKeptClassOnInitial() throws Exception {
+    // I { <-- kept
+    //   foo() <-- kept, initial
+    // }
+    //
+    // J {
+    //   default foo() { ... } <-- resolved
+    // }
+    //
+    // K {  }
+    //
+    // L extends I, K { }
+    //
+    // A implements L { <-- lowerbound, upperbound
+    //
+    // }
+    AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(I.class, I.class);
+    DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory());
+    ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
+    DexType typeI = buildType(I.class, appView.dexItemFactory());
+    DexType typeB = buildType(A.class, appView.dexItemFactory());
+    DexProgramClass classI = appView.definitionForProgramType(typeI);
+    DexProgramClass classA = appView.definitionForProgramType(typeB);
+    LookupResult lookupResult =
+        resolutionResult.lookupVirtualDispatchTargets(classI, appView.appInfo(), classA, classA);
+    assertTrue(lookupResult.isLookupResultSuccess());
+    LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
+    Set<String> targets = new HashSet<>();
+    lookupResultSuccess.forEach(
+        methodTarget -> targets.add(methodTarget.asMethodTarget().getMethod().qualifiedName()),
+        lambdaTarget -> {
+          assert false;
+        });
+    Set<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+    assertTrue(lookupResultSuccess.isComplete());
+  }
+
+  public interface I {
+    void foo();
+  }
+
+  public interface J extends I {
+    @Override
+    default void foo() {
+      System.out.println("K.foo");
+    }
+  }
+
+  public interface K extends J {}
+
+  public interface L extends I, K {}
+
+  public static class A implements L {}
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java
new file mode 100644
index 0000000..ec742ee
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DesugarToClassFile extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public DesugarToClassFile(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private void checkSomething(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(TestClass.class);
+    assertThat(classSubject, isPresent());
+  }
+
+  private void checkDiagnostics(TestDiagnosticMessages messages) {
+    messages.assertOnlyWarnings();
+    messages.assertWarningsCount(1);
+    assertThat(
+        messages.getWarnings().get(0).getDiagnosticMessage(),
+        containsString("not officially supported"));
+  }
+
+  @Test
+  public void test() throws Exception {
+    // Use D8 to desugar with Java classfile output.
+    Path jar =
+        testForD8()
+            .addInnerClasses(DesugarToClassFile.class)
+            .setMinApi(parameters.getApiLevel())
+            .setOutputMode(OutputMode.ClassFile)
+            .compile()
+            .inspectDiagnosticMessages(this::checkDiagnostics)
+            .inspect(this::checkSomething)
+            .writeToZip();
+
+    if (parameters.getRuntime().isCf()) {
+      // Run on the JVM
+      testForJvm()
+          .addProgramFiles(jar)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutputLines("Hello, world!", "I::foo");
+    } else {
+      assert parameters.getRuntime().isDex();
+      // Convert to DEX without desugaring.
+      testForD8()
+          .addProgramFiles(jar)
+          .setEnableDesugaring(false)
+          .setMinApi(parameters.getApiLevel())
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutputLines("Hello, world!", "I::foo");
+    }
+  }
+
+  interface I {
+    default void foo() {
+      System.out.println("I::foo");
+    }
+  }
+
+  static class A implements I {}
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Runnable runnable =
+          () -> {
+            System.out.println("Hello, world!");
+          };
+      runnable.run();
+
+      new A().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
index 190e7ee..523a018 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
@@ -17,13 +17,13 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import it.unimi.dsi.fastutil.ints.Int2IntAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2IntSortedMap;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.HashSet;
 import java.util.List;
-import java.util.NavigableMap;
 import java.util.Set;
-import java.util.TreeMap;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -33,7 +33,7 @@
   private final Class<?> testClass;
   private final Path testJar;
   private final String testClassName;
-  private final NavigableMap<AndroidApiLevel, Integer> invokeStaticCounts = new TreeMap<>();
+  private final Int2IntSortedMap invokeStaticCounts = new Int2IntAVLTreeMap();
   private final Set<String> ignoredInvokes = new HashSet<>();
 
   AbstractBackportTest(TestParameters parameters, Class<?> targetClass,
@@ -64,11 +64,16 @@
     }
 
     // Assume all method calls will be rewritten on the lowest API level.
-    invokeStaticCounts.put(AndroidApiLevel.B, 0);
+    invokeStaticCounts.put(AndroidApiLevel.B.getLevel(), 0);
   }
 
   void registerTarget(AndroidApiLevel apiLevel, int invokeStaticCount) {
-    invokeStaticCounts.put(apiLevel, invokeStaticCount);
+    invokeStaticCounts.put(apiLevel.getLevel(), invokeStaticCount);
+  }
+
+  private int getTargetInvokesCount(AndroidApiLevel apiLevel) {
+    int key = invokeStaticCounts.headMap(apiLevel.getLevel() + 1).lastIntKey();
+    return invokeStaticCounts.get(key);
   }
 
   void ignoreInvokes(String methodName) {
@@ -119,7 +124,7 @@
         .collect(toList());
 
     AndroidApiLevel apiLevel = parameters.getApiLevel();
-    long expectedTargetInvokes = invokeStaticCounts.ceilingEntry(apiLevel).getValue();
+    long expectedTargetInvokes = getTargetInvokesCount(apiLevel);
     long actualTargetInvokes = javaInvokeStatics.size();
     assertEquals("Expected "
         + expectedTargetInvokes
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
index 732c0b9..3cb6218 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
@@ -9,6 +9,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -32,7 +33,12 @@
   @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
   public static List<Object[]> data() {
     return buildParameters(
-        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+        BooleanUtils.values(),
+        getTestParameters()
+            .withDexRuntimes()
+            .withAllApiLevels()
+            .withApiLevel(AndroidApiLevel.N)
+            .build());
   }
 
   public JavaTimeTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
index 687a673..3d1b999 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
@@ -4,16 +4,12 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
 
-import static junit.framework.TestCase.assertEquals;
-
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import java.nio.file.Path;
 import java.util.List;
 import java.util.function.BiFunction;
@@ -25,7 +21,6 @@
 import java.util.function.IntSupplier;
 import java.util.function.LongConsumer;
 import java.util.function.LongSupplier;
-import java.util.stream.Collectors;
 import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.BeforeClass;
@@ -75,7 +70,6 @@
         .addLibraryClasses(CustomLibClass.class)
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
-        .inspect(this::assertSingleWrappers)
         .addDesugaredCoreLibraryRunClassPath(
             this::buildDesugaredLibrary,
             parameters.getApiLevel(),
@@ -107,26 +101,6 @@
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
-  private void assertSingleWrappers(CodeInspector i) {
-    List<FoundClassSubject> intSupplierWrapperClasses =
-        i.allClasses().stream()
-            .filter(c -> c.getOriginalName().contains("IntSupplier"))
-            .collect(Collectors.toList());
-    assertEquals(
-        "Expected 1 IntSupplier wrapper but got " + intSupplierWrapperClasses,
-        1,
-        intSupplierWrapperClasses.size());
-
-    List<FoundClassSubject> doubleSupplierWrapperClasses =
-        i.allClasses().stream()
-            .filter(c -> c.getOriginalName().contains("DoubleSupplier"))
-            .collect(Collectors.toList());
-    assertEquals(
-        "Expected 1 DoubleSupplier wrapper but got " + doubleSupplierWrapperClasses,
-        1,
-        doubleSupplierWrapperClasses.size());
-  }
-
   @Test
   public void testWrapperWithChecksum() throws Exception {
     Assume.assumeTrue(
@@ -142,7 +116,7 @@
         .inspect(
             inspector -> {
               Assert.assertEquals(
-                  8,
+                  9,
                   inspector.allClasses().stream()
                       .filter(
                           clazz ->
@@ -151,7 +125,7 @@
                                   .contains(DesugaredLibraryWrapperSynthesizer.TYPE_WRAPPER_SUFFIX))
                       .count());
               Assert.assertEquals(
-                  6,
+                  9,
                   inspector.allClasses().stream()
                       .filter(
                           clazz ->
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeConflictTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeConflictTest.java
deleted file mode 100644
index 26f4629..0000000
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeConflictTest.java
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import java.nio.file.Path;
-import java.util.function.IntSupplier;
-import org.junit.Test;
-
-public class WrapperMergeConflictTest extends DesugaredLibraryTestBase {
-
-  @Test
-  public void testWrapperMergeConflict() throws Exception {
-    Path customLib =
-        testForD8()
-            .addProgramClasses(CustomLibClass.class)
-            .setMinApi(AndroidApiLevel.B)
-            .compile()
-            .writeToZip();
-    Path path1 =
-        testForD8()
-            .addProgramClasses(Executor1.class)
-            .addLibraryClasses(CustomLibClass.class)
-            .setMinApi(AndroidApiLevel.B)
-            .enableCoreLibraryDesugaring(AndroidApiLevel.B)
-            .compile()
-            .inspect(i -> this.assertWrappers(i, 2))
-            .writeToZip();
-    Path path2 =
-        testForD8()
-            .addProgramClasses(Executor2.class)
-            .addLibraryClasses(CustomLibClass.class)
-            .setMinApi(AndroidApiLevel.B)
-            .enableCoreLibraryDesugaring(AndroidApiLevel.B)
-            .compile()
-            .inspect(i -> this.assertWrappers(i, 1))
-            .writeToZip();
-    testForD8()
-        .addProgramFiles(path1, path2)
-        .addLibraryClasses(CustomLibClass.class)
-        .compile()
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
-        .inspect(this::assertBigWrappersPresent)
-        .addRunClasspathFiles(customLib)
-        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor1.class)
-        .assertSuccessWithOutput(StringUtils.lines("3", "5"));
-  }
-
-  private void assertBigWrappersPresent(CodeInspector inspector) {
-    assertWrappers(inspector, 2);
-    inspector.allClasses().stream()
-        .filter(
-            c -> c.getOriginalName().startsWith(DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX))
-        .forEach(
-            c ->
-                assertTrue(
-                    c.uniqueMethodWithName("convert")
-                        .streamInstructions()
-                        .anyMatch(InstructionSubject::isInstanceOf)));
-  }
-
-  private void assertWrappers(CodeInspector inspector, int num) {
-    assertEquals(
-        num,
-        inspector.allClasses().stream()
-            .filter(
-                c ->
-                    c.getOriginalName()
-                        .startsWith(DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX))
-            .count());
-  }
-
-  static class Executor1 {
-
-    public static void main(String[] args) {
-      // Both wrappers are present.
-      CustomLibClass.printInt(() -> 3);
-      System.out.println(CustomLibClass.intSupplier().getAsInt());
-    }
-  }
-
-  static class Executor2 {
-
-    public static void main(String[] args) {
-      // One wrapper is present.
-      System.out.println(CustomLibClass.intSupplier().getAsInt());
-    }
-  }
-
-  @SuppressWarnings("WeakerAccess")
-  static class CustomLibClass {
-
-    public static IntSupplier intSupplier() {
-      return () -> 5;
-    }
-
-    public static void printInt(IntSupplier supplier) {
-      System.out.println(supplier.getAsInt());
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java
index f6a4285..6288598 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Paths;
@@ -35,6 +36,7 @@
         getTestParameters()
             .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
             .withAllApiLevels()
+            .withApiLevel(AndroidApiLevel.N)
             .build());
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
index 4b5e452..4fcfea3 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
@@ -44,7 +44,6 @@
     testForRuntime(parameters)
         .addProgramFiles(classesMatching("NestPvtMethodCallInlined"))
         .run(parameters.getRuntime(), getMainClass("pvtCallInlined"))
-        .disassemble()
         .assertSuccessWithOutput(getExpectedResult("pvtCallInlined"));
   }
 
@@ -74,25 +73,19 @@
     int nbDispatchInlining = 0;
     int nbNotInlinedPvtCall = 0;
     for (FoundClassSubject subj : inspector.allClasses()) {
-      // TODO (b/133745203): inline invokeinterface accessed through nest access control.
-      // Remove the if.
-      if (!subj.getDexClass().isInterface()) {
-        assertTrue(
-            "nestPvtCallToInline should be inlined (from " + subj.getOriginalName() + ")",
-            subj.allMethods().stream()
-                .noneMatch(
-                    method ->
-                        method.toString().contains("nestPvtCallToInline")
-                            || method.toString().contains("methodWithPvtCallToInline")));
-      }
+      assertTrue(
+          "nestPvtCallToInline should be inlined (from " + subj.getOriginalName() + ")",
+          subj.allMethods().stream()
+              .noneMatch(
+                  method ->
+                      method.toString().contains("nestPvtCallToInline")
+                          || method.toString().contains("methodWithPvtCallToInline")));
       // Inlining nest access should transform virtual/ift invokes -> direct.
       MethodSubject methodSubject = subj.uniqueMethodWithName("dispatchInlining");
       if (methodSubject.isPresent()) {
         nbDispatchInlining++;
         assertTrue(
             methodSubject.streamInstructions().noneMatch(InstructionSubject::isInvokeVirtual));
-        // TODO (b/133745203): inline invokeinterface accessed through nest access control.
-        // Also assert no invokeInterface
       }
       methodSubject = subj.uniqueMethodWithName("notInlinedPvtCall");
       if (methodSubject.isPresent()) {
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 399b6b0..1b56b1b 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
@@ -68,6 +68,7 @@
   void enableEnumOptions(InternalOptions options, boolean enumValueOptimization) {
     options.enableEnumUnboxing = true;
     options.enableEnumValueOptimization = enumValueOptimization;
+    options.enableEnumSwitchMapRemoval = enumValueOptimization;
     options.testing.enableEnumUnboxingDebugLogs = true;
   }
 
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
index 4bfa568..b7c7005 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
@@ -50,7 +50,13 @@
             .setMinApi(parameters.getApiLevel())
             .compile()
             .inspectDiagnosticMessages(
-                m -> assertEnumIsBoxed(ENUM_CLASS, classToTest.getSimpleName(), m))
+                m -> {
+                  if (enumValueOptimization) {
+                    assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m);
+                  } else {
+                    assertEnumIsBoxed(ENUM_CLASS, classToTest.getSimpleName(), m);
+                  }
+                })
             .run(parameters.getRuntime(), classToTest)
             .assertSuccess();
     assertLines2By2Correct(run.getStdOut());
diff --git a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
index 710e8da..775f030 100644
--- a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -15,6 +16,7 @@
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.Parser;
+import com.android.tools.r8.graph.GenericSignature.ReturnType;
 import com.android.tools.r8.graph.GenericSignature.TypeSignature;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.AndroidApp;
@@ -50,17 +52,25 @@
     assertThat(yy, isPresent());
     ClassSubject zz = inspector.clazz(A.Y.ZZ.class);
     assertThat(zz, isPresent());
+    ClassSubject b = inspector.clazz(B.class);
+    assertThat(b, isPresent());
     ClassSubject cy = inspector.clazz(CY.class);
     assertThat(cy, isPresent());
     ClassSubject cyy = inspector.clazz(CYY.class);
     assertThat(cyy, isPresent());
 
+    DexEncodedMethod method;
+
     ClassSignature classSignature;
     ClassTypeSignature classTypeSignature;
     FieldTypeSignature fieldTypeSignature;
     MethodTypeSignature methodTypeSignature;
     List<FieldTypeSignature> typeArguments;
     FieldTypeSignature typeArgument;
+    TypeSignature parameterSignature;
+    TypeSignature elementSignature;
+    ReturnType returnType;
+    TypeSignature returnTypeSignature;
 
     //
     // Testing ClassSignature
@@ -103,22 +113,35 @@
     // Testing MethodTypeSignature
     //
 
-    // A$Y$YY newYY()
+    // A$Y$YY newYY([B<T>)
     MethodSubject newYY = zz.uniqueMethodWithName("newYY");
     assertThat(newYY, isPresent());
-    DexEncodedMethod method = newYY.getMethod();
+    method = newYY.getMethod();
     assertNotNull(method);
 
     methodTypeSignature = Parser.toMethodTypeSignature(method, appView);
     assertNotNull(methodTypeSignature);
 
-    assertTrue(methodTypeSignature.typeSignatures.isEmpty());
-
     // return type: A$Y$YY
-    TypeSignature returnType = methodTypeSignature.returnType();
-    assertTrue(returnType.isFieldTypeSignature());
-    assertTrue(returnType.asFieldTypeSignature().isClassTypeSignature());
-    check_A_Y_YY(a, y, yy, returnType.asFieldTypeSignature().asClassTypeSignature());
+    returnType = methodTypeSignature.returnType();
+    assertFalse(returnType.isVoidDescriptor());
+    returnTypeSignature = returnType.typeSignature();
+    assertTrue(returnTypeSignature.isFieldTypeSignature());
+    assertTrue(returnTypeSignature.asFieldTypeSignature().isClassTypeSignature());
+    check_A_Y_YY(a, y, yy, returnTypeSignature.asFieldTypeSignature().asClassTypeSignature());
+
+    // type of 1st argument: [B<T>
+    assertEquals(1, methodTypeSignature.typeSignatures.size());
+    parameterSignature = methodTypeSignature.getParameterTypeSignature(0);
+    assertNotNull(parameterSignature);
+    assertTrue(parameterSignature.isFieldTypeSignature());
+    assertTrue(parameterSignature.asFieldTypeSignature().isArrayTypeSignature());
+    elementSignature =
+        parameterSignature.asFieldTypeSignature().asArrayTypeSignature().elementSignature;
+    assertTrue(elementSignature.isFieldTypeSignature());
+    assertTrue(elementSignature.asFieldTypeSignature().isClassTypeSignature());
+    classTypeSignature = elementSignature.asFieldTypeSignature().asClassTypeSignature();
+    assertEquals(b.getDexClass().type, classTypeSignature.type);
 
     // Function<A$Y$ZZ<TT>, A$Y$YY> convertToYY(Supplier<A$Y$ZZ<TT>>
     MethodSubject convertToYY = zz.uniqueMethodWithName("convertToYY");
@@ -131,9 +154,11 @@
 
     // return type: Function<A$Y$ZZ<TT>, A$Y$YY>
     returnType = methodTypeSignature.returnType();
-    assertTrue(returnType.isFieldTypeSignature());
-    assertTrue(returnType.asFieldTypeSignature().isClassTypeSignature());
-    classTypeSignature = returnType.asFieldTypeSignature().asClassTypeSignature();
+    assertFalse(returnType.isVoidDescriptor());
+    returnTypeSignature = returnType.typeSignature();
+    assertTrue(returnTypeSignature.isFieldTypeSignature());
+    assertTrue(returnTypeSignature.asFieldTypeSignature().isClassTypeSignature());
+    classTypeSignature = returnTypeSignature.asFieldTypeSignature().asClassTypeSignature();
     DexType functionType =
         factory.createType(DescriptorUtils.javaTypeToDescriptor(Function.class.getTypeName()));
     assertEquals(functionType, classTypeSignature.type);
@@ -151,19 +176,25 @@
 
     // type of 1st argument: Supplier<A$Y$ZZ<TT>>
     assertEquals(1, methodTypeSignature.typeSignatures.size());
-    TypeSignature parameterSignature = methodTypeSignature.getParameterTypeSignature(0);
-    assertNotNull(parameterSignature);
-    assertTrue(parameterSignature.isFieldTypeSignature());
-    assertTrue(parameterSignature.asFieldTypeSignature().isClassTypeSignature());
-    classTypeSignature = parameterSignature.asFieldTypeSignature().asClassTypeSignature();
-    DexType supplierType =
-        factory.createType(DescriptorUtils.javaTypeToDescriptor(Supplier.class.getTypeName()));
-    assertEquals(supplierType, classTypeSignature.type);
-    typeArguments = classTypeSignature.typeArguments;
-    assertEquals(1, typeArguments.size());
-    typeArgument = typeArguments.get(0);
-    assertTrue(typeArgument.isClassTypeSignature());
-    check_A_Y_ZZ(a, y, zz, typeArgument.asClassTypeSignature());
+    parameterSignature = methodTypeSignature.getParameterTypeSignature(0);
+    check_supplier(factory, a, y, zz, parameterSignature);
+
+    // void boo(Supplier<A$Y$ZZ<TT>>)
+    MethodSubject boo = zz.uniqueMethodWithName("boo");
+    assertThat(boo, isPresent());
+    method = boo.getMethod();
+    assertNotNull(method);
+
+    // return type: void
+    methodTypeSignature = Parser.toMethodTypeSignature(method, appView);
+    assertNotNull(methodTypeSignature);
+    returnType = methodTypeSignature.returnType();
+    assertTrue(returnType.isVoidDescriptor());
+
+    // type of 1st argument: Supplier<A$Y$ZZ<TT>>
+    assertEquals(1, methodTypeSignature.typeSignatures.size());
+    parameterSignature = methodTypeSignature.getParameterTypeSignature(0);
+    check_supplier(factory, a, y, zz, parameterSignature);
   }
 
   private void check_A_Y(ClassSubject a, ClassSubject y, ClassTypeSignature signature) {
@@ -193,6 +224,26 @@
     assertTrue(typeArgument.isTypeVariableSignature());
     assertEquals("TT", typeArgument.asTypeVariableSignature().typeVariable);
   }
+
+  private void check_supplier(
+      DexItemFactory factory,
+      ClassSubject a,
+      ClassSubject y,
+      ClassSubject zz,
+      TypeSignature signature) {
+    assertNotNull(signature);
+    assertTrue(signature.isFieldTypeSignature());
+    assertTrue(signature.asFieldTypeSignature().isClassTypeSignature());
+    ClassTypeSignature classTypeSignature = signature.asFieldTypeSignature().asClassTypeSignature();
+    DexType supplierType =
+        factory.createType(DescriptorUtils.javaTypeToDescriptor(Supplier.class.getTypeName()));
+    assertEquals(supplierType, classTypeSignature.type);
+    List<FieldTypeSignature> typeArguments = classTypeSignature.typeArguments;
+    assertEquals(1, typeArguments.size());
+    FieldTypeSignature typeArgument = typeArguments.get(0);
+    assertTrue(typeArgument.isClassTypeSignature());
+    check_A_Y_ZZ(a, y, zz, typeArgument.asClassTypeSignature());
+  }
 }
 
 //
@@ -209,7 +260,7 @@
     class ZZ<TT> extends YY {
       public YY yy;
 
-      YY newYY() {
+      YY newYY(B... bs) {
         return new YY();
       }
 
@@ -222,6 +273,10 @@
           }
         };
       }
+
+      void boo(Supplier<ZZ<TT>> zzSupplier) {
+        convertToYY(zzSupplier).apply(this);
+      }
     }
 
     ZZ<T> zz() {
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 7d04499..183ee3e 100644
--- a/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
@@ -45,13 +45,14 @@
             .addKeepRuleFiles(
                 Paths.get(base).resolve(BASE_PG_CONF),
                 Paths.get(ToolHelper.PROGUARD_SETTINGS_FOR_INTERNAL_APPS, PG_CONF))
-            .allowDiagnosticInfoMessages()
+            .allowDiagnosticMessages()
             .allowUnusedProguardConfigurationRules()
             .compile()
             .assertAllInfoMessagesMatch(
                 anyOf(
                     equalTo("Ignoring option: -optimizations"),
-                    containsString("Proguard configuration rule does not match anything")));
+                    containsString("Proguard configuration rule does not match anything")))
+            .assertAllWarningMessagesMatch(containsString("Ignoring option:"));
 
     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 88251be..741be70 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -4,7 +4,10 @@
 package com.android.tools.r8.internal;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestBase;
@@ -18,6 +21,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.LookupResult;
+import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
@@ -25,7 +29,6 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import org.junit.Before;
@@ -70,28 +73,49 @@
     ResolutionResult resolutionResult = appInfo().resolveMethodOnClass(clazz, method.method);
     LookupResult lookupResult =
         resolutionResult.lookupVirtualDispatchTargets(
-            clazz, appView, appInfo(), dexReference -> false);
+            clazz, appInfo(), appInfo(), dexReference -> false);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<DexEncodedMethod> targets = lookupResult.asLookupResultSuccess().getMethodTargets();
-    assertTrue(targets.contains(method));
+    assertTrue(lookupResult.asLookupResultSuccess().contains(method));
+  }
+
+  private static class Counter {
+    int count = 0;
+
+    void inc() {
+      count++;
+    }
   }
 
   private void testInterfaceLookup(DexProgramClass clazz, DexEncodedMethod method) {
-    LookupResult lookupResult =
+    LookupResultSuccess lookupResult =
         appInfo()
             .resolveMethodOnInterface(clazz, method.method)
-            .lookupVirtualDispatchTargets(clazz, appView, appInfo(), dexReference -> false);
-    assertTrue(lookupResult.isLookupResultSuccess());
-    Set<DexEncodedMethod> targets = lookupResult.asLookupResultSuccess().getMethodTargets();
+            .lookupVirtualDispatchTargets(clazz, appInfo(), appInfo(), dexReference -> false)
+            .asLookupResultSuccess();
+    assertNotNull(lookupResult);
+    assertFalse(lookupResult.hasLambdaTargets());
     if (appInfo().subtypes(method.method.holder).stream()
         .allMatch(t -> appInfo().definitionFor(t).isInterface())) {
-      assertEquals(
-          0,
-          targets.stream()
-              .filter(m -> m.accessFlags.isAbstract() || !m.accessFlags.isBridge())
-              .count());
+      Counter counter = new Counter();
+      lookupResult.forEach(
+          target -> {
+            DexEncodedMethod m = target.getMethod();
+            if (m.accessFlags.isAbstract() || !m.accessFlags.isBridge()) {
+              counter.inc();
+            }
+          },
+          l -> fail());
+      assertEquals(0, counter.count);
     } else {
-      assertEquals(0, targets.stream().filter(m -> m.accessFlags.isAbstract()).count());
+      Counter counter = new Counter();
+      lookupResult.forEach(
+          target -> {
+            if (target.getMethod().isAbstract()) {
+              counter.inc();
+            }
+          },
+          lambda -> fail());
+      assertEquals(0, counter.count);
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java b/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
index 57f4359..a6dd6b0 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
@@ -21,6 +21,7 @@
   static final String PG_JAR = "YouTubeRelease_proguard.jar";
   static final String PG_MAP = "YouTubeRelease_proguard.map";
   static final String PG_CONF = "YouTubeRelease_proguard.config";
+  static final String PG_PROTO_CONF = "YouTubeRelease_proto_safety.pgconf";
 
   final String base;
 
@@ -34,9 +35,14 @@
   }
 
   protected List<Path> getKeepRuleFiles() {
-    return ImmutableList.of(
-        Paths.get(base).resolve(PG_CONF),
-        Paths.get(ToolHelper.PROGUARD_SETTINGS_FOR_INTERNAL_APPS).resolve(PG_CONF));
+    ImmutableList.Builder<Path> builder = ImmutableList.builder();
+    builder.add(Paths.get(base).resolve(PG_CONF));
+    builder.add(Paths.get(ToolHelper.PROGUARD_SETTINGS_FOR_INTERNAL_APPS).resolve(PG_CONF));
+    Path config = Paths.get(base).resolve(PG_PROTO_CONF);
+    if (config.toFile().exists()) {
+      builder.add(config);
+    }
+    return builder.build();
   }
 
   protected List<Path> getLibraryFiles() {
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java
index f20a827..48acfdb 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java
@@ -46,23 +46,6 @@
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
             .addKeepRuleFiles(getKeepRuleFiles())
-            .addOptionsModification(
-                options -> {
-                  assert !options.enableFieldBitAccessAnalysis;
-                  options.enableFieldBitAccessAnalysis = true;
-
-                  assert !options.protoShrinking().enableGeneratedExtensionRegistryShrinking;
-                  options.protoShrinking().enableGeneratedExtensionRegistryShrinking = true;
-
-                  assert !options.protoShrinking().enableGeneratedMessageLiteShrinking;
-                  options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
-
-                  assert options.protoShrinking().traverseOneOfAndRepeatedProtoFields;
-                  options.protoShrinking().traverseOneOfAndRepeatedProtoFields = false;
-
-                  assert !options.enableStringSwitchConversion;
-                  options.enableStringSwitchConversion = true;
-                })
             .allowUnusedProguardConfigurationRules()
             .compile();
 
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java
index ab63ba7..b2498b5 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java
@@ -59,26 +59,6 @@
             .addLibraryFiles(librarySanitizer.getSanitizedLibrary())
             .addKeepRuleFiles(getKeepRuleFiles())
             .addMainDexRuleFiles(getMainDexRuleFiles())
-            .addOptionsModification(
-                options -> {
-                  assert !options.applyInliningToInlinee;
-                  options.applyInliningToInlinee = true;
-
-                  assert !options.enableFieldBitAccessAnalysis;
-                  options.enableFieldBitAccessAnalysis = true;
-
-                  assert !options.protoShrinking().enableGeneratedExtensionRegistryShrinking;
-                  options.protoShrinking().enableGeneratedExtensionRegistryShrinking = true;
-
-                  assert !options.protoShrinking().enableGeneratedMessageLiteShrinking;
-                  options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
-
-                  assert options.protoShrinking().traverseOneOfAndRepeatedProtoFields;
-                  options.protoShrinking().traverseOneOfAndRepeatedProtoFields = false;
-
-                  assert !options.enableStringSwitchConversion;
-                  options.enableStringSwitchConversion = true;
-                })
             .setMinApi(AndroidApiLevel.H_MR2)
             .allowDiagnosticMessages()
             .allowUnusedProguardConfigurationRules()
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java
index 01120c8..e63b126 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java
@@ -59,26 +59,6 @@
             .addLibraryFiles(librarySanitizer.getSanitizedLibrary())
             .addKeepRuleFiles(getKeepRuleFiles())
             .addMainDexRuleFiles(getMainDexRuleFiles())
-            .addOptionsModification(
-                options -> {
-                  assert !options.applyInliningToInlinee;
-                  options.applyInliningToInlinee = true;
-
-                  assert !options.enableFieldBitAccessAnalysis;
-                  options.enableFieldBitAccessAnalysis = true;
-
-                  assert !options.protoShrinking().enableGeneratedExtensionRegistryShrinking;
-                  options.protoShrinking().enableGeneratedExtensionRegistryShrinking = true;
-
-                  assert !options.protoShrinking().enableGeneratedMessageLiteShrinking;
-                  options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
-
-                  assert options.protoShrinking().traverseOneOfAndRepeatedProtoFields;
-                  options.protoShrinking().traverseOneOfAndRepeatedProtoFields = false;
-
-                  assert !options.enableStringSwitchConversion;
-                  options.enableStringSwitchConversion = true;
-                })
             .allowCheckDiscardedErrors()
             .allowDiagnosticMessages()
             .allowUnusedProguardConfigurationRules()
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
index 0788985..23dc574 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
@@ -72,30 +72,11 @@
             .addProgramFiles(PROGRAM_FILES)
             .addKeepMainRules(mains)
             .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
-            .addOptionsModification(
-                options -> {
-                  assert !options.applyInliningToInlinee;
-                  options.applyInliningToInlinee = true;
-
-                  assert !options.enableFieldBitAccessAnalysis;
-                  options.enableFieldBitAccessAnalysis = true;
-
-                  assert !options.protoShrinking().enableGeneratedExtensionRegistryShrinking;
-                  options.protoShrinking().enableGeneratedExtensionRegistryShrinking = true;
-
-                  assert !options.protoShrinking().enableGeneratedMessageLiteShrinking;
-                  options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
-
-                  assert !options.protoShrinking().enableGeneratedMessageLiteBuilderShrinking;
-                  options.protoShrinking().enableGeneratedMessageLiteBuilderShrinking = true;
-
-                  assert !options.enableStringSwitchConversion;
-                  options.enableStringSwitchConversion = true;
-                })
             .allowAccessModification()
             .allowDiagnosticMessages()
             .allowUnusedProguardConfigurationRules()
             .enableInliningAnnotations()
+            .enableProtoShrinking()
             .setMinApi(parameters.getApiLevel())
             .compile()
             .assertAllInfoMessagesMatch(
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index 1473e03..cd743a9 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -82,17 +82,10 @@
             .addKeepMainRule("proto2.TestClass")
             .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
             .addKeepRules(allGeneratedMessageLiteSubtypesAreInstantiatedRule())
-            .addOptionsModification(
-                options -> {
-                  options.enableFieldBitAccessAnalysis = true;
-                  options.protoShrinking().enableGeneratedExtensionRegistryShrinking = true;
-                  options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
-                  options.protoShrinking().enableGeneratedMessageLiteBuilderShrinking = true;
-                  options.enableStringSwitchConversion = true;
-                })
             .allowAccessModification(allowAccessModification)
             .allowDiagnosticMessages()
             .allowUnusedProguardConfigurationRules()
+            .enableProtoShrinking()
             .minification(enableMinification)
             .setMinApi(parameters.getApiLevel())
             .compile()
@@ -357,15 +350,10 @@
         .addKeepRules(keepAllProtosRule())
         // Retain the signature of dynamicMethod() and newMessageInfo().
         .addKeepRules(keepDynamicMethodSignatureRule(), keepNewMessageInfoSignatureRule())
-        // Enable the dynamicMethod() rewritings.
-        .addOptionsModification(
-            options -> {
-              assert !options.protoShrinking().enableGeneratedMessageLiteShrinking;
-              options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
-            })
         .allowAccessModification(allowAccessModification)
         .allowDiagnosticMessages()
         .allowUnusedProguardConfigurationRules()
+        .enableProtoShrinking()
         .minification(enableMinification)
         .setMinApi(parameters.getApiLevel())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
index 7a261ff..429c340 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
@@ -58,17 +58,10 @@
         .addProgramFiles(PROGRAM_FILES)
         .addKeepMainRule("proto3.TestClass")
         .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
-        .addOptionsModification(
-            options -> {
-              options.enableFieldBitAccessAnalysis = true;
-              options.protoShrinking().enableGeneratedExtensionRegistryShrinking = true;
-              options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
-              options.protoShrinking().enableGeneratedMessageLiteBuilderShrinking = true;
-              options.enableStringSwitchConversion = true;
-            })
         .allowAccessModification(allowAccessModification)
         .allowDiagnosticMessages()
         .allowUnusedProguardConfigurationRules()
+        .enableProtoShrinking()
         .minification(enableMinification)
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -113,15 +106,10 @@
         .addKeepRules(keepAllProtosRule())
         // Retain the signature of dynamicMethod() and newMessageInfo().
         .addKeepRules(keepDynamicMethodSignatureRule(), keepNewMessageInfoSignatureRule())
-        // Enable the dynamicMethod() rewritings.
-        .addOptionsModification(
-            options -> {
-              assert !options.protoShrinking().enableGeneratedMessageLiteShrinking;
-              options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
-            })
         .allowAccessModification(allowAccessModification)
         .allowDiagnosticMessages()
         .allowUnusedProguardConfigurationRules()
+        .enableProtoShrinking()
         .minification(enableMinification)
         .setMinApi(parameters.getApiLevel())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/internal/proto/YouTubeProtoRewritingTest.java b/src/test/java/com/android/tools/r8/internal/proto/YouTubeProtoRewritingTest.java
index cff1e45..6088600 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/YouTubeProtoRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/YouTubeProtoRewritingTest.java
@@ -46,12 +46,6 @@
         .addKeepRules(keepAllProtosRule())
         // Retain the signature of dynamicMethod() and newMessageInfo().
         .addKeepRules(keepDynamicMethodSignatureRule(), keepNewMessageInfoSignatureRule())
-        // Enable the dynamicMethod() rewritings.
-        .addOptionsModification(
-            options -> {
-              assert !options.protoShrinking().enableGeneratedMessageLiteShrinking;
-              options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
-            })
         .allowUnusedProguardConfigurationRules()
         .compile()
         .inspect(
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/PutObjectWithFinalizeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/PutObjectWithFinalizeTest.java
index d133adf..c4d321a 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/PutObjectWithFinalizeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/PutObjectWithFinalizeTest.java
@@ -32,7 +32,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public PutObjectWithFinalizeTest(TestParameters parameters) {
@@ -48,7 +48,7 @@
         .addOptionsModification(options -> options.enableClassStaticizer = false)
         .enableInliningAnnotations()
         .enableMergeAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
             inspector -> {
@@ -70,7 +70,7 @@
                       "otherArrayInstanceWithoutFinalizer");
               for (String name : presentFields) {
                 FieldSubject fieldSubject = classSubject.uniqueFieldWithName(name);
-                assertThat(fieldSubject, isPresent());
+                assertThat(name, fieldSubject, isPresent());
                 assertTrue(
                     mainSubject
                         .streamInstructions()
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java
index 7813a2d..9461c6b 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java
@@ -8,6 +8,7 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -37,6 +38,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(SingletonClassInitializerPatternCanBePostponedTest.class)
         .addKeepMainRule(TestClass.class)
+        .enableMemberValuePropagationAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
@@ -65,7 +67,7 @@
 
     static A INSTANCE = new A(" world!");
 
-    final String message;
+    @NeverPropagateValue final String message;
 
     A(String message) {
       this.message = message;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
index a963edf..a1c69fa 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
@@ -191,7 +191,8 @@
 
       unknownArg(instance);
       try {
-        unknownArg(null);
+        Foo alwaysNull = System.currentTimeMillis() > 0 ? null : instance;
+        unknownArg(alwaysNull);
         throw new AssertionError("Expected NullPointerException");
       } catch (NullPointerException npe) {
         System.out.println("Expected NPE");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastInterfaceArrayTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastInterfaceArrayTest.java
index 819c573..a8ef7a1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastInterfaceArrayTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastInterfaceArrayTest.java
@@ -40,7 +40,6 @@
           .addInnerClasses(CheckCastInterfaceArrayTest.class)
           .setMinApi(parameters.getApiLevel())
           .run(parameters.getRuntime(), TestClass.class)
-          .disassemble()
           .assertSuccessWithOutput(EXPECTED);
     }
   }
@@ -55,7 +54,6 @@
         .noMinification()
         .noTreeShaking()
         .compile()
-        .disassemble()
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED);
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ifs/IfThrowNullPointerExceptionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ifs/IfThrowNullPointerExceptionTest.java
new file mode 100644
index 0000000..2264a29
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ifs/IfThrowNullPointerExceptionTest.java
@@ -0,0 +1,129 @@
+// 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.ifs;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IfThrowNullPointerExceptionTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public IfThrowNullPointerExceptionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addInnerClasses(IfThrowNullPointerExceptionTest.class)
+        .release()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Caught NPE", "Caught NPE");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(IfThrowNullPointerExceptionTest.class)
+        .addKeepClassAndMembersRules(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Caught NPE", "Caught NPE");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(TestClass.class);
+    assertThat(classSubject, isPresent());
+
+    for (String methodName : ImmutableList.of("testThrowNPE", "testThrowNull")) {
+      MethodSubject methodSubject = classSubject.uniqueMethodWithName(methodName);
+      assertThat(methodSubject, isPresent());
+
+      IRCode code = methodSubject.buildIR();
+      assertEquals(1, code.blocks.size());
+
+      BasicBlock entryBlock = code.entryBlock();
+      assertEquals(3, entryBlock.getInstructions().size());
+      assertTrue(entryBlock.getInstructions().getFirst().isArgument());
+      assertTrue(entryBlock.getInstructions().getLast().isReturn());
+
+      Instruction nullCheckInstruction = entryBlock.getInstructions().get(1);
+      if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.K)) {
+        assertTrue(nullCheckInstruction.isInvokeVirtual());
+        assertEquals(
+            "java.lang.Class java.lang.Object.getClass()",
+            nullCheckInstruction.asInvokeVirtual().getInvokedMethod().toSourceString());
+      } else {
+        assertTrue(nullCheckInstruction.isInvokeStatic());
+        assertEquals(
+            "java.lang.Object java.util.Objects.requireNonNull(java.lang.Object)",
+            nullCheckInstruction.asInvokeStatic().getInvokedMethod().toSourceString());
+      }
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      testThrowNPE(new Object());
+      testThrowNull(new Object());
+
+      try {
+        testThrowNPE(null);
+      } catch (NullPointerException e) {
+        System.out.println("Caught NPE");
+      }
+      try {
+        testThrowNull(null);
+      } catch (NullPointerException e) {
+        System.out.println("Caught NPE");
+      }
+    }
+
+    static void testThrowNPE(Object x) {
+      if (x == null) {
+        throw new NullPointerException();
+      }
+    }
+
+    static void testThrowNull(Object x) {
+      if (x == null) {
+        throw null;
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FinalFieldWithDefaultValueAssignmentPropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FinalFieldWithDefaultValueAssignmentPropagationTest.java
index 8710a6a..97dd34c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FinalFieldWithDefaultValueAssignmentPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FinalFieldWithDefaultValueAssignmentPropagationTest.java
@@ -7,7 +7,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
@@ -59,8 +59,7 @@
 
     MethodSubject configConstructorSubject = configClassSubject.init();
     assertThat(configConstructorSubject, isPresent());
-    // TODO(b/147799637): Should be true.
-    assertFalse(
+    assertTrue(
         configConstructorSubject.streamInstructions().noneMatch(InstructionSubject::isInstancePut));
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
index 2c8f4b3..0745620 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverClassInline;
@@ -88,9 +87,7 @@
 
     ClassSubject aClassSubject = inspector.clazz(A.class);
     assertThat(aClassSubject, isPresent());
-    // TODO(b/125282093): Need to remove the instance-put instructions in A.<init>(). This can not
-    //  be done safely by the time we process A.<init>(), so some kind of post-processing is needed.
-    assertEquals(3, aClassSubject.allInstanceFields().size());
+    assertTrue(aClassSubject.allInstanceFields().isEmpty());
   }
 
   static class TestClass {
@@ -125,7 +122,7 @@
     String s = "Hello world!";
   }
 
-  enum MyEnum {
+  public enum MyEnum {
     A,
     B
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java
index 9c32e92..adc1f0b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java
@@ -56,7 +56,10 @@
     assertThat(aClassSubject, isPresent());
 
     FieldSubject fieldSubject = aClassSubject.uniqueFieldWithName("field");
-    assertThat(fieldSubject, isPresent());
+    assertThat(fieldSubject, not(isPresent()));
+
+    FieldSubject clinitFieldSubject = aClassSubject.uniqueFieldWithName("$r8$clinit");
+    assertThat(clinitFieldSubject, isPresent());
 
     // B.method() is present.
     ClassSubject bClassSubject = inspector.clazz(B.class);
@@ -81,7 +84,7 @@
         mainMethodSubject
             .streamInstructions()
             .filter(InstructionSubject::isStaticGet)
-            .anyMatch(x -> x.getField() == fieldSubject.getField().field));
+            .anyMatch(x -> x.getField() == clinitFieldSubject.getField().field));
     assertTrue(
         mainMethodSubject
             .streamInstructions()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithDefaultValueAssignmentPropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithDefaultValueAssignmentPropagationTest.java
index f68a131..433ee13 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithDefaultValueAssignmentPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithDefaultValueAssignmentPropagationTest.java
@@ -7,7 +7,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
@@ -61,8 +61,7 @@
 
     MethodSubject configConstructorSubject = configClassSubject.init();
     assertThat(configConstructorSubject, isPresent());
-    // TODO(b/147799637): Should be true.
-    assertFalse(
+    assertTrue(
         configConstructorSubject.streamInstructions().noneMatch(InstructionSubject::isInstancePut));
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/LambdaInstantiatedTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/LambdaInstantiatedTypeTest.java
index 575580d..1b3e93e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/LambdaInstantiatedTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/LambdaInstantiatedTypeTest.java
@@ -8,7 +8,9 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersBuilder;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -24,29 +26,36 @@
 @RunWith(Parameterized.class)
 public class LambdaInstantiatedTypeTest extends TestBase {
 
-  private final Backend backend;
+  private final TestParameters parameters;
 
-  @Parameters(name = "Backend: {0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return TestParametersBuilder.builder().withAllRuntimesAndApiLevels().build();
   }
 
-  public LambdaInstantiatedTypeTest(Backend backend) {
-    this.backend = backend;
+  public LambdaInstantiatedTypeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  String expected = StringUtils.joinLines("In TestClass.live()");
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(LambdaInstantiatedTypeTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(expected);
   }
 
   @Test
   public void test() throws Exception {
-    String expected = StringUtils.joinLines("In TestClass.live()");
-
-    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expected);
-
     CodeInspector inspector =
-        testForR8(backend)
+        testForR8(parameters.getBackend())
             .addInnerClasses(LambdaInstantiatedTypeTest.class)
             .addKeepMainRule(TestClass.class)
             .enableInliningAnnotations()
-            .run(TestClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expected)
             .inspector();
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
index 483b3aa..e29373a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import org.junit.Test;
@@ -38,9 +39,12 @@
   private void test(
       Collection<String> rules, ThrowableConsumer<R8FullTestBuilder> consumer) throws Exception {
     testForR8(parameters.getBackend())
-        .addLibraryFiles(ToolHelper.getDefaultAndroidJar(), ToolHelper.getKotlinStdlibJar())
+        .addLibraryFiles(ToolHelper.getMostRecentAndroidJar(), ToolHelper.getKotlinStdlibJar())
         .addProgramFiles(ToolHelper.getKotlinReflectJar())
         .addKeepRules(rules)
+        .addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
+        .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
+        .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
         .apply(consumer)
         .compile();
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
index 956c37b..4c45d3c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import org.junit.Test;
@@ -39,6 +40,9 @@
     testForR8(parameters.getBackend())
         .addProgramFiles(ToolHelper.getKotlinStdlibJar())
         .addKeepRules(rules)
+        .addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
+        .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
+        .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
         .apply(consumer)
         .compile();
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
index 562fdbb..1720b55 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
@@ -15,4 +15,14 @@
 
   static final String PKG = KotlinMetadataTestBase.class.getPackage().getName();
   static final String PKG_PREFIX = DescriptorUtils.getBinaryNameFromJavaType(PKG);
+
+  static final String KT_ARRAY = "Lkotlin/Array;";
+  static final String KT_CHAR_SEQUENCE = "Lkotlin/CharSequence;";
+  static final String KT_STRING = "Lkotlin/String;";
+  static final String KT_LONG = "Lkotlin/Long;";
+  static final String KT_LONG_ARRAY = "Lkotlin/LongArray;";
+  static final String KT_MAP = "Lkotlin/collections/Map;";
+  static final String KT_UNIT = "Lkotlin/Unit;";
+
+  static final String KT_FUNCTION1 = "Lkotlin/Function1;";
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
index d03fcd1..7b9380f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
@@ -32,6 +33,7 @@
 
 @RunWith(Parameterized.class)
 public class MetadataRewriteInClasspathTypeTest extends KotlinMetadataTestBase {
+  private static final String EXPECTED = StringUtils.lines("Impl::foo");
 
   private final TestParameters parameters;
 
@@ -97,7 +99,7 @@
         .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), baseLibJar, libJar)
         .addClasspath(output)
         .run(parameters.getRuntime(), PKG + ".classpath_app.MainKt")
-        .assertSuccessWithOutputLines("Impl::foo");
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspectMerged(CodeInspector inspector) {
@@ -163,7 +165,7 @@
         .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), baseLibJar, libJar)
         .addClasspath(output)
         .run(parameters.getRuntime(), PKG + ".classpath_app.MainKt")
-        .assertSuccessWithOutputLines("Impl::foo");
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspectRenamed(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
index 1f8fee7..85a1869 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
@@ -7,7 +7,6 @@
 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;
@@ -17,12 +16,15 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
 import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeProjectionSubject;
 import com.android.tools.r8.utils.codeinspector.KmTypeSubject;
+import com.android.tools.r8.utils.codeinspector.KmValueParameterSubject;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.HashMap;
@@ -35,6 +37,7 @@
 
 @RunWith(Parameterized.class)
 public class MetadataRewriteInExtensionFunctionTest extends KotlinMetadataTestBase {
+  private static final String EXPECTED = StringUtils.lines("do stuff", "do stuff", "do stuff");
 
   private final TestParameters parameters;
 
@@ -76,6 +79,9 @@
             // to be called with Kotlin syntax from other kotlin code.
             .addKeepRules("-keep class **.BKt { <methods>; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
+            .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
+            .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
             .compile()
             .inspect(this::inspectMerged)
             .writeToZip();
@@ -91,7 +97,7 @@
         .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
         .addClasspath(output)
         .run(parameters.getRuntime(), PKG + ".extension_function_app.MainKt")
-        .assertSuccessWithOutputLines("do stuff", "do stuff");
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspectMerged(CodeInspector inspector) {
@@ -127,6 +133,9 @@
             // to be called with Kotlin syntax from other kotlin code.
             .addKeepRules("-keep class **.BKt { <methods>; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
+            .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
+            .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
             .compile()
             .inspect(this::inspectRenamed)
             .writeToZip();
@@ -142,7 +151,7 @@
         .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
         .addClasspath(output)
         .run(parameters.getRuntime(), PKG + ".extension_function_app.MainKt")
-        .assertSuccessWithOutputLines("do stuff", "do stuff");
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspectRenamed(CodeInspector inspector) {
@@ -190,23 +199,34 @@
     kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("csHash");
     assertThat(kmFunction, isExtensionFunction());
     kmTypeSubject = kmFunction.receiverParameterType();
-    assertEquals("Lkotlin/CharSequence;", kmTypeSubject.descriptor());
+    assertEquals(KT_CHAR_SEQUENCE, kmTypeSubject.descriptor());
     kmTypeSubject = kmFunction.returnType();
-    assertEquals("Lkotlin/Long;", kmTypeSubject.descriptor());
+    assertEquals(KT_LONG, kmTypeSubject.descriptor());
 
     kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("longArrayHash");
     assertThat(kmFunction, isExtensionFunction());
     kmTypeSubject = kmFunction.receiverParameterType();
-    assertEquals("Lkotlin/LongArray;", kmTypeSubject.descriptor());
+    assertEquals(KT_LONG_ARRAY, kmTypeSubject.descriptor());
     kmTypeSubject = kmFunction.returnType();
-    assertEquals("Lkotlin/Long;", kmTypeSubject.descriptor());
+    assertEquals(KT_LONG, kmTypeSubject.descriptor());
 
+    // fun B.myApply(apply: B.() -> Unit): Unit
+    // https://github.com/JetBrains/kotlin/blob/master/spec-docs/function-types.md#extension-functions
     kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("myApply");
     assertThat(kmFunction, isExtensionFunction());
     kmTypeSubject = kmFunction.receiverParameterType();
     assertEquals(impl.getFinalDescriptor(), kmTypeSubject.descriptor());
-    // TODO(b/70169921): Check param[0] has type kotlin/Function1<(renamed) B, Unit>
-    String desc = kmFunction.signature().getDesc();
-    assertThat(desc, containsString("kotlin/jvm/functions/Function1"));
+
+    List<KmValueParameterSubject> valueParameters = kmFunction.valueParameters();
+    assertEquals(1, valueParameters.size());
+
+    KmValueParameterSubject valueParameter = valueParameters.get(0);
+    assertEquals(KT_FUNCTION1, valueParameter.type().descriptor());
+    List<KmTypeProjectionSubject> typeArguments = valueParameter.type().typeArguments();
+    assertEquals(2, typeArguments.size());
+    KmTypeSubject typeArgument = typeArguments.get(0).type();
+    assertEquals(impl.getFinalDescriptor(), typeArgument.descriptor());
+    typeArgument = typeArguments.get(1).type();
+    assertEquals(KT_UNIT, typeArgument.descriptor());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
index 5fb5150..06fcd3a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
@@ -35,6 +36,7 @@
 
 @RunWith(Parameterized.class)
 public class MetadataRewriteInExtensionPropertyTest extends KotlinMetadataTestBase {
+  private static final String EXPECTED = StringUtils.lines("do stuff", "do stuff");
 
   private final TestParameters parameters;
 
@@ -91,7 +93,7 @@
         .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
         .addClasspath(output)
         .run(parameters.getRuntime(), PKG + ".extension_property_app.MainKt")
-        .assertSuccessWithOutputLines("do stuff", "do stuff");
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspectMerged(CodeInspector inspector) {
@@ -155,7 +157,7 @@
         .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
         .addClasspath(output)
         .run(parameters.getRuntime(), PKG + ".extension_property_app.MainKt")
-        .assertSuccessWithOutputLines("do stuff", "do stuff");
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspectRenamed(CodeInspector inspector) {
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 eb98862..e72c314 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
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
@@ -34,6 +35,7 @@
 
 @RunWith(Parameterized.class)
 public class MetadataRewriteInFunctionTest extends KotlinMetadataTestBase {
+  private static final String EXPECTED = StringUtils.lines("do stuff", "do stuff");
 
   private final TestParameters parameters;
 
@@ -89,7 +91,7 @@
         .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
         .addClasspath(output)
         .run(parameters.getRuntime(), PKG + ".function_app.MainKt")
-        .assertSuccessWithOutputLines("do stuff", "do stuff");
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspectMerged(CodeInspector inspector) {
@@ -151,7 +153,7 @@
         .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
         .addClasspath(output)
         .run(parameters.getRuntime(), PKG + ".function_app.MainKt")
-        .assertSuccessWithOutputLines("do stuff", "do stuff");
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspectRenamed(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java
index 54675ca..0e41755 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java
@@ -7,22 +7,23 @@
 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.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
 import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeProjectionSubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeSubject;
 import com.android.tools.r8.utils.codeinspector.KmValueParameterSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.nio.file.Path;
@@ -37,6 +38,7 @@
 
 @RunWith(Parameterized.class)
 public class MetadataRewriteInFunctionWithDefaultValueTest extends KotlinMetadataTestBase {
+  private static final String EXPECTED = StringUtils.lines("a", "b", "c");
 
   private final TestParameters parameters;
 
@@ -75,27 +77,25 @@
             // Keep LibKt and applyMap function, along with applyMap$default
             .addKeepRules("-keep class **.LibKt { *** applyMap*(...); }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
+            .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
+            .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
             .compile()
             .inspect(this::inspect)
             .writeToZip();
 
-    ProcessResult kotlinTestCompileResult =
+    Path output =
         kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
             .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/default_value_app", "main"))
             .setOutputPath(temp.newFolder().toPath())
-            // TODO(b/70169921): update to just .compile() once fixed.
-            .compileRaw();
+            .compile();
 
-    // TODO(b/70169921): should be able to compile!
-    assertNotEquals(0, kotlinTestCompileResult.exitCode);
-    assertThat(
-        kotlinTestCompileResult.stderr,
-        containsString("type mismatch: inferred type is kotlin.collections.Map<String, String"));
-    assertThat(
-        kotlinTestCompileResult.stderr, containsString("but java.util.Map<K, V> was expected"));
-    assertThat(
-        kotlinTestCompileResult.stderr, not(containsString("no value passed for parameter 'p2'")));
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".default_value_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspect(CodeInspector inspector) {
@@ -116,13 +116,24 @@
     KmPackageSubject kmPackage = libKt.getKmPackage();
     assertThat(kmPackage, isPresent());
 
+    // String applyMap(Map<String, String>, (default) String)
     KmFunctionSubject kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("applyMap");
     assertThat(kmFunction, isExtensionFunction());
     List<KmValueParameterSubject> valueParameters = kmFunction.valueParameters();
     assertEquals(2, valueParameters.size());
-    // TODO(b/70169921): inspect 1st arg is Map with correct type parameter.
-    KmValueParameterSubject valueParameter = valueParameters.get(1);
+
+    KmValueParameterSubject valueParameter = valueParameters.get(0);
+    assertFalse(valueParameter.declaresDefaultValue());
+    assertEquals(KT_MAP, valueParameter.type().descriptor());
+    List<KmTypeProjectionSubject> typeArguments = valueParameter.type().typeArguments();
+    assertEquals(2, typeArguments.size());
+    KmTypeSubject typeArgument = typeArguments.get(0).type();
+    assertEquals(KT_STRING, typeArgument.descriptor());
+    typeArgument = typeArguments.get(1).type();
+    assertEquals(KT_STRING, typeArgument.descriptor());
+
+    valueParameter = valueParameters.get(1);
     assertTrue(valueParameter.declaresDefaultValue());
-    assertEquals("Lkotlin/String;", valueParameter.type().descriptor());
+    assertEquals(KT_STRING, valueParameter.type().descriptor());
   }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
index 1bf7552..68714f6 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
@@ -7,21 +7,23 @@
 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.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
 import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeProjectionSubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeSubject;
 import com.android.tools.r8.utils.codeinspector.KmValueParameterSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.nio.file.Path;
@@ -36,6 +38,7 @@
 
 @RunWith(Parameterized.class)
 public class MetadataRewriteInFunctionWithVarargTest extends KotlinMetadataTestBase {
+  private static final String EXPECTED = StringUtils.lines("SomeClass::R8");
 
   private final TestParameters parameters;
 
@@ -75,24 +78,25 @@
             // Keep LibKt, along with bar function.
             .addKeepRules("-keep class **.LibKt { *** bar(...); }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
+            .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
+            .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
             .compile()
             .inspect(this::inspect)
             .writeToZip();
 
-    ProcessResult kotlinTestCompileResult =
+    Path output =
         kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
             .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/vararg_app", "main"))
             .setOutputPath(temp.newFolder().toPath())
-            // TODO(b/70169921): update to just .compile() once fixed.
-            .compileRaw();
+            .compile();
 
-    // TODO(b/70169921): should be able to compile!
-    assertNotEquals(0, kotlinTestCompileResult.exitCode);
-    assertThat(
-        kotlinTestCompileResult.stderr,
-        not(containsString("type mismatch: inferred type is String but Array<T> was expected")));
-    assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: foo"));
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".vararg_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspect(CodeInspector inspector) {
@@ -118,13 +122,32 @@
     KmPackageSubject kmPackage = libKt.getKmPackage();
     assertThat(kmPackage, isPresent());
 
+    // Unit bar(vararg String, (SomeClass, String) -> Unit)
     KmFunctionSubject kmFunction = kmPackage.kmFunctionWithUniqueName("bar");
     assertThat(kmFunction, not(isExtensionFunction()));
     List<KmValueParameterSubject> valueParameters = kmFunction.valueParameters();
     assertEquals(2, valueParameters.size());
+
     KmValueParameterSubject valueParameter = valueParameters.get(0);
     assertTrue(valueParameter.isVararg());
-    assertEquals("Lkotlin/String;", valueParameter.varargElementType().descriptor());
-    // TODO(b/70169921): inspect 2nd arg is lambda with correct type parameter.
+    assertEquals(KT_STRING, valueParameter.varargElementType().descriptor());
+
+    assertEquals(KT_ARRAY, valueParameter.type().descriptor());
+    List<KmTypeProjectionSubject> typeArguments = valueParameter.type().typeArguments();
+    assertEquals(1, typeArguments.size());
+    KmTypeSubject typeArgument = typeArguments.get(0).type();
+    assertEquals(KT_STRING, typeArgument.descriptor());
+
+    valueParameter = valueParameters.get(1);
+    assertFalse(valueParameter.isVararg());
+    typeArguments = valueParameter.type().typeArguments();
+    assertEquals(3, typeArguments.size());
+
+    typeArgument = typeArguments.get(0).type();
+    assertEquals(cls.getFinalDescriptor(), typeArgument.descriptor());
+    typeArgument = typeArguments.get(1).type();
+    assertEquals(KT_STRING, typeArgument.descriptor());
+    typeArgument = typeArguments.get(2).type();
+    assertEquals(KT_UNIT, typeArgument.descriptor());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
index ba6b08f..0bca5ee 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
@@ -30,6 +31,7 @@
 
 @RunWith(Parameterized.class)
 public class MetadataRewriteInLibraryTypeTest extends KotlinMetadataTestBase {
+  private static final String EXPECTED = StringUtils.lines("Sub::foo", "Sub::boo", "true");
 
   private final TestParameters parameters;
 
@@ -104,7 +106,7 @@
         .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), baseLibJarMap.get(targetVersion))
         .addClasspath(out)
         .run(parameters.getRuntime(), main)
-        .assertSuccessWithOutputLines("Sub::foo", "Sub::boo", "true");
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java
index 7ecf894..0e734ea 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
@@ -30,6 +31,8 @@
 
 @RunWith(Parameterized.class)
 public class MetadataRewriteInNestedClassTest extends KotlinMetadataTestBase {
+  private static final String EXPECTED =
+      StringUtils.lines("Inner::inner", "42", "Nested::nested", "42");
 
   private final TestParameters parameters;
 
@@ -88,7 +91,7 @@
         .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
         .addClasspath(output)
         .run(parameters.getRuntime(), PKG + ".nested_app.MainKt")
-        .assertSuccessWithOutputLines("Inner::inner", "42", "Nested::nested", "42");
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java
index 6dcbc1a..05acc76 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
@@ -29,6 +30,7 @@
 
 @RunWith(Parameterized.class)
 public class MetadataRewriteInParameterTypeTest extends KotlinMetadataTestBase {
+  private static final String EXPECTED = StringUtils.lines("Impl::bar", "Program::bar");
 
   private final TestParameters parameters;
 
@@ -83,7 +85,7 @@
         .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
         .addClasspath(output)
         .run(parameters.getRuntime(), PKG + ".parametertype_app.MainKt")
-        .assertSuccessWithOutputLines("Impl::bar", "Program::bar");
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java
index 8aba4f7..4fc0029 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
@@ -29,6 +30,7 @@
 
 @RunWith(Parameterized.class)
 public class MetadataRewriteInPropertyTypeTest extends KotlinMetadataTestBase {
+  private static final String EXPECTED = StringUtils.lines("Impl::8");
 
   private final TestParameters parameters;
 
@@ -81,7 +83,7 @@
         .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
         .addClasspath(output)
         .run(parameters.getRuntime(), PKG + ".propertytype_app.MainKt")
-        .assertSuccessWithOutputLines("Impl::8");
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java
index 1e33e7c..21fcbb6 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
@@ -29,6 +30,8 @@
 
 @RunWith(Parameterized.class)
 public class MetadataRewriteInReturnTypeTest extends KotlinMetadataTestBase {
+  private static final String EXPECTED = StringUtils.lines("Impl::foo", "Program::foo", "true");
+
   private final TestParameters parameters;
 
   @Parameterized.Parameters(name = "{0} target: {1}")
@@ -82,7 +85,7 @@
         .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
         .addClasspath(output)
         .run(parameters.getRuntime(), PKG + ".returntype_app.MainKt")
-        .assertSuccessWithOutputLines("Impl::foo", "Program::foo", "true");
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
index 57cd84c..373dc40 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
@@ -21,6 +21,7 @@
 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.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
@@ -37,6 +38,7 @@
 
 @RunWith(Parameterized.class)
 public class MetadataRewriteInSealedClassTest extends KotlinMetadataTestBase {
+  private static final String EXPECTED = StringUtils.lines("6");
 
   private final TestParameters parameters;
 
@@ -93,7 +95,7 @@
         .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
         .addClasspath(output)
         .run(parameters.getRuntime(), PKG + ".sealed_app.ValidKt")
-        .assertSuccessWithOutputLines("6");
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspectValid(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java
index ba59f4b..9de95b4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
@@ -29,6 +30,7 @@
 
 @RunWith(Parameterized.class)
 public class MetadataRewriteInSuperTypeTest extends KotlinMetadataTestBase {
+  private static final String EXPECTED = StringUtils.lines("Impl::foo", "Program::foo");
 
   private final TestParameters parameters;
 
@@ -83,7 +85,7 @@
         .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
         .addClasspath(output)
         .run(parameters.getRuntime(), PKG + ".supertype_app.MainKt")
-        .assertSuccessWithOutputLines("Impl::foo", "Program::foo");
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspectMerged(CodeInspector inspector) {
@@ -130,7 +132,7 @@
         .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
         .addClasspath(output)
         .run(parameters.getRuntime(), PKG + ".supertype_app.MainKt")
-        .assertSuccessWithOutputLines("Impl::foo", "Program::foo");
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspectRenamed(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/default_value_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/default_value_app/main.kt
index 9cb6039..08551b5 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/default_value_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/default_value_app/main.kt
@@ -7,6 +7,6 @@
 
 fun main() {
   val m = mapOf("A" to "a", "B" to "b", "C" to "c")
-  val s = listOf("A", "B", "C").joinToString(separator = "\n")
+  val s = listOf("A", "B", "C").joinToString(separator = System.lineSeparator())
   println(s.applyMap(m))
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/default_value_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/default_value_lib/lib.kt
index c76b5be..dafbdbf 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/default_value_lib/lib.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/default_value_lib/lib.kt
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata.default_value_lib
 
-fun String.applyMap(map: Map<String, String>, separator: String = "\n") =
+fun String.applyMap(map: Map<String, String>, separator: String = System.lineSeparator()) =
   this.split(separator).joinToString(separator = separator) {
     if (map.containsKey(it)) map.getValue(it) else it
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_app/main.kt
index d85c992..72e86b4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_app/main.kt
@@ -15,6 +15,5 @@
 
   "R8".csHash()
   longArrayOf(42L).longArrayHash()
-  // TODO(b/70169921): Need to set arguments as type parameter.
-  //  B().myApply { this.doStuff() }
+  B().myApply { this.doStuff() }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java b/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java
index 374f91d..d7eeae4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java
@@ -42,7 +42,10 @@
         .addProgramFiles(Paths.get(ToolHelper.EXAMPLES_KOTLIN_BUILD_DIR, "enumswitch.jar"))
         .addKeepMainRule("enumswitch.EnumSwitchKt")
         .addOptionsModification(
-            options -> options.enableEnumValueOptimization = enableSwitchMapRemoval)
+            options -> {
+              options.enableEnumValueOptimization = enableSwitchMapRemoval;
+              options.enableEnumSwitchMapRemoval = enableSwitchMapRemoval;
+            })
         .setMinApi(parameters.getRuntime())
         .noMinification()
         .compile()
diff --git a/src/test/java/com/android/tools/r8/naming/ClassNameMinifierOriginalClassNameTest.java b/src/test/java/com/android/tools/r8/naming/ClassNameMinifierOriginalClassNameTest.java
new file mode 100644
index 0000000..9d4c4fb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/ClassNameMinifierOriginalClassNameTest.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.testclasses.A;
+import com.android.tools.r8.naming.testclasses.B;
+import com.android.tools.r8.utils.FileUtils;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Function;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** This test is designed to test the workaround for b/149946708. */
+@RunWith(Parameterized.class)
+public class ClassNameMinifierOriginalClassNameTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassNameMinifierOriginalClassNameTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private static Function<TestParameters, R8TestCompileResult> compilationResults =
+      memoizeFunction(ClassNameMinifierOriginalClassNameTest::compile);
+
+  private static R8TestCompileResult compile(TestParameters parameters)
+      throws CompilationFailedException, IOException, ExecutionException {
+    // Adding the obfuscation dictionary just ensures that we assign a name to B that will collide
+    // independent of minification scheme.
+    Path dictionary = getStaticTemp().newFolder().toPath().resolve("dictionary.txt");
+    FileUtils.writeTextFile(dictionary, "A");
+    return testForR8(getStaticTemp(), parameters.getBackend())
+        .addProgramClasses(A.class, B.class)
+        .addKeepClassAndMembersRulesWithAllowObfuscation(B.class)
+        .addKeepRules("-classobfuscationdictionary " + dictionary.toString(), "-keeppackagenames")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              assertEquals(1, inspector.allClasses().size());
+              assertThat(inspector.clazz(B.class), isRenamed());
+              assertEquals(A.class.getTypeName(), inspector.clazz(B.class).getFinalName());
+            });
+  }
+
+  @Test
+  public void testR8() throws ExecutionException, CompilationFailedException, IOException {
+    R8TestCompileResult libraryCompileResult = compilationResults.apply(parameters);
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .noMinification()
+        .addClasspathClasses(A.class, B.class)
+        .addApplyMapping(libraryCompileResult.getProguardMap())
+        .compile()
+        .addRunClasspathFiles(libraryCompileResult.writeToZip())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("B.foo");
+  }
+
+  @Test
+  public void testR8WithReferenceToNotMapped() {
+    assumeTrue(parameters.isDexRuntime());
+    R8TestCompileResult libraryCompileResult = compilationResults.apply(parameters);
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForR8(parameters.getBackend())
+                .addProgramClasses(MainWithReferenceToNotMapped.class)
+                .setMinApi(parameters.getApiLevel())
+                .addKeepMainRule(MainWithReferenceToNotMapped.class)
+                .noMinification()
+                .addClasspathClasses(A.class, B.class)
+                .addApplyMapping(libraryCompileResult.getProguardMap())
+                .allowDiagnosticWarningMessages()
+                .compileWithExpectedDiagnostics(
+                    diagnosticMessages ->
+                        diagnosticMessages.assertAllWarningMessagesMatch(
+                            containsString(
+                                "'"
+                                    + B.class.getTypeName()
+                                    + "' cannot be mapped to '"
+                                    + A.class.getTypeName()
+                                    + "' because it is in conflict"))));
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new B().foo();
+    }
+  }
+
+  public static class MainWithReferenceToNotMapped {
+
+    public static void main(String[] args) {
+      System.out.println(new A());
+      new B().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/FieldMinificationCollisionTest.java b/src/test/java/com/android/tools/r8/naming/FieldMinificationCollisionTest.java
index 69b4b03..180541a 100644
--- a/src/test/java/com/android/tools/r8/naming/FieldMinificationCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/naming/FieldMinificationCollisionTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -29,6 +30,7 @@
             .addKeepMainRule(TestClass.class)
             .addKeepRules(
                 "-keep class " + B.class.getTypeName() + " { public java.lang.String f2; }")
+            .enableMemberValuePropagationAnnotations()
             .enableNeverClassInliningAnnotations()
             .enableInliningAnnotations()
             .enableMergeAnnotations()
@@ -55,7 +57,7 @@
   @NeverMerge
   static class A {
 
-    public String f1;
+    @NeverPropagateValue public String f1;
 
     public A(String f1) {
       this.f1 = f1;
@@ -65,7 +67,7 @@
   @NeverMerge
   static class B extends A {
 
-    public String f2;
+    @NeverPropagateValue public String f2;
 
     public B(String f1, String f2) {
       super(f1);
@@ -76,7 +78,7 @@
   @NeverClassInline
   static class C extends B {
 
-    public String f3;
+    @NeverPropagateValue public String f3;
 
     public C(String f1, String f2, String f3) {
       super(f1, f2);
diff --git a/src/test/java/com/android/tools/r8/naming/LibraryClassInheritingFromProgramClassNamingTest.java b/src/test/java/com/android/tools/r8/naming/LibraryClassInheritingFromProgramClassNamingTest.java
new file mode 100644
index 0000000..a6c49e4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/LibraryClassInheritingFromProgramClassNamingTest.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import 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 LibraryClassInheritingFromProgramClassNamingTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final Class[] LIBRARY_CLASSES =
+      new Class[] {AndroidTestCase.class, ApplicationTestCase.class};
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public LibraryClassInheritingFromProgramClassNamingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws ExecutionException, CompilationFailedException, IOException {
+    R8TestCompileResult libraryResult =
+        testForR8(parameters.getBackend())
+            .setMinApi(parameters.getApiLevel())
+            .addProgramClasses(LIBRARY_CLASSES)
+            .addProgramClasses(
+                I.class, Assert.class, TestCase.class, ApplicationTestCaseInProgram.class)
+            .addKeepAllClassesRule()
+            .compile();
+    testForR8Compat(parameters.getBackend())
+        .addLibraryClasses(LIBRARY_CLASSES)
+        .addLibraryFiles(runtimeJar(parameters.getBackend()))
+        .addProgramClasses(
+            I.class, Assert.class, TestCase.class, ApplicationTestCaseInProgram.class, Main.class)
+        .addKeepClassAndMembersRulesWithAllowObfuscation(
+            I.class, Assert.class, TestCase.class, ApplicationTestCaseInProgram.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .allowDiagnosticWarningMessages()
+        .compile()
+        .assertAllWarningMessagesMatch(
+            containsString(
+                "Library class "
+                    + AndroidTestCase.class.getTypeName()
+                    + " extends program class "
+                    + TestCase.class.getTypeName()))
+        .addRunClasspathFiles(libraryResult.writeToZip())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("TestCase.foo");
+  }
+
+  public interface I {
+
+    void foo();
+  }
+
+  public static class Assert implements I {
+
+    @Override
+    public void foo() {
+      System.out.println("Assert.foo");
+    }
+  }
+
+  public static class TestCase extends Assert {
+
+    @Override
+    public void foo() {
+      System.out.println("TestCase.foo");
+    }
+  }
+
+  public static class AndroidTestCase extends TestCase {}
+
+  public static class ApplicationTestCase extends AndroidTestCase {}
+
+  public static class ApplicationTestCaseInProgram extends AndroidTestCase {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new ApplicationTestCaseInProgram().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubClassTest.java b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubClassTest.java
index 77190ce..943e4bf 100644
--- a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubClassTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubClassTest.java
@@ -10,6 +10,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -34,7 +35,8 @@
 
   @Parameterized.Parameters(name = "{0}, reserve name: {1}")
   public static List<Object[]> data() {
-    return buildParameters(getTestParameters().withAllRuntimes().build(), BooleanUtils.values());
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
   }
 
   public ReservedFieldNameInSubClassTest(TestParameters parameters, boolean reserveName) {
@@ -49,6 +51,7 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(
                 TestClass.class, A.class, B.class, C.class, I.class, J.class, K.class)
+            .enableMemberValuePropagationAnnotations()
             .enableMergeAnnotations()
             .addKeepMainRule(TestClass.class)
             .addKeepRules(
@@ -57,7 +60,7 @@
                         + C.class.getTypeName()
                         + "{ java.lang.String a; }"
                     : "")
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expectedOutput)
             .inspector();
@@ -140,13 +143,13 @@
   @NeverMerge
   static class A {
 
-    String f1 = "He";
+    @NeverPropagateValue String f1 = "He";
   }
 
   @NeverMerge
   static class B extends A {
 
-    String f2 = "l";
+    @NeverPropagateValue String f2 = "l";
   }
 
   @NeverMerge
@@ -169,7 +172,7 @@
 
   static class C extends B implements J, K {
 
-    String a = "!";
+    @NeverPropagateValue String a = "!";
 
     @Override
     public String toString() {
diff --git a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubInterfaceTest.java b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubInterfaceTest.java
index 057ba0c..309c156 100644
--- a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubInterfaceTest.java
@@ -11,6 +11,7 @@
 import static org.junit.Assume.assumeFalse;
 
 import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -46,6 +47,7 @@
     R8TestRunResult result =
         testForR8(Backend.DEX)
             .addProgramClasses(TestClass.class, A.class, B.class, I.class, J.class)
+            .enableMemberValuePropagationAnnotations()
             .enableMergeAnnotations()
             .addKeepMainRule(TestClass.class)
             .addKeepRules(
@@ -102,6 +104,7 @@
         .addProgramClasses(TestClass.class, A.class, B.class)
         .addLibraryClasses(I.class, J.class)
         .addLibraryFiles(runtimeJar(Backend.DEX))
+        .enableMemberValuePropagationAnnotations()
         .enableMergeAnnotations()
         .addKeepMainRule(TestClass.class)
         .compile()
@@ -138,7 +141,7 @@
   @NeverMerge
   static class A {
 
-    String f2 = " ";
+    @NeverPropagateValue String f2 = " ";
   }
 
   static class B extends A implements J {
diff --git a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSuperInterfaceTest.java b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSuperInterfaceTest.java
index f7551b7..4af8655 100644
--- a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSuperInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSuperInterfaceTest.java
@@ -10,6 +10,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeFalse;
 
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -50,6 +51,7 @@
                 reserveName
                     ? "-keepclassmembernames class " + I.class.getTypeName() + "{ <fields>; }"
                     : "")
+            .enableMemberValuePropagationAnnotations()
             .run(TestClass.class)
             .assertSuccessWithOutput(expectedOutput);
 
@@ -79,6 +81,7 @@
         .addLibraryClasses(I.class)
         .addLibraryFiles(runtimeJar(Backend.DEX))
         .addKeepMainRule(TestClass.class)
+        .enableMemberValuePropagationAnnotations()
         .compile()
         .addRunClasspathFiles(testForD8().addProgramClasses(I.class).compile().writeToZip())
         .run(TestClass.class)
@@ -116,7 +119,7 @@
 
   static class A implements I {
 
-    String f2 = "world!";
+    @NeverPropagateValue String f2 = "world!";
 
     @Override
     public String toString() {
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingKeepPrecedenceTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingKeepPrecedenceTest.java
index da47d4c..3d4b859 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingKeepPrecedenceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingKeepPrecedenceTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -38,7 +39,7 @@
   @NeverClassInline
   public static class B { // Should be kept with all members without renaming.
 
-    int foo = 4;
+    @NeverPropagateValue int foo = 4;
 
     @NeverInline
     int bar() {
@@ -63,7 +64,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public ApplyMappingKeepPrecedenceTest(TestParameters parameters) {
@@ -90,7 +91,8 @@
         .addKeepClassRules(A.class)
         .addKeepRules("-keepclassmembernames class " + B.class.getTypeName() + " { *; }")
         .addKeepMainRule(Main.class)
-        .setMinApi(parameters.getRuntime())
+        .enableMemberValuePropagationAnnotations()
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(
             "What is the answer to life the universe and everything?", "42")
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMinificationTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMinificationTest.java
index d6382aa..f527ead 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMinificationTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMinificationTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -32,8 +33,10 @@
 
   @NeverClassInline
   public static class A {
-    public int fieldA = 1;
-    public int fieldB = 2;
+
+    @NeverPropagateValue public int fieldA = 1;
+
+    @NeverPropagateValue public int fieldB = 2;
 
     @NeverInline
     public void methodA() {
@@ -75,7 +78,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public ApplyMappingMinificationTest(TestParameters parameters) {
@@ -96,9 +99,10 @@
             .addKeepRules(
                 "-keepclassmembers class " + A.class.getTypeName() + " { void methodC(); }")
             .enableInliningAnnotations()
+            .enableMemberValuePropagationAnnotations()
             .enableNeverClassInliningAnnotations()
             .addApplyMapping(StringUtils.lines(pgMap))
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), C.class)
             .assertSuccessWithOutputLines("1", "2", "A.methodA", "A.methodB", "A.methodC", "B.foo")
             .inspect(
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java
index 58cdef6..ad3b7eb 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -44,7 +43,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection params() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -67,7 +66,7 @@
     testForR8(parameters.getBackend())
         .addProgramClasses(LibraryInterface.class, ProgramClass.class)
         .addKeepMainRule(ProgramClass.class)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), ProgramClass.class)
         .assertSuccessWithOutput(EXPECTED);
   }
@@ -79,12 +78,12 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(LibraryInterface.class)
             .addKeepRules(ruleContent)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .compile();
     CodeInspector inspector = libraryResult.inspector();
     assertTrue(inspector.clazz(LibraryInterface.class).isPresent());
     assertTrue(inspector.method(LibraryInterface.class.getMethod("foo")).isPresent());
-    if (willDesugarDefaultInterfaceMethods(parameters.getRuntime())) {
+    if (willDesugarDefaultInterfaceMethods(parameters.getApiLevel())) {
       ClassSubject companion = inspector.clazz(Reference.classFromDescriptor(
           InterfaceMethodRewriter.getCompanionClassDescriptor(
               classFromClass(LibraryInterface.class).getDescriptor())));
@@ -100,15 +99,14 @@
         .addClasspathClasses(LibraryInterface.class)
         .addApplyMapping(libraryResult.getProguardMap())
         .addKeepMainRule(ProgramClass.class)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .addRunClasspathFiles(libraryResult.writeToZip())
         .run(parameters.getRuntime(), ProgramClass.class)
         .assertSuccessWithOutput(EXPECTED);
   }
 
-  private static boolean willDesugarDefaultInterfaceMethods(TestRuntime runtime) {
-    return runtime.isDex()
-        && runtime.asDex().getMinApiLevel().getLevel() < AndroidApiLevel.N.getLevel();
+  private static boolean willDesugarDefaultInterfaceMethods(AndroidApiLevel apiLevel) {
+    return apiLevel != null && apiLevel.getLevel() < AndroidApiLevel.N.getLevel();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/shared/NameClashTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/shared/NameClashTest.java
index 0b37f73..3466e21 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/shared/NameClashTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/shared/NameClashTest.java
@@ -401,21 +401,6 @@
   }
 
   @Test
-  public void testR8_originalLibClassRenamedToExistingName() throws Exception {
-    FileUtils.writeTextFile(mappingFile, mappingToExistingClassName());
-    try {
-      testR8_originalLibraryJar(mappingFile);
-      fail("Expect compilation failure.");
-    } catch (CompilationFailedException e) {
-      assertThat(e.getCause().getMessage(), containsString("cannot be mapped to"));
-      assertThat(
-          e.getCause().getMessage(),
-          containsString("because it is in conflict with an existing class with the same name."));
-      assertThat(e.getCause().getMessage(), containsString(ProgramClass.class.getTypeName()));
-    }
-  }
-
-  @Test
   public void testProguard_minifiedLib() throws Exception {
     FileUtils.writeTextFile(mappingFile, invertedMapping());
     try {
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java
index 30a8126..3b683fa 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java
@@ -9,6 +9,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -29,7 +30,7 @@
 @NeverMerge
 abstract class AbstractChecker {
   // String tag -> p
-  private String tag = "PrivateInitialTag_AbstractChecker";
+  @NeverPropagateValue private String tag = "PrivateInitialTag_AbstractChecker";
 
   // check() -> x
   private void check() {
@@ -80,7 +81,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public MemberResolutionTest(TestParameters parameters) {
@@ -121,9 +122,10 @@
                 AbstractChecker.class, ConcreteChecker.class, MemberResolutionTestMain.class)
             .addKeepMainRule(MemberResolutionTestMain.class)
             .addKeepRules("-applymapping " + mapPath)
+            .enableMemberValuePropagationAnnotations()
             .enableMergeAnnotations()
             .addOptionsModification(options -> options.enableInlining = false)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), MemberResolutionTestMain.class)
             .assertSuccessWithOutput(expectedOutput)
             .inspector();
diff --git a/src/test/java/com/android/tools/r8/naming/testclasses/A.java b/src/test/java/com/android/tools/r8/naming/testclasses/A.java
new file mode 100644
index 0000000..28a93a6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/testclasses/A.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.testclasses;
+
+public class A {}
diff --git a/src/test/java/com/android/tools/r8/naming/testclasses/B.java b/src/test/java/com/android/tools/r8/naming/testclasses/B.java
new file mode 100644
index 0000000..a364510
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/testclasses/B.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.testclasses;
+
+public class B extends A {
+
+  public void foo() {
+    System.out.println("B.foo");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b150688800/IdempotentCountErrorTest.java b/src/test/java/com/android/tools/r8/regress/b150688800/IdempotentCountErrorTest.java
new file mode 100644
index 0000000..52222e0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b150688800/IdempotentCountErrorTest.java
@@ -0,0 +1,60 @@
+// 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.regress.b150688800;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class IdempotentCountErrorTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("0.0");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public IdempotentCountErrorTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .enableInliningAnnotations()
+        .enableMemberValuePropagationAnnotations()
+        .addInnerClasses(IdempotentCountErrorTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  static class TestClass {
+
+    @NeverInline
+    @NeverPropagateValue
+    public static double twoInputValues(double x, double y) {
+      return x + y;
+    }
+
+    public static void main(String[] args) {
+      if (args.length > 42) {
+        System.out.println(twoInputValues(0, 0));
+      } else {
+        System.out.println(twoInputValues(0, 0));
+      }
+    }
+  }
+}
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 72ede27..0d89583 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.resolution;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.AsmTestBase;
@@ -31,10 +32,11 @@
 import com.android.tools.r8.resolution.singletarget.two.OtherSubSubClassTwo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
+import java.io.IOException;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.stream.Collectors;
 import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -76,11 +78,11 @@
       String methodName,
       Class invokeReceiver,
       Class singleTargetHolderOrNull,
-      List<Class> allTargetHolders) {
+      List<Class> virtualTargetHolders) {
     this.methodName = methodName;
     this.invokeReceiver = invokeReceiver;
     this.singleTargetHolderOrNull = singleTargetHolderOrNull;
-    this.allTargetHolders = allTargetHolders;
+    this.virtualTargetHolders = virtualTargetHolders;
   }
 
   @BeforeClass
@@ -89,6 +91,10 @@
     appInfo = appView.appInfo();
   }
 
+  private static Object[] noVirtualSingleTarget(String name, Class<?> receiverAndTarget) {
+    return new Object[] {name, receiverAndTarget, null, Collections.emptyList()};
+  }
+
   private static Object[] singleTarget(String name, Class<?> receiverAndTarget) {
     return new Object[]{name, receiverAndTarget, receiverAndTarget,
         Collections.singletonList(receiverAndTarget)};
@@ -119,54 +125,29 @@
         new Object[][] {
           singleTarget("singleTargetAtTop", AbstractTopClass.class),
           singleTargetWithAbstracts(
-              "singleShadowingOverride",
-              AbstractTopClass.class,
-              AbstractSubClass.class,
-              AbstractTopClass.class),
+              "singleShadowingOverride", AbstractTopClass.class, AbstractSubClass.class),
           manyTargets(
               "abstractTargetAtTop",
               AbstractTopClass.class,
-              AbstractTopClass.class,
               SubSubClassOne.class,
               SubSubClassTwo.class),
           singleTargetWithAbstracts(
-              "overridenInAbstractClassOnly",
-              AbstractTopClass.class,
-              AbstractTopClass.class,
-              SubSubClassThree.class),
-          onlyUnreachableTargets(
-              "overridenInAbstractClassOnly", SubSubClassThree.class, SubSubClassThree.class),
+              "overridenInAbstractClassOnly", AbstractTopClass.class, AbstractTopClass.class),
+          onlyUnreachableTargets("overridenInAbstractClassOnly", SubSubClassThree.class),
           manyTargets(
               "overriddenInTwoSubTypes",
               AbstractTopClass.class,
-              AbstractTopClass.class,
               SubSubClassOne.class,
               SubSubClassTwo.class),
           manyTargets(
               "definedInTwoSubTypes",
               AbstractTopClass.class,
-              AbstractTopClass.class,
               SubSubClassOne.class,
               SubSubClassTwo.class),
           onlyUnreachableTargets("staticMethod", AbstractTopClass.class),
-          manyTargets(
-              "overriddenInTwoSubTypes",
-              OtherAbstractTopClass.class,
-              OtherAbstractTopClass.class,
-              OtherSubSubClassOne.class,
-              OtherSubSubClassTwo.class),
-          manyTargets(
-              "abstractOverriddenInTwoSubTypes",
-              OtherAbstractTopClass.class,
-              OtherAbstractTopClass.class,
-              OtherSubSubClassOne.class,
-              OtherSubSubClassTwo.class),
-          manyTargets(
-              "overridesOnDifferentLevels",
-              OtherAbstractTopClass.class,
-              OtherAbstractTopClass.class,
-              OtherSubSubClassOne.class,
-              OtherAbstractSubClassTwo.class),
+          manyTargets("overriddenInTwoSubTypes", OtherAbstractTopClass.class),
+          manyTargets("abstractOverriddenInTwoSubTypes", OtherAbstractTopClass.class),
+          manyTargets("overridesOnDifferentLevels", OtherAbstractTopClass.class),
           singleTarget("defaultMethod", AbstractTopClass.class, InterfaceWithDefault.class),
           manyTargets(
               "overriddenDefault",
@@ -177,18 +158,11 @@
           singleTarget("overriddenByIrrelevantInterface", AbstractTopClass.class),
           singleTarget(
               "overriddenByIrrelevantInterface", SubSubClassOne.class, AbstractTopClass.class),
-          manyTargets(
-              "overriddenInOtherInterface",
-              AbstractTopClass.class,
-              InterfaceWithDefault.class),
-          manyTargets(
-              "abstractMethod",
-              ThirdAbstractTopClass.class,
-              ThirdAbstractTopClass.class,
-              ThirdSubClassOne.class),
-          singleTarget("instanceMethod", ThirdAbstractTopClass.class, ThirdAbstractTopClass.class),
           singleTarget(
-              "otherInstanceMethod", ThirdAbstractTopClass.class, ThirdAbstractTopClass.class),
+              "overriddenInOtherInterface", AbstractTopClass.class, InterfaceWithDefault.class),
+          manyTargets("abstractMethod", ThirdAbstractTopClass.class),
+          noVirtualSingleTarget("instanceMethod", ThirdAbstractTopClass.class),
+          noVirtualSingleTarget("otherInstanceMethod", ThirdAbstractTopClass.class),
         });
   }
 
@@ -199,14 +173,15 @@
   private final String methodName;
   private final Class invokeReceiver;
   private final Class singleTargetHolderOrNull;
-  private final List<Class> allTargetHolders;
+  private final List<Class> virtualTargetHolders;
 
   @Test
   public void lookupSingleTarget() {
     DexMethod method = buildNullaryVoidMethod(invokeReceiver, methodName, appInfo.dexItemFactory());
     Assert.assertNotNull(
         appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget());
-    DexEncodedMethod singleVirtualTarget = appInfo.lookupSingleVirtualTarget(method, method.holder);
+    DexEncodedMethod singleVirtualTarget =
+        appInfo.lookupSingleVirtualTarget(method, method.holder, false);
     if (singleTargetHolderOrNull == null) {
       Assert.assertNull(singleVirtualTarget);
     } else {
@@ -217,7 +192,7 @@
   }
 
   @Test
-  public void lookupVirtualTargets() {
+  public void lookupVirtualTargets() throws IOException {
     DexMethod method = buildNullaryVoidMethod(invokeReceiver, methodName, appInfo.dexItemFactory());
     Assert.assertNotNull(
         appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget());
@@ -226,16 +201,24 @@
       LookupResult lookupResult =
           resolutionResult.lookupVirtualDispatchTargets(
               appView.definitionForProgramType(buildType(Main.class, appView.dexItemFactory())),
-              appView);
+              appInfo);
       assertTrue(lookupResult.isLookupResultSuccess());
-      Set<DexEncodedMethod> targets = lookupResult.asLookupResultSuccess().getMethodTargets();
-      Set<DexType> targetHolders =
-          targets.stream().map(m -> m.method.holder).collect(Collectors.toSet());
-      Assert.assertEquals(allTargetHolders.size(), targetHolders.size());
+      assertFalse(lookupResult.asLookupResultSuccess().hasLambdaTargets());
+      Set<DexType> targetHolders = new HashSet<>();
+      lookupResult
+          .asLookupResultSuccess()
+          .forEach(
+              methodTarget -> targetHolders.add(methodTarget.getHolder().type),
+              lambdaTarget -> {
+                assert false;
+              });
+      Assert.assertEquals(virtualTargetHolders.size(), targetHolders.size());
       assertTrue(
-          allTargetHolders.stream().map(t -> toType(t, appInfo)).allMatch(targetHolders::contains));
+          virtualTargetHolders.stream()
+              .map(t -> toType(t, appInfo))
+              .allMatch(targetHolders::contains));
     } else {
-      assertTrue(allTargetHolders.isEmpty());
+      assertTrue(virtualTargetHolders.isEmpty());
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
index 674c090..eb40786 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
@@ -124,7 +124,7 @@
     assertEquals(methodOnB, resolved.method);
     assertFalse(resolutionResult.isVirtualTarget());
     DexEncodedMethod singleVirtualTarget =
-        appInfo.lookupSingleInterfaceTarget(methodOnB, methodOnB.holder);
+        appInfo.lookupSingleVirtualTarget(methodOnB, methodOnB.holder, false);
     Assert.assertNull(singleVirtualTarget);
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
index f9fb653..1f24983 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
@@ -170,7 +170,7 @@
     assertEquals(methodOnA, resolved.method);
     assertFalse(resolutionResult.isVirtualTarget());
     DexEncodedMethod singleVirtualTarget =
-        appInfo.lookupSingleVirtualTarget(methodOnB, methodOnB.holder);
+        appInfo.lookupSingleVirtualTarget(methodOnB, methodOnB.holder, false);
     Assert.assertNull(singleVirtualTarget);
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java
index e517531..94b3c45 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java
@@ -70,7 +70,6 @@
     testForRuntime(parameters)
         .addProgramClasses(getClasses())
         .run(parameters.getRuntime(), Main.class)
-        .disassemble()
         .apply(this::checkExpectedResult);
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java
index 1d75a8b..34af60e 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java
@@ -70,7 +70,6 @@
         .addProgramClasses(getClasses())
         .addProgramClassFileData(getTransforms())
         .run(parameters.getRuntime(), Main.class)
-        .disassemble()
         .apply(this::checkExpectedResult);
   }
 
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 27da1c4..1e9ca71 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
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -16,7 +17,6 @@
 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;
@@ -24,9 +24,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -60,15 +60,13 @@
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected =
         ImmutableSet.of(
-            I.class.getTypeName() + ".bar",
             A.class.getTypeName() + ".bar",
             J.class.getTypeName() + ".bar");
     assertEquals(expected, targets);
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 c8df559..ccb80a5 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
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -16,7 +17,6 @@
 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;
@@ -24,9 +24,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -58,12 +58,11 @@
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected =
         ImmutableSet.of(A.class.getTypeName() + ".bar", I.class.getTypeName() + ".bar");
     assertEquals(expected, targets);
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 ce8ce66..b3aae9e 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
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -16,7 +17,6 @@
 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;
@@ -24,9 +24,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -62,12 +62,11 @@
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
   }
@@ -106,12 +105,11 @@
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
   }
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
index da61d6a..7c50e54 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -16,7 +17,6 @@
 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;
@@ -24,9 +24,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -60,12 +60,11 @@
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
   }
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 016347b..06f7e8d 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
@@ -66,7 +66,7 @@
         () ->
             appInfo
                 .resolveMethod(method.holder, method)
-                .lookupVirtualDispatchTargets(context, appView));
+                .lookupVirtualDispatchTargets(context, appInfo));
   }
 
   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 0ba0226..c8fad67 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
@@ -57,7 +57,7 @@
         () ->
             appInfo
                 .resolveMethod(method.holder, method)
-                .lookupVirtualDispatchTargets(context, appView));
+                .lookupVirtualDispatchTargets(context, appInfo));
   }
 
   @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 eeca9fe..9cf0cf2 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
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -16,7 +17,6 @@
 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;
@@ -24,9 +24,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -59,12 +59,11 @@
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected =
         ImmutableSet.of(A.class.getTypeName() + ".bar", J.class.getTypeName() + ".bar");
     assertEquals(expected, targets);
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 64c81fa..0013abb 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
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -16,7 +17,6 @@
 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;
@@ -24,9 +24,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -59,12 +59,11 @@
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected =
         ImmutableSet.of(B.class.getTypeName() + ".foo", C.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
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 a21d3e1..fbe03e3 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
@@ -5,6 +5,7 @@
 package com.android.tools.r8.resolution.interfacetargets;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
@@ -16,7 +17,6 @@
 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;
@@ -24,9 +24,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -60,12 +60,17 @@
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    assertFalse(lookupResult.asLookupResultSuccess().hasLambdaTargets());
+    Set<String> targets = new HashSet<>();
+    lookupResult
+        .asLookupResultSuccess()
+        .forEach(
+            target -> targets.add(target.getMethod().qualifiedName()),
+            lambda -> {
+              assert false;
+            });
     ImmutableSet<String> expected =
         ImmutableSet.of(A.class.getTypeName() + ".foo", B.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
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 80caa62..4ccdb10 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
@@ -5,7 +5,9 @@
 package com.android.tools.r8.resolution.interfacetargets;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -15,7 +17,6 @@
 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;
@@ -23,9 +24,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -59,17 +60,19 @@
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    assertFalse(lookupResult.asLookupResultSuccess().hasLambdaTargets());
+    Set<String> targets = new HashSet<>();
+    lookupResult
+        .asLookupResultSuccess()
+        .forEach(
+            target -> targets.add(target.getMethod().qualifiedName()),
+            lambda -> {
+              fail();
+            });
     ImmutableSet<String> expected =
-        ImmutableSet.of(
-            J.class.getTypeName() + ".foo",
-            A.class.getTypeName() + ".foo",
-            C.class.getTypeName() + ".foo");
+        ImmutableSet.of(J.class.getTypeName() + ".foo", A.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
   }
 
@@ -115,7 +118,6 @@
     @Override
     public void foo() {
       System.out.println("A.foo");
-      ;
     }
   }
 
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 713d80e..d5e8bb3 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
@@ -5,7 +5,9 @@
 package com.android.tools.r8.resolution.interfacetargets;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -15,7 +17,6 @@
 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;
@@ -23,9 +24,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -58,12 +59,17 @@
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    assertFalse(lookupResult.asLookupResultSuccess().hasLambdaTargets());
+    Set<String> targets = new HashSet<>();
+    lookupResult
+        .asLookupResultSuccess()
+        .forEach(
+            target -> targets.add(target.getMethod().qualifiedName()),
+            lambda -> {
+              fail();
+            });
     ImmutableSet<String> expected =
         ImmutableSet.of(B.class.getTypeName() + ".foo", C.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
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 ba468e0..23f1c7b 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
@@ -5,7 +5,9 @@
 package com.android.tools.r8.resolution.interfacetargets;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -15,7 +17,6 @@
 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;
@@ -23,9 +24,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -58,12 +59,17 @@
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    assertFalse(lookupResult.asLookupResultSuccess().hasLambdaTargets());
+    Set<String> targets = new HashSet<>();
+    lookupResult
+        .asLookupResultSuccess()
+        .forEach(
+            target -> targets.add(target.getMethod().qualifiedName()),
+            lambda -> {
+              fail();
+            });
     ImmutableSet<String> expected =
         ImmutableSet.of(A.class.getTypeName() + ".foo", B.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
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
index a714a03..379b6e8 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -13,7 +14,6 @@
 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;
@@ -25,9 +25,9 @@
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.HashSet;
 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;
@@ -71,12 +71,11 @@
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Abstract.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(C.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
   }
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 eb08197..b5f36aa 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
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -14,7 +15,6 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRunResult;
 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;
@@ -25,9 +25,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -59,17 +59,12 @@
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     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",
-            B.class.getTypeName() + ".bar",
-            D.class.getTypeName() + ".bar");
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+    ImmutableSet<String> expected = ImmutableSet.of(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 619ee45..2305c93 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
@@ -4,9 +4,9 @@
 
 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.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -15,8 +15,8 @@
 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.AccessFlags;
 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;
@@ -28,9 +28,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -63,16 +63,13 @@
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected =
         ImmutableSet.of(
-            A.class.getTypeName() + ".bar",
-            B.class.getTypeName() + ".bar",
             D.class.getTypeName() + ".bar");
     assertEquals(expected, targets);
   }
@@ -96,19 +93,18 @@
   @Test
   public void testR8()
       throws ExecutionException, CompilationFailedException, IOException, NoSuchMethodException {
-    // TODO(b/149363086): Fix test.
     testForR8(parameters.getBackend())
         .addProgramClasses(A.class, B.class, C.class, Main.class)
         .addProgramClassFileData(getDWithPackagePrivateFoo())
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Main.class)
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatMatches(containsString("IllegalAccessError"));
+        .assertSuccessWithOutputLines(EXPECTED);
   }
 
   private byte[] getDWithPackagePrivateFoo() throws NoSuchMethodException, IOException {
     return transformer(D.class)
-        .setAccessFlags(D.class.getDeclaredMethod("bar"), m -> m.unsetPublic())
+        .setAccessFlags(D.class.getDeclaredMethod("bar"), AccessFlags::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
index cc85b0d..e156624 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethod2Test.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethod2Test.java
@@ -7,6 +7,7 @@
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -17,7 +18,6 @@
 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;
@@ -31,9 +31,9 @@
 import com.android.tools.r8.transformers.ClassTransformer;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -75,12 +75,11 @@
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     // TODO(b/148591377): The set should be empty.
     ImmutableSet<String> expected = ImmutableSet.of(AbstractWidening.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
@@ -123,8 +122,7 @@
             .addProgramClassFileData(getNonAbstractWithoutDeclaredMethods())
             .setMinApi(parameters.getApiLevel())
             .addKeepMainRule(Main.class)
-            .run(parameters.getRuntime(), Main.class)
-            .disassemble();
+            .run(parameters.getRuntime(), Main.class);
     if (parameters.isDexRuntime()
         && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
       runResult.assertFailure();
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 42efe5c..d711ba7 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
@@ -7,6 +7,7 @@
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -17,7 +18,6 @@
 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;
@@ -31,9 +31,9 @@
 import com.android.tools.r8.transformers.ClassTransformer;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -74,12 +74,11 @@
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     // TODO(b/148591377): The set should be empty.
     ImmutableSet<String> expected = ImmutableSet.of(Abstract.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
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 b761626..ec013d6 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,6 +5,8 @@
 package com.android.tools.r8.resolution.packageprivate;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -14,7 +16,6 @@
 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;
@@ -24,9 +25,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -59,13 +60,17 @@
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     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());
-    ImmutableSet<String> expected =
-        ImmutableSet.of(A.class.getTypeName() + ".bar", B.class.getTypeName() + ".bar");
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
+    assertFalse(lookupResult.asLookupResultSuccess().hasLambdaTargets());
+    Set<String> targets = new HashSet<>();
+    lookupResult
+        .asLookupResultSuccess()
+        .forEach(
+            target -> targets.add(target.getMethod().qualifiedName()),
+            lambda -> {
+              fail();
+            });
+    ImmutableSet<String> expected = ImmutableSet.of(B.class.getTypeName() + ".bar");
     assertEquals(expected, targets);
   }
 
@@ -85,13 +90,12 @@
 
   @Test
   public void testR8() throws ExecutionException, CompilationFailedException, IOException {
-    // TODO(b/149363086): Fix expectation.
     testForR8(parameters.getBackend())
         .addProgramClasses(A.class, B.class, C.class, Main.class)
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Main.class)
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(EXPECTED_DALVIK);
+        .assertSuccessWithOutputLines(EXPECTED);
   }
 
   public static class C extends B {
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/one/SubSubClassThree.java b/src/test/java/com/android/tools/r8/resolution/singletarget/one/SubSubClassThree.java
index 9b4e20a..1cd4170 100644
--- a/src/test/java/com/android/tools/r8/resolution/singletarget/one/SubSubClassThree.java
+++ b/src/test/java/com/android/tools/r8/resolution/singletarget/one/SubSubClassThree.java
@@ -5,6 +5,7 @@
 
 public abstract class SubSubClassThree extends AbstractSubClass {
 
+  @Override
   public void overridenInAbstractClassOnly() {
     System.out.println(SubSubClassThree.class.getCanonicalName());
   }
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 88c7b9e..ebbb52f 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
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -16,7 +17,6 @@
 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;
@@ -24,9 +24,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -59,17 +59,14 @@
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
-    // TODO(b/148591377): Should we report B.foo()?
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected =
         ImmutableSet.of(
             A.class.getTypeName() + ".foo",
-            B.class.getTypeName() + ".foo",
             C.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
   }
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 00ef1db..4f572ee 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
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -16,7 +17,6 @@
 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;
@@ -24,9 +24,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -59,12 +59,11 @@
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     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 3c30267..55d4138 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
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -16,7 +17,6 @@
 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;
@@ -24,9 +24,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -59,12 +59,11 @@
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
   }
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 dccf199..2800d2c 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
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -16,7 +17,6 @@
 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;
@@ -24,9 +24,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -62,12 +62,11 @@
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java
index 8eb3399..d909591 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java
@@ -62,7 +62,7 @@
               assertTrue(resolutionResult.isSingleResolution());
               DexType mainType = buildType(Main.class, appInfo.dexItemFactory());
               DexProgramClass main = appView.definitionForProgramType(mainType);
-              assertNull(resolutionResult.lookupVirtualDispatchTarget(main, appView));
+              assertNull(resolutionResult.lookupVirtualDispatchTarget(main, appInfo));
             });
     assertThat(
         foo.getMessage(),
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 7d32527..b2c70f9 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
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -16,7 +17,6 @@
 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;
@@ -24,9 +24,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -58,12 +58,11 @@
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(I.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
index ffc9114..b2e57b2 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
@@ -6,13 +6,13 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 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.DexType;
@@ -26,6 +26,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -61,7 +62,8 @@
         methodToBeKept,
         classToBeKept,
         Arrays.asList(expectedMethodHolders),
-        Arrays.asList(I.class, A.class, B.class, C.class));
+        Arrays.asList(I.class, A.class, B.class, C.class, Main.class),
+        Main.class);
   }
 
   private LookupResultSuccess testLookup(
@@ -69,7 +71,8 @@
       Class<?> methodToBeKept,
       Class<?> classToBeKept,
       Collection<Class<?>> expectedMethodHolders,
-      Collection<Class<?>> classes)
+      Collection<Class<?>> classes,
+      Class<?> main)
       throws Exception {
     AppView<AppInfoWithLiveness> appView =
         computeAppViewWithLiveness(
@@ -78,6 +81,7 @@
               List<ProguardConfigurationRule> rules = new ArrayList<>();
               rules.addAll(buildKeepRuleForClassAndMethods(methodToBeKept, factory));
               rules.addAll(buildKeepRuleForClass(classToBeKept, factory));
+              rules.addAll(buildKeepRuleForClassAndMethods(main, factory));
               return rules;
             });
     AppInfoWithLiveness appInfo = appView.appInfo();
@@ -85,13 +89,12 @@
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Unrelated.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
     LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
-    Set<String> targets =
-        lookupResultSuccess.getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     Set<String> expected =
         expectedMethodHolders.stream()
             .map(c -> c.getTypeName() + ".foo")
@@ -237,19 +240,18 @@
     LookupResult lookupResult =
         resolutionResult.lookupVirtualDispatchTargets(
             classB,
-            appView,
+            appInfo,
             (type, subTypeConsumer, callSiteConsumer) -> {
-              if (type == typeA) {
+              if (type == typeB) {
                 subTypeConsumer.accept(classB);
               }
             },
             reference -> false);
     assertTrue(lookupResult.isLookupResultSuccess());
     LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
-    Set<String> targets =
-        lookupResultSuccess.getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     Set<String> expected = ImmutableSet.of(A.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
     assertTrue(lookupResultSuccess.isComplete());
@@ -267,13 +269,12 @@
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Unrelated.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
     LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
-    Set<String> targets =
-        lookupResultSuccess.getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     Set<String> expected = ImmutableSet.of(Unrelated.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
     assertTrue(lookupResultSuccess.isIncomplete());
@@ -334,7 +335,8 @@
                 I.class,
                 Z.class,
                 Collections.singleton(X.class),
-                Arrays.asList(X.class, I.class, Y.class, Z.class))
+                Arrays.asList(X.class, I.class, Y.class, Z.class),
+                MainXYZ.class)
             .isIncomplete());
   }
 
@@ -368,6 +370,17 @@
     }
   }
 
+  public static class Main {
+
+    public static void main(String[] args) {
+      // This is necessary for considering the classes as instantiated.
+      new Unrelated();
+      new A();
+      new B();
+      new C();
+    }
+  }
+
   public static class X {
 
     public void foo() {
@@ -378,4 +391,14 @@
   public static class Y extends X implements I {}
 
   public static class Z extends Y {}
+
+  public static class MainXYZ {
+
+    public static void main(String[] args) {
+      // This is necessary for considering the classes as instantiated.
+      new X();
+      new Y();
+      new Z();
+    }
+  }
 }
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 37c071a..e0f4416 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
@@ -7,6 +7,7 @@
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -16,7 +17,6 @@
 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;
@@ -27,9 +27,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -64,14 +64,12 @@
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(TopRunner.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     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");
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+    ImmutableSet<String> expected = ImmutableSet.of(Middle.class.getTypeName() + ".clear");
     assertEquals(expected, targets);
   }
 
@@ -91,13 +89,12 @@
 
   @Test
   public void testR8() throws ExecutionException, CompilationFailedException, IOException {
-    // TODO(b/148584615): Fix test.
     testForR8(parameters.getBackend())
         .addProgramClasses(Top.class, Middle.class, Bottom.class, TopRunner.class, Main.class)
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
+        .assertSuccessWithOutputLines(EXPECTED);
   }
 
   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 dfc9d43..03425c6 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
@@ -7,6 +7,7 @@
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -18,7 +19,6 @@
 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;
@@ -30,9 +30,9 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -44,8 +44,8 @@
   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 static final String[] AMBIGUOUS_EXPECTED_OUTPUT =
+      new String[] {"ViewModel.clear()", "MyViewModel.clear()", "MyViewModel.clear()"};
 
   private final TestParameters parameters;
 
@@ -72,12 +72,11 @@
     DexProgramClass context =
         appView.definitionForProgramType(
             buildType(ViewModelRunner.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(ViewModel.class.getTypeName() + ".clear");
     assertEquals(expected, targets);
   }
@@ -99,13 +98,12 @@
 
   @Test
   public void testR8() throws ExecutionException, CompilationFailedException, IOException {
-    // 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);
+        .assertSuccessWithOutputLines(EXPECTED);
   }
 
   @Test
@@ -121,7 +119,7 @@
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultFailure());
   }
 
@@ -137,21 +135,20 @@
         && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
       runResult.assertFailureWithErrorThatMatches(containsString("clear overrides final"));
     } else {
-      runResult.assertFailureWithErrorThatMatches(containsString("java.lang.IllegalAccessError"));
+      runResult.assertFailureWithErrorThatThrows(IllegalAccessError.class);
     }
   }
 
   @Test
   public void testR8WithInvalidInvoke()
       throws ExecutionException, CompilationFailedException, IOException {
-    // 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"));
+        .assertFailureWithErrorThatThrows(IllegalAccessError.class);
   }
 
   @Test
@@ -169,12 +166,11 @@
     DexProgramClass context =
         appView.definitionForProgramType(
             buildType(ViewModelRunnerWithCast.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(ViewModel.class.getTypeName() + ".clear");
     assertEquals(expected, targets);
   }
@@ -191,22 +187,20 @@
         && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
       runResult.assertFailureWithErrorThatMatches(containsString("clear overrides final"));
     } else {
-      runResult.assertSuccessWithOutputLines(
-          "ViewModel.clear()", "MyViewModel.clear()", "MyViewModel.clear()");
+      runResult.assertSuccessWithOutputLines(AMBIGUOUS_EXPECTED_OUTPUT);
     }
   }
 
   @Test
   public void testR8WithAmbiguousInvoke()
       throws ExecutionException, CompilationFailedException, IOException {
-    // 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);
+        .assertSuccessWithOutputLines(AMBIGUOUS_EXPECTED_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
index 915cd21..645fb55 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PrivateOverrideOfVirtualTargetTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PrivateOverrideOfVirtualTargetTest.java
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -13,7 +14,6 @@
 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;
@@ -24,9 +24,9 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -57,18 +57,17 @@
             buildClasses(A.class, Main.class)
                 .addClassProgramData(getBWithModifiedInvokes())
                 .build(),
-            DefaultWithoutTopTest.Main.class);
+            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);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    Set<String> targets = new HashSet<>();
+    lookupResult.forEach(
+        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(A.class.getTypeName() + ".bar");
     assertEquals(expected, targets);
   }
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 3afeb68..628c927 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
@@ -5,7 +5,9 @@
 package com.android.tools.r8.resolution.virtualtargets;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -16,7 +18,6 @@
 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;
@@ -24,9 +25,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.HashSet;
 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;
@@ -59,12 +60,17 @@
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
-    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets =
-        lookupResult.asLookupResultSuccess().getMethodTargets().stream()
-            .map(DexEncodedMethod::qualifiedName)
-            .collect(Collectors.toSet());
+    assertFalse(lookupResult.asLookupResultSuccess().hasLambdaTargets());
+    Set<String> targets = new HashSet<>();
+    lookupResult
+        .asLookupResultSuccess()
+        .forEach(
+            target -> targets.add(target.getMethod().qualifiedName()),
+            lambda -> {
+              fail();
+            });
     ImmutableSet<String> expected =
         ImmutableSet.of(B.class.getTypeName() + ".foo", I.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
index 2f9a1ec..78a36f2 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
@@ -48,6 +48,7 @@
 
   private void configure(InternalOptions options) {
     options.enableEnumValueOptimization = enableOptimization;
+    options.enableEnumSwitchMapRemoval = enableOptimization;
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumValuesLengthTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumValuesLengthTest.java
new file mode 100644
index 0000000..7a5300d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumValuesLengthTest.java
@@ -0,0 +1,157 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.rewrite.enums;
+
+import static junit.framework.TestCase.assertTrue;
+
+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.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+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 EnumValuesLengthTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public EnumValuesLengthTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testValuesLengthRemoved() throws Exception {
+    testForR8(parameters.getBackend())
+        .addKeepMainRule(Main.class)
+        .addInnerClasses(EnumValuesLengthTest.class)
+        .noMinification()
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(
+            opt -> {
+              opt.enableEnumValueOptimization = true;
+              // We need to keep the switch map to ensure kept switch maps have their
+              // values array length rewritten.
+              opt.enableEnumSwitchMapRemoval = false;
+            })
+        .compile()
+        .inspect(this::assertValuesLengthRemoved)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("0", "2", "5", "a", "D", "c", "D");
+  }
+
+  @Test
+  public void testValuesLengthSwitchMapRemoved() throws Exception {
+    // Make sure SwitchMap can still be removed with valuesLength optimization.
+    testForR8(parameters.getBackend())
+        .addKeepMainRule(Main.class)
+        .addInnerClasses(EnumValuesLengthTest.class)
+        .noMinification()
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(
+            opt -> {
+              opt.enableEnumValueOptimization = true;
+            })
+        .compile()
+        .inspect(this::assertSwitchMapRemoved)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("0", "2", "5", "a", "D", "c", "D");
+  }
+
+  private void assertSwitchMapRemoved(CodeInspector inspector) {
+    assertTrue(
+        inspector.allClasses().stream()
+            .noneMatch(c -> !c.getDexClass().isEnum() && !c.getFinalName().endsWith("Main")));
+  }
+
+  private void assertValuesLengthRemoved(CodeInspector inspector) {
+    for (FoundClassSubject clazz : inspector.allClasses()) {
+      clazz.forAllMethods(this::assertValuesLengthRemoved);
+    }
+  }
+
+  private void assertValuesLengthRemoved(FoundMethodSubject method) {
+    assertTrue(method.streamInstructions().noneMatch(InstructionSubject::isArrayLength));
+    assertTrue(
+        method
+            .streamInstructions()
+            .noneMatch(
+                instr ->
+                    instr.isInvokeStatic() && instr.getMethod().name.toString().equals("values")));
+  }
+
+  public static class Main {
+
+    @NeverClassInline
+    enum E0 {}
+
+    @NeverClassInline
+    enum E2 {
+      A,
+      B
+    }
+
+    @NeverClassInline
+    enum E5 {
+      A,
+      B,
+      C,
+      D,
+      E
+    }
+
+    @NeverClassInline
+    enum EUnusedValues {
+      A,
+      B,
+      C
+    }
+
+    @NeverClassInline
+    enum ESwitch {
+      A,
+      B,
+      C,
+      D
+    }
+
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    public static void main(String[] args) {
+      EUnusedValues.values();
+      System.out.println(E0.values().length);
+      System.out.println(E2.values().length);
+      System.out.println(E5.values().length);
+      System.out.println(switchOn(ESwitch.A));
+      System.out.println(switchOn(ESwitch.B));
+      System.out.println(switchOn(ESwitch.C));
+      System.out.println(switchOn(ESwitch.D));
+    }
+
+    // SwitchMaps feature an array length on values, and some of them are not removed.
+    @NeverInline
+    static char switchOn(ESwitch e) {
+      switch (e) {
+        case A:
+          return 'a';
+        case C:
+          return 'c';
+        default:
+          return 'D';
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/NonTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/NonTargetedMethodTest.java
new file mode 100644
index 0000000..8f560a2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/NonTargetedMethodTest.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.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.utils.StringUtils;
+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 NonTargetedMethodTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("A::foo", "C::bar");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public NonTargetedMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .addInnerClasses(NonTargetedMethodTest.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkIsFooPresent);
+  }
+
+  private void checkIsFooPresent(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(C.class);
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject.uniqueMethodWithName("foo"), not(isPresent()));
+  }
+
+  @NeverMerge
+  private static class A {
+    @NeverInline
+    public void foo() {
+      System.out.println("A::foo");
+    }
+  }
+
+  @NeverMerge
+  @NeverClassInline
+  private static class B extends A {
+    // No override of foo, but B::foo will be the only target.
+  }
+
+  @NeverClassInline
+  private static class C extends A {
+
+    // Non-targeted override.
+    @Override
+    public void foo() {
+      System.out.println("C::foo");
+    }
+
+    @NeverInline
+    public void bar() {
+      System.out.println("C::bar");
+    }
+  }
+
+  private static class Main {
+
+    public static void main(String[] args) {
+      new B().foo();
+      new C().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
index 5220c01..8673412 100644
--- a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
@@ -169,7 +169,8 @@
   @Test
   public void test() throws Exception {
     // Run the program on Art after is has been compiled with R8.
-    String referenceResult = expectedResults.apply(isDexVmBetween5_1_1and7_0_0(parameters));
+    String referenceResult =
+        expectedResults.apply(!enableClassInlining && isDexVmBetween5_1_1and7_0_0(parameters));
     R8TestCompileResult compiled =
         compilationResults.apply(
             new Dimensions(
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
index 327e1b5..9c88b7f 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -158,7 +158,9 @@
     Optional<ClassSubject> thing = inspector.clazz("shaking8.Thing");
     assertTrue(thing.isPresent());
     assertTrue(thing.get().field("int", "aField"));
-    assertFalse(inspector.clazz("shaking8.OtherThing").isPresent());
+    Optional<ClassSubject> otherThing = inspector.clazz("shaking8.OtherThing");
+    assertTrue(otherThing.isPresent());
+    assertTrue(otherThing.get().field("int", "otherField"));
     assertTrue(inspector.clazz("shaking8.YetAnotherThing").isPresent());
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index fbe40d6..d246ba7 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -140,7 +140,11 @@
             .collect(Collectors.joining("\n"));
     String refMapping =
         new String(
-            Files.readAllBytes(Paths.get(EXAMPLES_DIR, "shaking1", "print-mapping.ref")),
+            Files.readAllBytes(
+                Paths.get(
+                    EXAMPLES_DIR,
+                    "shaking1",
+                    "print-mapping-" + backend.name().toLowerCase() + ".ref")),
             StandardCharsets.UTF_8);
     Assert.assertEquals(sorted(refMapping), sorted(actualMapping));
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesForConstantValuedFieldTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesForConstantValuedFieldTest.java
index 94e96f1..0c6c8b8 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesForConstantValuedFieldTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesForConstantValuedFieldTest.java
@@ -19,7 +19,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public AssumeValuesForConstantValuedFieldTest(TestParameters parameters) {
@@ -32,7 +32,7 @@
         .addInnerClasses(AssumeValuesForConstantValuedFieldTest.class)
         .addKeepMainRule(TestClass.class)
         .addKeepRules("-assumevalues class * { static boolean field return false; }")
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines(
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
index 5b1009e..9b8d209 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
@@ -3,6 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+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.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -50,11 +54,10 @@
   }
 
   private static void shaking8ThingClassIsAbstractAndEmpty(CodeInspector inspector) {
-    ClassSubject clazz = inspector.clazz("shaking8.Thing");
-    Assert.assertTrue(clazz.isAbstract());
-    clazz.forAllMethods((method) -> Assert.fail());
-    clazz = inspector.clazz("shaking8.YetAnotherThing");
-    Assert.assertTrue(clazz.isAbstract());
-    clazz.forAllMethods((method) -> Assert.fail());
+    ClassSubject thingClass = inspector.clazz("shaking8.Thing");
+    Assert.assertTrue(thingClass.isAbstract());
+    thingClass.forAllMethods((method) -> Assert.fail());
+    ClassSubject yetAnotherThingClass = inspector.clazz("shaking8.YetAnotherThing");
+    assertThat(yetAnotherThingClass, not(isPresent()));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
index 707a415..366a97e 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
@@ -13,6 +13,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -51,7 +52,7 @@
   public enum T {
     A(A::new);
 
-    private final Supplier<Object> factory;
+    @NeverPropagateValue private final Supplier<Object> factory;
 
     T(Supplier<Object> factory) {
       this.factory = factory;
@@ -111,6 +112,7 @@
             .enableGraphInspector(consumer)
             .addProgramClassesAndInnerClasses(Main.class, A.class, T.class)
             .addKeepMethodRules(mainMethod)
+            .enableMemberValuePropagationAnnotations()
             .setMinApi(AndroidApiLevel.N)
             .apply(
                 b -> {
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/testclasses/SubInterface.java b/src/test/java/com/android/tools/r8/shaking/proxy/testclasses/SubInterface.java
index 755c063..4c0823d 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/testclasses/SubInterface.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/testclasses/SubInterface.java
@@ -5,5 +5,6 @@
 package com.android.tools.r8.shaking.proxy.testclasses;
 
 public interface SubInterface extends BaseInterface {
+  @Override
   void method();
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/testclasses/TestClass.java b/src/test/java/com/android/tools/r8/shaking/proxy/testclasses/TestClass.java
index 2113eb7..97fdb2b 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/testclasses/TestClass.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/testclasses/TestClass.java
@@ -11,6 +11,7 @@
     this.name = name;
   }
 
+  @Override
   public void method() {
     System.out.println(name);
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
index 28eff88..cdf57aa 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
@@ -54,18 +54,18 @@
   public KmTypeSubject receiverParameterType() {
     KmType kmType = kmFunction.getReceiverParameterType();
     assert !isExtension() || kmType != null;
-    return kmType == null ? null : new KmTypeSubject(kmType);
+    return kmType == null ? null : new KmTypeSubject(codeInspector, kmType);
   }
 
   @Override
   public List<KmValueParameterSubject> valueParameters() {
     return kmFunction.getValueParameters().stream()
-        .map(KmValueParameterSubject::new)
+        .map(kmValueParameter -> new KmValueParameterSubject(codeInspector, kmValueParameter))
         .collect(Collectors.toList());
   }
 
   @Override
   public KmTypeSubject returnType() {
-    return new KmTypeSubject(kmFunction.getReturnType());
+    return new KmTypeSubject(codeInspector, kmFunction.getReturnType());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeProjectionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeProjectionSubject.java
new file mode 100644
index 0000000..790ecd2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeProjectionSubject.java
@@ -0,0 +1,36 @@
+// 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.codeinspector;
+
+import com.android.tools.r8.errors.Unreachable;
+import kotlinx.metadata.KmTypeProjection;
+
+public class KmTypeProjectionSubject extends Subject {
+  private final CodeInspector codeInspector;
+  private final KmTypeProjection kmTypeProjection;
+
+  KmTypeProjectionSubject(CodeInspector codeInspector, KmTypeProjection kmTypeProjection) {
+    this.codeInspector = codeInspector;
+    this.kmTypeProjection = kmTypeProjection;
+  }
+
+  public KmTypeSubject type() {
+    return new KmTypeSubject(codeInspector, kmTypeProjection.getType());
+  }
+
+  @Override
+  public boolean isPresent() {
+    return true;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    return type().isRenamed();
+  }
+
+  @Override
+  public boolean isSynthetic() {
+    throw new Unreachable("Cannot determine if a type argument is synthetic");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
index aa52c67..5d7ec81 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
@@ -6,15 +6,20 @@
 import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKotlinClassifier;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.Box;
+import java.util.List;
+import java.util.stream.Collectors;
 import kotlinx.metadata.KmType;
 import kotlinx.metadata.KmTypeVisitor;
 
 public class KmTypeSubject extends Subject {
+  private final CodeInspector codeInspector;
   private final KmType kmType;
 
-  KmTypeSubject(KmType kmType) {
+  KmTypeSubject(CodeInspector codeInspector, KmType kmType) {
     assert kmType != null;
+    this.codeInspector = codeInspector;
     this.kmType = kmType;
   }
 
@@ -46,6 +51,12 @@
     return getDescriptorFromKmType(kmType);
   }
 
+  public List<KmTypeProjectionSubject> typeArguments() {
+    return kmType.getArguments().stream()
+        .map(kmTypeProjection -> new KmTypeProjectionSubject(codeInspector, kmTypeProjection))
+        .collect(Collectors.toList());
+  }
+
   @Override
   public boolean isPresent() {
     return true;
@@ -53,7 +64,8 @@
 
   @Override
   public boolean isRenamed() {
-    throw new Unreachable("Cannot determine if a type is renamed");
+    ClassSubject classSubject = codeInspector.clazz(Reference.classFromDescriptor(descriptor()));
+    return classSubject.isRenamed();
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmValueParameterSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmValueParameterSubject.java
index 1f96b7d..61a2531 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmValueParameterSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmValueParameterSubject.java
@@ -9,21 +9,23 @@
 import kotlinx.metadata.KmValueParameter;
 
 public class KmValueParameterSubject extends Subject {
+  private final CodeInspector codeInspector;
   private final KmValueParameter kmValueParameter;
 
-  KmValueParameterSubject(KmValueParameter kmValueParameter) {
+  KmValueParameterSubject(CodeInspector codeInspector, KmValueParameter kmValueParameter) {
+    this.codeInspector = codeInspector;
     this.kmValueParameter = kmValueParameter;
   }
 
   public KmTypeSubject type() {
-    return new KmTypeSubject(kmValueParameter.getType());
+    return new KmTypeSubject(codeInspector, kmValueParameter.getType());
   }
 
   public KmTypeSubject varargElementType() {
     if (!isVararg()) {
       return null;
     }
-    return new KmTypeSubject(kmValueParameter.getVarargElementType());
+    return new KmTypeSubject(codeInspector, kmValueParameter.getVarargElementType());
   }
 
   public boolean isVararg() {
diff --git a/third_party/proguardsettings.tar.gz.sha1 b/third_party/proguardsettings.tar.gz.sha1
index d5c183a..13c1c86 100644
--- a/third_party/proguardsettings.tar.gz.sha1
+++ b/third_party/proguardsettings.tar.gz.sha1
@@ -1 +1 @@
-7bb1badb721635d2baa7b63aab1e3a67434f94dd
\ No newline at end of file
+8f67c53a54b0f05c657310c1f76896e4d7f402db
\ No newline at end of file
diff --git a/third_party/youtube/youtube.android_15.08.tar.gz.sha1 b/third_party/youtube/youtube.android_15.08.tar.gz.sha1
index 285c854..357adeb 100644
--- a/third_party/youtube/youtube.android_15.08.tar.gz.sha1
+++ b/third_party/youtube/youtube.android_15.08.tar.gz.sha1
@@ -1 +1 @@
-803d7565c1d56568cfe5d8da18e7fe82bfcac006
\ No newline at end of file
+dc8d9b8f89d9284336897fa4cb2a925ff9e07c67
\ No newline at end of file
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 6f78eef..6bfb5cb 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -23,9 +23,14 @@
     help='Dump file to compile',
     default=None)
   parser.add_argument(
+    '--temp',
+    help='Temp directory to extract the dump to, allows you to rerun the command'
+      ' more easily in the terminal with changes',
+    default=None)
+  parser.add_argument(
     '-c',
     '--compiler',
-    help='Compiler to use (default read from version file)',
+    help='Compiler to use',
     default=None)
   parser.add_argument(
     '-v',
@@ -37,6 +42,10 @@
       '  <hash> to run that hash from master.',
     default=None)
   parser.add_argument(
+    '--r8-jar',
+    help='Path to an R8 jar.',
+    default=None)
+  parser.add_argument(
     '--nolib',
     help='Use the non-lib distribution (default uses the lib distribution)',
     default=False,
@@ -103,9 +112,10 @@
   return args.version
 
 def determine_compiler(args, dump):
-  compilers = ('d8', 'r8', 'r8full')
+  compilers = ['d8', 'r8', 'r8full']
   if args.compiler not in compilers:
-    error("Unable to determine a compiler to use. Valid options: %s" % compilers.join(', '))
+    error("Unable to determine a compiler to use. Specified %s,"
+          " Valid options: %s" % (args.compiler, ', '.join(compilers)))
   return args.compiler
 
 def determine_output(args, temp):
@@ -137,11 +147,15 @@
 
 def run(args, otherargs):
   with utils.TempDir() as temp:
+    if args.temp:
+      temp = args.temp
+      if not os.path.exists(temp):
+        os.makedirs(temp)
     dump = read_dump(args, temp)
     version = determine_version(args, dump)
     compiler = determine_compiler(args, dump)
     out = determine_output(args, temp)
-    jar = download_distribution(args, version, temp)
+    jar = args.r8_jar if args.r8_jar else download_distribution(args, version, temp)
     wrapper_dir = prepare_wrapper(jar, temp)
     cmd = [jdk.GetJavaExecutable()]
     if args.debug_agent:
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index b96ccf4..16db28d 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -188,8 +188,6 @@
 # do Bug: #BUG in the commit message of disabling to ensure re-enabling
 DISABLED_PERMUTATIONS = [
   # (app, version, type), e.g., ('gmail', '180826.15', 'deploy'),
-  ('youtube', '13.37', 'deploy'), # b/120977564
-  ('youtube', '15.08', 'deploy'), # b/150267318
   ('youtube', '15.09', 'deploy'), # b/150267318
 ]
 
@@ -517,14 +515,6 @@
         args.extend(['--main-dex-rules', rules])
     if 'allow-type-errors' in values:
       extra_args.append('-Dcom.android.tools.r8.allowTypeErrors=1')
-    if 'proto-shrinking' in values:
-      extra_args.append('-Dcom.android.tools.r8.applyInliningToInlinee=1')
-      extra_args.append('-Dcom.android.tools.r8.fieldBitAccessAnalysis=1')
-      extra_args.append('-Dcom.android.tools.r8.generatedExtensionRegistryShrinking=1')
-      extra_args.append('-Dcom.android.tools.r8.generatedMessageLiteShrinking=1')
-      extra_args.append('-Dcom.android.tools.r8.generatedMessageLiteBuilderShrinking=1')
-      extra_args.append('-Dcom.android.tools.r8.stringSwitchConversion=1')
-      extra_args.append('-Dcom.android.tools.r8.traverseOneOfAndRepeatedProtoFields=0')
 
   if not options.no_libraries:
     for lib in libraries:
diff --git a/tools/youtube_data.py b/tools/youtube_data.py
index e8accdd..828e04b 100644
--- a/tools/youtube_data.py
+++ b/tools/youtube_data.py
@@ -141,7 +141,6 @@
       'pgconf': [
           '%s_proguard.config' % V14_19_PREFIX,
           '%s/proguardsettings/YouTubeRelease_proguard.config' % utils.THIRD_PARTY],
-      'proto-shrinking': 1,
       'maindexrules' : [
           os.path.join(V14_19_BASE, 'mainDexClasses.rules'),
           os.path.join(V14_19_BASE, 'main-dex-classes-release-optimized.pgcfg'),
@@ -171,7 +170,6 @@
       'pgconf': [
           '%s_proguard.config' % V14_44_PREFIX,
           '%s/proguardsettings/YouTubeRelease_proguard.config' % utils.THIRD_PARTY],
-      'proto-shrinking': 1,
       'maindexrules' : [
           os.path.join(V14_44_BASE, 'mainDexClasses.rules'),
           os.path.join(V14_44_BASE, 'main-dex-classes-release-optimized.pgcfg'),
@@ -200,8 +198,8 @@
       'libraries' : [os.path.join(V15_08_BASE, 'legacy_YouTubeRelease_combined_library_jars.jar')],
       'pgconf': [
           '%s_proguard.config' % V15_08_PREFIX,
+          '%s_proto_safety.pgcfg' % V15_08_PREFIX,
           '%s/proguardsettings/YouTubeRelease_proguard.config' % utils.THIRD_PARTY],
-      'proto-shrinking': 1,
       'maindexrules' : [
           os.path.join(V15_08_BASE, 'mainDexClasses.rules'),
           os.path.join(V15_08_BASE, 'main-dex-classes-release-optimized.pgcfg'),
@@ -231,7 +229,6 @@
       'pgconf': [
           '%s_proguard.config' % V15_09_PREFIX,
           '%s/proguardsettings/YouTubeRelease_proguard.config' % utils.THIRD_PARTY],
-      'proto-shrinking': 1,
       'maindexrules' : [
           os.path.join(V15_09_BASE, 'mainDexClasses.rules'),
           os.path.join(V15_09_BASE, 'main-dex-classes-release-optimized.pgcfg'),