Merge commit 'ea84911a967250e1de9b8d86a3f0da304dacda56' into dev-release
diff --git a/build.gradle b/build.gradle
index 171ede1..fd47c97 100644
--- a/build.gradle
+++ b/build.gradle
@@ -426,7 +426,9 @@
         "youtube/youtube.android_12.22",
         "youtube/youtube.android_13.37",
         "youtube/youtube.android_14.19",
-        "youtube/youtube.android_14.44"
+        "youtube/youtube.android_14.44",
+        "youtube/youtube.android_15.08",
+        "youtube/youtube.android_15.09"
     ],
 ]
 
diff --git a/src/library_desugar/desugar_jdk_libs.json b/src/library_desugar/desugar_jdk_libs.json
index 7d2924f..aadb671 100644
--- a/src/library_desugar/desugar_jdk_libs.json
+++ b/src/library_desugar/desugar_jdk_libs.json
@@ -1,6 +1,6 @@
 {
   "configuration_format_version": 3,
-  "version": "0.11.0",
+  "version": "0.11.1",
   "required_compilation_api_level": 26,
   "synthesized_library_classes_package_prefix": "j$.",
   "library_flags": [
@@ -68,13 +68,13 @@
         "java.util.concurrent.ConcurrentMap": "j$.util.concurrent.ConcurrentMap"
       },
       "custom_conversion": {
-        "java.util.Optional": "j$.util.OptionalConversions",
-        "java.util.OptionalDouble": "j$.util.OptionalConversions",
-        "java.util.OptionalInt": "j$.util.OptionalConversions",
-        "java.util.OptionalLong": "j$.util.OptionalConversions",
-        "java.util.LongSummaryStatistics": "j$.util.LongSummaryStatisticsConversions",
-        "java.util.IntSummaryStatistics": "j$.util.IntSummaryStatisticsConversions",
-        "java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatisticsConversions"
+        "java.util.Optional": "java.util.OptionalConversions",
+        "java.util.OptionalDouble": "java.util.OptionalConversions",
+        "java.util.OptionalInt": "java.util.OptionalConversions",
+        "java.util.OptionalLong": "java.util.OptionalConversions",
+        "java.util.LongSummaryStatistics": "java.util.LongSummaryStatisticsConversions",
+        "java.util.IntSummaryStatistics": "java.util.IntSummaryStatisticsConversions",
+        "java.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatisticsConversions"
       }
     }
   ],
@@ -93,12 +93,12 @@
         "java.util.GregorianCalendar#toZonedDateTime": "java.util.DesugarGregorianCalendar"
       },
       "custom_conversion": {
-        "java.time.ZonedDateTime": "j$.time.TimeConversions",
-        "java.time.LocalDate": "j$.time.TimeConversions",
-        "java.time.Duration": "j$.time.TimeConversions",
-        "java.time.ZoneId": "j$.time.TimeConversions",
-        "java.time.MonthDay": "j$.time.TimeConversions",
-        "java.time.Instant": "j$.time.TimeConversions"
+        "java.time.ZonedDateTime": "java.time.TimeConversions",
+        "java.time.LocalDate": "java.time.TimeConversions",
+        "java.time.Duration": "java.time.TimeConversions",
+        "java.time.ZoneId": "java.time.TimeConversions",
+        "java.time.MonthDay": "java.time.TimeConversions",
+        "java.time.Instant": "java.time.TimeConversions"
       }
     },
     {
@@ -152,13 +152,13 @@
         "java.util.concurrent.ConcurrentMap": "j$.util.concurrent.ConcurrentMap"
       },
       "custom_conversion": {
-        "java.util.Optional": "j$.util.OptionalConversions",
-        "java.util.OptionalDouble": "j$.util.OptionalConversions",
-        "java.util.OptionalInt": "j$.util.OptionalConversions",
-        "java.util.OptionalLong": "j$.util.OptionalConversions",
-        "java.util.LongSummaryStatistics": "j$.util.LongSummaryStatisticsConversions",
-        "java.util.IntSummaryStatistics": "j$.util.IntSummaryStatisticsConversions",
-        "java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatisticsConversions"
+        "java.util.Optional": "java.util.OptionalConversions",
+        "java.util.OptionalDouble": "java.util.OptionalConversions",
+        "java.util.OptionalInt": "java.util.OptionalConversions",
+        "java.util.OptionalLong": "java.util.OptionalConversions",
+        "java.util.LongSummaryStatistics": "java.util.LongSummaryStatisticsConversions",
+        "java.util.IntSummaryStatistics": "java.util.IntSummaryStatisticsConversions",
+        "java.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatisticsConversions"
       }
     }
   ],
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 4c4d2d5..2086f5e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -42,7 +42,7 @@
 import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.UninstantiatedTypeOptimizationGraphLense;
 import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector;
 import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector.UnusedArgumentsGraphLense;
-import com.android.tools.r8.ir.optimize.enums.EnumInfoMapCollector;
+import com.android.tools.r8.ir.optimize.enums.EnumValueInfoMapCollector;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.kotlin.Kotlin;
@@ -328,7 +328,10 @@
                         options.getProguardConfiguration().getRules(), synthesizedProguardRules))
                 .run(executorService));
 
-        AppView<AppInfoWithLiveness> appViewWithLiveness = runEnqueuer(executorService, appView);
+        AnnotationRemover.Builder annotationRemoverBuilder =
+            options.isShrinking() ? AnnotationRemover.builder() : null;
+        AppView<AppInfoWithLiveness> appViewWithLiveness =
+            runEnqueuer(annotationRemoverBuilder, executorService, appView);
         application = appViewWithLiveness.appInfo().app().asDirect();
         assert appView.rootSet().verifyKeptFieldsAreAccessedAndLive(appViewWithLiveness.appInfo());
         assert appView.rootSet().verifyKeptMethodsAreTargetedAndLive(appViewWithLiveness.appInfo());
@@ -366,16 +369,15 @@
                       pruner.getMethodsToKeepForConfigurationDebugging()));
           appView.setAppServices(appView.appServices().prunedCopy(pruner.getRemovedClasses()));
           new AbstractMethodRemover(appView.appInfo().withLiveness()).run();
+
+          AnnotationRemover annotationRemover =
+              annotationRemoverBuilder
+                  .computeClassesToRetainInnerClassAttributeFor(appViewWithLiveness)
+                  .build(appViewWithLiveness);
+          annotationRemover.ensureValid().run();
+          classesToRetainInnerClassAttributeFor =
+              annotationRemover.getClassesToRetainInnerClassAttributeFor();
         }
-
-        classesToRetainInnerClassAttributeFor =
-            AnnotationRemover.computeClassesToRetainInnerClassAttributeFor(appView.withLiveness());
-        // TODO(b/149729626): Annotations should not be removed until after the second round of tree
-        //  shaking, since they are needed for interpretation of keep rules.
-        new AnnotationRemover(appView.withLiveness(), classesToRetainInnerClassAttributeFor)
-            .ensureValid()
-            .run();
-
       } finally {
         timing.end();
       }
@@ -514,7 +516,7 @@
         appViewWithLiveness.setAppInfo(new SwitchMapCollector(appViewWithLiveness).run());
       }
       if (options.enableEnumValueOptimization || options.enableEnumUnboxing) {
-        appViewWithLiveness.setAppInfo(new EnumInfoMapCollector(appViewWithLiveness).run());
+        appViewWithLiveness.setAppInfo(new EnumValueInfoMapCollector(appViewWithLiveness).run());
       }
 
       appView.setAppServices(appView.appServices().rewrittenWithLens(appView.graphLense()));
@@ -660,6 +662,7 @@
             // assert Inliner.verifyNoMethodsInlinedDueToSingleCallSite(appView);
 
             assert appView.allMergedClasses().verifyAllSourcesPruned(appViewWithLiveness);
+            assert appView.validateUnboxedEnumsHaveBeenPruned();
 
             processWhyAreYouKeepingAndCheckDiscarded(
                 appView.rootSet(),
@@ -674,7 +677,9 @@
 
             // Remove annotations that refer to types that no longer exist.
             assert classesToRetainInnerClassAttributeFor != null;
-            new AnnotationRemover(appView.withLiveness(), classesToRetainInnerClassAttributeFor)
+            AnnotationRemover.builder()
+                .setClassesToRetainInnerClassAttributeFor(classesToRetainInnerClassAttributeFor)
+                .build(appView.withLiveness())
                 .run();
             if (!mainDexClasses.isEmpty()) {
               // Remove types that no longer exists from the computed main dex list.
@@ -816,10 +821,13 @@
     }
   }
 
-  private AppView<AppInfoWithLiveness> runEnqueuer(ExecutorService executorService,
-      AppView<AppInfoWithSubtyping> appView) throws ExecutionException {
+  private AppView<AppInfoWithLiveness> runEnqueuer(
+      AnnotationRemover.Builder annotationRemoverBuilder,
+      ExecutorService executorService,
+      AppView<AppInfoWithSubtyping> appView)
+      throws ExecutionException {
     Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView);
-
+    enqueuer.setAnnotationRemoverBuilder(annotationRemoverBuilder);
     if (appView.options().enableInitializedClassesInInstanceMethodsAnalysis) {
       enqueuer.registerAnalysis(new InitializedClassesInInstanceMethodsAnalysis(appView));
     }
@@ -878,22 +886,22 @@
       }
     }
     for (DexDefinition definition : failed) {
-      if (!failed.isEmpty()) {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        whyAreYouKeepingConsumer.printWhyAreYouKeeping(
-            enqueuer.getGraphReporter().getGraphNode(definition.toReference()),
-            new PrintStream(baos));
-        options.reporter.info(
-            new StringDiagnostic(
-                "Item " + definition.toSourceString() + " was not discarded.\n" + baos.toString()));
-      }
+      ByteArrayOutputStream baos = new ByteArrayOutputStream();
+      whyAreYouKeepingConsumer.printWhyAreYouKeeping(
+          enqueuer.getGraphReporter().getGraphNode(definition.toReference()),
+          new PrintStream(baos));
+      options.reporter.info(
+          new StringDiagnostic(
+              "Item " + definition.toSourceString() + " was not discarded.\n" + baos.toString()));
     }
-    throw new CompilationError("Discard checks failed.");
+    if (!options.testing.allowCheckDiscardedErrors) {
+      throw new CompilationError("Discard checks failed.");
+    }
   }
 
   private void computeKotlinInfoForProgramClasses(
       DexApplication application, AppView<?> appView, ExecutorService executorService)
-      throws ExecutionException{
+      throws ExecutionException {
     if (appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations) {
       return;
     }
@@ -906,8 +914,7 @@
           programClass.setKotlinInfo(kotlinInfo);
           KotlinMemberInfo.markKotlinMemberInfo(programClass, kotlinInfo, reporter);
         },
-        executorService
-    );
+        executorService);
   }
 
   private static boolean verifyNoJarApplicationReaders(List<DexProgramClass> classes) {
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index e30a78d..9eedd04 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.cf.code.CfJsrRet;
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.cf.code.CfLogicalBinop;
@@ -654,6 +655,11 @@
     builder.append(type.getType().toString());
   }
 
+  public void print(CfJsrRet ret) {
+    indent();
+    builder.append("ret ").append(ret.getLocal());
+  }
+
   private String getLabel(CfLabel label) {
     return labelToIndex != null ? ("L" + labelToIndex.getInt(label)) : "L?";
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
new file mode 100644
index 0000000..55aea68
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.cf.code;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.MethodVisitor;
+
+public class CfJsrRet extends CfInstruction {
+
+  private static CompilationError error() {
+    throw new CompilationError(
+        "Invalid compilation of code with reachable jump subroutine RET instruction");
+  }
+
+  private final int local;
+
+  public CfJsrRet(int local) {
+    this.local = local;
+  }
+
+  @Override
+  public void write(MethodVisitor visitor, NamingLens lens) {
+    throw error();
+  }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    throw error();
+  }
+
+  @Override
+  public ConstraintWithTarget inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    throw error();
+  }
+
+  public int getLocal() {
+    return local;
+  }
+}
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 eb972ca..a791cfc 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.ResolutionResult.NoSuchMethodResult;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
@@ -416,6 +417,13 @@
     return builder.lookup();
   }
 
+  // Non-private lookup (ie, not resolution) to find interface targets.
+  DexClassAndMethod lookupMaximallySpecificTarget(LambdaDescriptor lambda, DexMethod method) {
+    MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
+    resolveMethodStep3Helper(method, dexItemFactory.objectType, lambda.interfaces, builder);
+    return builder.lookup();
+  }
+
   /** Helper method that builds the set of maximally specific methods. */
   private void resolveMethodStep3Helper(
       DexMethod method, DexClass clazz, MaximallySpecificMethodsBuilder builder) {
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 e0e8269..35f989e 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.TraversalContinuation.BREAK;
 import static com.android.tools.r8.utils.TraversalContinuation.CONTINUE;
 
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
@@ -210,6 +211,11 @@
     return lookupMaximallySpecificTarget(clazz, method);
   }
 
+  public DexClassAndMethod lookupMaximallySpecificMethod(
+      LambdaDescriptor lambda, DexMethod method) {
+    return lookupMaximallySpecificTarget(lambda, method);
+  }
+
   /**
    * Lookup instance field starting in type and following the interface and super chain.
    *
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 9a07dd2..105868f 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -43,7 +43,6 @@
       Consumer<DexCallSite> callSiteConsumer) {
     WorkList<DexType> workList = WorkList.newIdentityWorkList();
     workList.addIfNotSeen(type);
-    workList.addIfNotSeen(allImmediateSubtypes(type));
     while (workList.hasNext()) {
       DexType subType = workList.next();
       DexProgramClass clazz = definitionForProgramType(subType);
diff --git a/src/main/java/com/android/tools/r8/graph/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java
index 584a879..5c1c7ad 100644
--- a/src/main/java/com/android/tools/r8/graph/AppServices.java
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -13,13 +13,13 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.io.ByteStreams;
 import java.io.IOException;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -176,7 +176,7 @@
       private void readServiceImplementationsForService(
           String contents, Origin origin, List<DexType> serviceImplementations) {
         if (contents != null) {
-          Arrays.stream(contents.split(System.lineSeparator()))
+          StringUtils.splitLines(contents).stream()
               .map(String::trim)
               .map(this::prefixUntilCommentChar)
               .filter(line -> !line.isEmpty())
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 1f24d35..8e0dfc4 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.utils.OptionalBool;
 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;
@@ -64,6 +65,7 @@
   private Set<DexMethod> unneededVisibilityBridgeMethods = ImmutableSet.of();
   private HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses;
   private VerticallyMergedClasses verticallyMergedClasses;
+  private Set<DexType> unboxedEnums = Collections.emptySet();
 
   private Map<DexClass, DexValueString> sourceDebugExtensions = new IdentityHashMap<>();
 
@@ -368,6 +370,20 @@
     this.verticallyMergedClasses = verticallyMergedClasses;
   }
 
+  public void setUnboxedEnums(Set<DexType> unboxedEnums) {
+    this.unboxedEnums = unboxedEnums;
+  }
+
+  public boolean validateUnboxedEnumsHaveBeenPruned() {
+    for (DexType unboxedEnum : unboxedEnums) {
+      assert definitionForProgramType(unboxedEnum) == null
+          : "Enum " + unboxedEnum + " has been unboxed but is still in the program.";
+      assert appInfo().withLiveness().wasPruned(unboxedEnum)
+          : "Enum " + unboxedEnum + " has been unboxed but was not pruned.";
+    }
+    return true;
+  }
+
   @SuppressWarnings("unchecked")
   public AppView<AppInfoWithClassHierarchy> withClassHierarchy() {
     return appInfo.hasClassHierarchy()
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 9c02dec..e9e47b6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -290,9 +290,13 @@
       debugInfo = debugInfoIterator.hasNext() ? debugInfoIterator.next() : null;
     }
     int instructionNumber = 0;
+    Map<Integer, DebugLocalInfo> locals = Collections.emptyMap();
     for (Instruction insn : instructions) {
       while (debugInfo != null && debugInfo.address == insn.getOffset()) {
-        builder.append("         ").append(debugInfo.toString(false)).append("\n");
+        if (debugInfo.lineEntry || !locals.equals(debugInfo.locals)) {
+          builder.append("         ").append(debugInfo.toString(false)).append("\n");
+        }
+        locals = debugInfo.locals;
         debugInfo = debugInfoIterator.hasNext() ? debugInfoIterator.next() : null;
       }
       StringUtils.appendLeftPadded(builder, Integer.toString(instructionNumber++), 5);
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinition.java b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
index 7f6796d..09daf79 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.shaking.AnnotationRemover;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
@@ -23,6 +25,11 @@
     return annotations;
   }
 
+  public DexAnnotationSet liveAnnotations(AppView<AppInfoWithLiveness> appView) {
+    return annotations.keepIf(
+        annotation -> AnnotationRemover.shouldKeepAnnotation(appView, this, annotation));
+  }
+
   public void clearAnnotations() {
     setAnnotations(DexAnnotationSet.empty());
   }
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 ce89a41..cba6dfc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -181,6 +181,10 @@
     AbstractValue abstractValue = getOptimizationInfo().getAbstractValue();
     if (abstractValue.isSingleValue()) {
       SingleValue singleValue = abstractValue.asSingleValue();
+      if (singleValue.isSingleFieldValue()
+          && singleValue.asSingleFieldValue().getField() == field) {
+        return null;
+      }
       if (singleValue.isMaterializableInContext(appView, code.method.method.holder)) {
         TypeLatticeElement type = TypeLatticeElement.fromDexType(field.type, maybeNull(), appView);
         return singleValue.createMaterializingInstruction(
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 1fdd415..342a3ed 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -62,6 +62,8 @@
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.AnnotationRemover;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Pair;
@@ -255,6 +257,11 @@
     assert parameterAnnotationsList != null;
   }
 
+  public ParameterAnnotationsList liveParameterAnnotations(AppView<AppInfoWithLiveness> appView) {
+    return parameterAnnotationsList.keepIf(
+        annotation -> AnnotationRemover.shouldKeepAnnotation(appView, this, annotation));
+  }
+
   public OptionalBool isLibraryMethodOverride() {
     return isNonPrivateVirtualMethod() ? isLibraryMethodOverride : OptionalBool.FALSE;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexMember.java b/src/main/java/com/android/tools/r8/graph/DexMember.java
index 96afd57..d04808b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -9,6 +9,7 @@
   public final DexType holder;
 
   public DexMember(DexType holder) {
+    assert holder != null;
     this.holder = holder;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 08d4d61..c046e6a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -11,6 +11,7 @@
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.EMULATE_LIBRARY_CLASS_NAME_SUFFIX;
 import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX;
 import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_GROUP_CLASS_NAME_PREFIX;
+import static com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_CLASS_NAME;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
@@ -272,6 +273,7 @@
   public boolean isD8R8SynthesizedClassType() {
     String name = toSourceString();
     return name.contains(COMPANION_CLASS_NAME_SUFFIX)
+        || name.contains(ENUM_UNBOXING_UTILITY_CLASS_NAME)
         || name.contains(EMULATE_LIBRARY_CLASS_NAME_SUFFIX)
         || name.contains(DISPATCH_CLASS_NAME_SUFFIX)
         || name.contains(TYPE_WRAPPER_SUFFIX)
diff --git a/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java b/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java
new file mode 100644
index 0000000..470685f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java
@@ -0,0 +1,121 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import java.util.Set;
+
+public class EnumValueInfoMapCollection {
+
+  public static EnumValueInfoMapCollection empty() {
+    return new EnumValueInfoMapCollection(ImmutableMap.of());
+  }
+
+  private final Map<DexType, EnumValueInfoMap> maps;
+
+  private EnumValueInfoMapCollection(Map<DexType, EnumValueInfoMap> maps) {
+    this.maps = maps;
+  }
+
+  public EnumValueInfoMap getEnumValueInfoMap(DexType type) {
+    return maps.get(type);
+  }
+
+  public boolean isEmpty() {
+    return maps.isEmpty();
+  }
+
+  public boolean containsEnum(DexType type) {
+    return maps.containsKey(type);
+  }
+
+  public Set<DexType> enumSet() {
+    return maps.keySet();
+  }
+
+  public EnumValueInfoMapCollection rewrittenWithLens(GraphLense lens) {
+    Builder builder = builder();
+    maps.forEach(
+        (type, map) -> {
+          DexType dexType = lens.lookupType(type);
+          // Enum unboxing may have changed the type to int type.
+          // Do not keep the map for such enums.
+          if (!dexType.isPrimitiveType()) {
+            builder.put(dexType, map.rewrittenWithLens(lens));
+          }
+        });
+    return builder.build();
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+
+    private ImmutableMap.Builder<DexType, EnumValueInfoMap> builder;
+
+    public Builder put(DexType type, EnumValueInfoMap map) {
+      if (builder == null) {
+        builder = ImmutableMap.builder();
+      }
+      builder.put(type, map);
+      return this;
+    }
+
+    public EnumValueInfoMapCollection build() {
+      if (builder == null) {
+        return empty();
+      }
+      return new EnumValueInfoMapCollection(builder.build());
+    }
+  }
+
+  public static final class EnumValueInfoMap {
+
+    private final Map<DexField, EnumValueInfo> map;
+
+    public EnumValueInfoMap(Map<DexField, EnumValueInfo> map) {
+      this.map = ImmutableMap.copyOf(map);
+    }
+
+    public int size() {
+      return map.size();
+    }
+
+    public EnumValueInfo getEnumValueInfo(DexField field) {
+      return map.get(field);
+    }
+
+    EnumValueInfoMap rewrittenWithLens(GraphLense lens) {
+      ImmutableMap.Builder<DexField, EnumValueInfo> builder = ImmutableMap.builder();
+      map.forEach(
+          (field, valueInfo) ->
+              builder.put(lens.lookupField(field), valueInfo.rewrittenWithLens(lens)));
+      return new EnumValueInfoMap(builder.build());
+    }
+  }
+
+  public static final class EnumValueInfo {
+
+    // The anonymous subtype of this specific value or the enum type.
+    public final DexType type;
+    public final int ordinal;
+
+    public EnumValueInfo(DexType type, int ordinal) {
+      this.type = type;
+      this.ordinal = ordinal;
+    }
+
+    EnumValueInfo rewrittenWithLens(GraphLense lens) {
+      DexType newType = lens.lookupType(type);
+      if (type == newType) {
+        return this;
+      }
+      return new EnumValueInfo(newType, ordinal);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignature.java b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
new file mode 100644
index 0000000..f948208
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -0,0 +1,941 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.graph;
+
+import static com.android.tools.r8.utils.DescriptorUtils.getClassBinaryNameFromDescriptor;
+import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromClassBinaryName;
+
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.GenericSignatureFormatError;
+import java.nio.CharBuffer;
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * Internal encoding of the generics signature attribute as defined by JVMS 7 $ 4.3.4.
+ * <pre>
+ * ClassSignature ::=
+ *     FormalTypeParameters? SuperclassSignature SuperinterfaceSignature*
+ *
+ *
+ * FormalTypeParameters ::=
+ *     < FormalTypeParameter+ >
+ *
+ * FormalTypeParameter ::=
+ *     Identifier ClassBound InterfaceBound*
+ *
+ * ClassBound ::=
+ *     : FieldTypeSignature?
+ *
+ * InterfaceBound ::=
+ *     : FieldTypeSignature
+ *
+ * SuperclassSignature ::=
+ *     ClassTypeSignature
+ *
+ * SuperinterfaceSignature ::=
+ *     ClassTypeSignature
+ *
+ *
+ * FieldTypeSignature ::=
+ *     ClassTypeSignature
+ *     ArrayTypeSignature
+ *     TypeVariableSignature
+ *
+ *
+ * ClassTypeSignature ::=
+ *     L PackageSpecifier? SimpleClassTypeSignature ClassTypeSignatureSuffix* ;
+ *
+ * PackageSpecifier ::=
+ *     Identifier / PackageSpecifier*
+ *
+ * SimpleClassTypeSignature ::=
+ *     Identifier TypeArguments?
+ *
+ * ClassTypeSignatureSuffix ::=
+ *     . SimpleClassTypeSignature
+ *
+ * TypeVariableSignature ::=
+ *     T Identifier ;
+ *
+ * TypeArguments ::=
+ *     < TypeArgument+ >
+ *
+ * TypeArgument ::=
+ *     WildcardIndicator? FieldTypeSignature
+ *     *
+ *
+ * WildcardIndicator ::=
+ *     +
+ *     -
+ *
+ * ArrayTypeSignature ::=
+ *     [ TypeSignature
+ *
+ * TypeSignature ::=
+ *     FieldTypeSignature
+ *     BaseType
+ *
+ *
+ * MethodTypeSignature ::=
+ *     FormalTypeParameters? (TypeSignature*) ReturnType ThrowsSignature*
+ *
+ * ReturnType ::=
+ *     TypeSignature
+ *     VoidDescriptor
+ *
+ * ThrowsSignature ::=
+ *     ^ ClassTypeSignature
+ *     ^ TypeVariableSignature
+ * </pre>
+ */
+public class GenericSignature {
+
+  interface DexDefinitionSignature<T extends DexDefinition> {
+    default boolean isClassSignature() {
+      return false;
+    }
+
+    default ClassSignature asClassSignature() {
+      return null;
+    }
+
+    default boolean isFieldTypeSignature() {
+      return false;
+    }
+
+    default FieldTypeSignature asFieldTypeSignature() {
+      return null;
+    }
+
+    default boolean isMethodTypeSignature() {
+      return false;
+    }
+
+    default MethodTypeSignature asMethodTypeSignature() {
+      return null;
+    }
+  }
+
+  public static class ClassSignature implements DexDefinitionSignature<DexClass> {
+    static final ClassSignature UNKNOWN_CLASS_SIGNATURE =
+        new ClassSignature(ClassTypeSignature.UNKNOWN_CLASS_TYPE_SIGNATURE, ImmutableList.of());
+
+    // TODO(b/129925954): encoding formal type parameters
+    final ClassTypeSignature superClassSignature;
+    final List<ClassTypeSignature> superInterfaceSignatures;
+
+    ClassSignature(
+        ClassTypeSignature superClassSignature,
+        List<ClassTypeSignature> superInterfaceSignatures) {
+      this.superClassSignature = superClassSignature;
+      this.superInterfaceSignatures = superInterfaceSignatures;
+    }
+
+    public ClassTypeSignature superClassSignature() {
+      return superClassSignature;
+    }
+
+    public List<ClassTypeSignature> superInterfaceSignatures() {
+      return superInterfaceSignatures;
+    }
+
+    @Override
+    public boolean isClassSignature() {
+      return true;
+    }
+
+    @Override
+    public ClassSignature asClassSignature() {
+      return this;
+    }
+  }
+
+  public abstract static class TypeSignature {
+    public boolean isFieldTypeSignature() {
+      return false;
+    }
+
+    public FieldTypeSignature asFieldTypeSignature() {
+      return null;
+    }
+
+    public boolean isBaseTypeSignature() {
+      return false;
+    }
+
+    public BaseTypeSignature asBaseTypeSignature() {
+      return null;
+    }
+
+    public abstract TypeSignature toArrayTypeSignature(AppView<?> appView);
+
+    public abstract TypeSignature toArrayElementTypeSignature(AppView<?> appView);
+  }
+
+  // TODO(b/129925954): better structures for a circle of
+  //  TypeSignature - FieldTypeSignature - ClassTypeSignature - TypeArgument
+  public abstract static class FieldTypeSignature
+      extends TypeSignature implements DexDefinitionSignature<DexEncodedField> {
+    @Override
+    public boolean isFieldTypeSignature() {
+      return true;
+    }
+
+    @Override
+    public FieldTypeSignature asFieldTypeSignature() {
+      return this;
+    }
+
+    public boolean isClassTypeSignature() {
+      return false;
+    }
+
+    public ClassTypeSignature asClassTypeSignature() {
+      return null;
+    }
+
+    public boolean isTypeVariableSignature() {
+      return false;
+    }
+
+    public TypeVariableSignature asTypeVariableSignature() {
+      return null;
+    }
+  }
+
+  // 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());
+
+    // 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>>.
+    // TODO(b/129925954): What about * ?
+    final List<FieldTypeSignature> typeArguments;
+
+    // TODO(b/129925954): towards immutable structure?
+    // Double-linked enclosing-inner relations.
+    ClassTypeSignature enclosingTypeSignature;
+    ClassTypeSignature innerTypeSignature;
+
+    ClassTypeSignature(DexType type, List<FieldTypeSignature> typeArguments) {
+      this.type = type;
+      this.typeArguments = typeArguments;
+    }
+
+    public DexType type() {
+      return type;
+    }
+
+    public List<FieldTypeSignature> typeArguments() {
+      return typeArguments;
+    }
+
+    @Override
+    public boolean isClassTypeSignature() {
+      return true;
+    }
+
+    @Override
+    public ClassTypeSignature asClassTypeSignature() {
+      return this;
+    }
+
+    @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;
+    }
+
+    static void link(ClassTypeSignature outer, ClassTypeSignature inner) {
+      assert outer.innerTypeSignature == null && inner.enclosingTypeSignature == null;
+      outer.innerTypeSignature = inner;
+      inner.enclosingTypeSignature = outer;
+    }
+  }
+
+  public static class TypeVariableSignature extends FieldTypeSignature {
+    final String typeVariable;
+
+    TypeVariableSignature(String typeVariable) {
+      this.typeVariable = typeVariable;
+    }
+
+    @Override
+    public boolean isTypeVariableSignature() {
+      return true;
+    }
+
+    @Override
+    public TypeVariableSignature asTypeVariableSignature() {
+      return this;
+    }
+
+    @Override
+    public TypeSignature toArrayTypeSignature(AppView<?> appView) {
+      throw new Unimplemented("TypeVariableSignature::toArrayTypeSignature");
+    }
+
+    @Override
+    public TypeSignature toArrayElementTypeSignature(AppView<?> appView) {
+      throw new Unimplemented("TypeVariableSignature::toArrayElementTypeSignature");
+    }
+  }
+
+  // TODO(b/129925954): Canonicalization?
+  public static class BaseTypeSignature extends TypeSignature {
+    final DexType type;
+
+    BaseTypeSignature(DexType type) {
+      assert type.isPrimitiveType() || type.isPrimitiveArrayType()
+              || type.isVoidType()
+          : type.toDescriptorString();
+      this.type = type;
+    }
+
+    @Override
+    public boolean isBaseTypeSignature() {
+      return true;
+    }
+
+    @Override
+    public BaseTypeSignature asBaseTypeSignature() {
+      return this;
+    }
+
+    @Override
+    public BaseTypeSignature toArrayTypeSignature(AppView<?> appView) {
+      assert !type.isVoidType();
+      DexType arrayType = type.toArrayType(1, appView.dexItemFactory());
+      return new BaseTypeSignature(arrayType);
+    }
+
+    @Override
+    public BaseTypeSignature toArrayElementTypeSignature(AppView<?> appView) {
+      assert type.isPrimitiveArrayType();
+      DexType elementType = type.toArrayElementType(appView.dexItemFactory());
+      return new BaseTypeSignature(elementType);
+    }
+  }
+
+  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());
+
+    // TODO(b/129925954): encoding formal type parameters
+    final List<TypeSignature> typeSignatures;
+    final TypeSignature returnType;
+    final List<TypeSignature> throwsSignatures;
+
+    MethodTypeSignature(
+        List<TypeSignature> typeSignatures,
+        TypeSignature returnType,
+        List<TypeSignature> throwsSignatures) {
+      this.typeSignatures = typeSignatures;
+      this.returnType = returnType;
+      this.throwsSignatures = throwsSignatures;
+    }
+
+    public TypeSignature getParameterTypeSignature(int i) {
+      if (typeSignatures.isEmpty() || i < 0 || i >= typeSignatures.size()) {
+        return null;
+      }
+      return typeSignatures.get(i);
+    }
+
+    public TypeSignature returnType() {
+      return returnType;
+    }
+
+    public List<TypeSignature> throwsSignatures() {
+      return throwsSignatures;
+    }
+
+    @Override
+    public boolean isMethodTypeSignature() {
+      return true;
+    }
+
+    @Override
+    public MethodTypeSignature asMethodTypeSignature() {
+      return this;
+    }
+  }
+
+  enum Kind {
+    CLASS, FIELD, METHOD;
+
+    static Kind fromDexDefinition(DexDefinition definition) {
+      if (definition.isDexClass()) {
+        return CLASS;
+      }
+      if (definition.isDexEncodedField()) {
+        return FIELD;
+      }
+      if (definition.isDexEncodedMethod()) {
+        return METHOD;
+      }
+      throw new Unreachable("Unexpected kind of DexDefinition: " + definition);
+    }
+
+    Function<String, ? extends DexDefinitionSignature<? extends DexDefinition>>
+        parserMethod(Parser parser) {
+      switch (this) {
+        case CLASS:
+          return parser::parseClassSignature;
+        case FIELD:
+          return parser::parseFieldTypeSignature;
+        case METHOD:
+          return parser::parseMethodTypeSignature;
+      }
+      throw new Unreachable("Unexpected kind: " + this);
+    }
+  }
+
+  public static class Parser {
+    // TODO(b/129925954): Can we merge variants of to*Signature below and just expose
+    //  type-parameterized version of this, like
+    //    <T extends DexDefinitionSignature<?>> T toGenericSignature
+    //  without unchecked cast?
+    private static DexDefinitionSignature<? extends DexDefinition> toGenericSignature(
+        DexClass currentClassContext,
+        DexDefinition definition,
+        AppView<AppInfoWithLiveness> appView) {
+      DexAnnotationSet annotations = definition.annotations();
+      if (annotations.annotations.length == 0) {
+        return null;
+      }
+      for (int i = 0; i < annotations.annotations.length; i++) {
+        DexAnnotation annotation = annotations.annotations[i];
+        if (!DexAnnotation.isSignatureAnnotation(annotation, appView.dexItemFactory())) {
+          continue;
+        }
+        Kind kind = Kind.fromDexDefinition(definition);
+        Parser parser = new Parser(currentClassContext, appView);
+        String signature = DexAnnotation.getSignature(annotation);
+        try {
+          return kind.parserMethod(parser).apply(signature);
+        } catch (GenericSignatureFormatError e) {
+          appView.options().warningInvalidSignature(
+              definition, currentClassContext.getOrigin(), signature, e);
+        }
+      }
+      return null;
+    }
+
+    public static ClassSignature toClassSignature(
+        DexClass clazz, AppView<AppInfoWithLiveness> appView) {
+      DexDefinitionSignature<?> signature = toGenericSignature(clazz, clazz, appView);
+      if (signature != null) {
+        assert signature.isClassSignature();
+        return signature.asClassSignature();
+      }
+      return ClassSignature.UNKNOWN_CLASS_SIGNATURE;
+    }
+
+    public static FieldTypeSignature toFieldTypeSignature(
+        DexEncodedField field, AppView<AppInfoWithLiveness> appView) {
+      DexClass currentClassContext = appView.definitionFor(field.field.holder);
+      DexDefinitionSignature<?> signature =
+          toGenericSignature(currentClassContext, field, appView);
+      if (signature != null) {
+        assert signature.isFieldTypeSignature();
+        return signature.asFieldTypeSignature();
+      }
+      return ClassTypeSignature.UNKNOWN_CLASS_TYPE_SIGNATURE;
+    }
+
+    public static MethodTypeSignature toMethodTypeSignature(
+        DexEncodedMethod method, AppView<AppInfoWithLiveness> appView) {
+      DexClass currentClassContext = appView.definitionFor(method.method.holder);
+      DexDefinitionSignature<?> signature =
+          toGenericSignature(currentClassContext, method, appView);
+      if (signature != null) {
+        assert signature.isMethodTypeSignature();
+        return signature.asMethodTypeSignature();
+      }
+      return MethodTypeSignature.UNKNOWN_METHOD_TYPE_SIGNATURE;
+    }
+
+    /*
+     * Parser:
+     */
+    private char symbol; // 0: eof; else valid term symbol or first char of identifier.
+
+    private String identifier;
+
+    /*
+     * Scanner:
+     * eof is private to the scan methods
+     * and it's set only when a scan is issued at the end of the buffer.
+     */
+    private boolean eof;
+
+    private char[] buffer;
+
+    private int pos;
+
+    private Parser(DexClass currentClassContext, AppView<AppInfoWithLiveness> appView) {
+      this.currentClassContext = currentClassContext;
+      this.appView = appView;
+    }
+
+    ClassSignature parseClassSignature(String signature) {
+      try {
+        setInput(signature);
+        return parseClassSignature();
+      } catch (Unimplemented e) {
+        // TODO(b/129925954): Should not catch this once fully implemented
+        return ClassSignature.UNKNOWN_CLASS_SIGNATURE;
+      } catch (GenericSignatureFormatError e) {
+        throw e;
+      } catch (Throwable t) {
+        Error e = new GenericSignatureFormatError(
+            "Unknown error parsing class signature: " + t.getMessage());
+        e.addSuppressed(t);
+        throw e;
+      }
+    }
+
+    MethodTypeSignature parseMethodTypeSignature(String signature) {
+      try {
+        setInput(signature);
+        return parseMethodTypeSignature();
+      } catch (Unimplemented e) {
+        // TODO(b/129925954): Should not catch this once fully implemented
+        return MethodTypeSignature.UNKNOWN_METHOD_TYPE_SIGNATURE;
+      } catch (GenericSignatureFormatError e) {
+        throw e;
+      } catch (Throwable t) {
+        Error e = new GenericSignatureFormatError(
+            "Unknown error parsing method signature: " + t.getMessage());
+        e.addSuppressed(t);
+        throw e;
+      }
+    }
+
+    FieldTypeSignature parseFieldTypeSignature(String signature) {
+      try {
+        setInput(signature);
+        return parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION);
+      } catch (Unimplemented e) {
+        // TODO(b/129925954): Should not catch this once fully implemented
+        return ClassTypeSignature.UNKNOWN_CLASS_TYPE_SIGNATURE;
+      } catch (GenericSignatureFormatError e) {
+        throw e;
+      } catch (Throwable t) {
+        Error e = new GenericSignatureFormatError(
+            "Unknown error parsing field signature: " + t.getMessage());
+        e.addSuppressed(t);
+        throw e;
+      }
+    }
+
+    private void setInput(String input) {
+      this.buffer = input.toCharArray();
+      this.eof = false;
+      pos = 0;
+      symbol = 0;
+      identifier = null;
+      scanSymbol();
+    }
+
+    //
+    // Action:
+    //
+
+    enum ParserPosition {
+      CLASS_SUPER_OR_INTERFACE_ANNOTATION,
+      ENCLOSING_INNER_OR_TYPE_ANNOTATION,
+      MEMBER_ANNOTATION
+    }
+
+    private final AppView<AppInfoWithLiveness> appView;
+    private final DexClass currentClassContext;
+    private DexType lastWrittenType = null;
+
+    private DexType parsedTypeName(String name, ParserPosition parserPosition) {
+      if (parserPosition == ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION
+          && lastWrittenType == null) {
+        // We are writing type-arguments for a merged class.
+        return null;
+      }
+      String originalDescriptor = getDescriptorFromClassBinaryName(name);
+      DexType type =
+          appView.graphLense().lookupType(appView.dexItemFactory().createType(originalDescriptor));
+      if (appView.appInfo().wasPruned(type)) {
+        type = appView.dexItemFactory().objectType;
+      }
+      if (parserPosition == ParserPosition.CLASS_SUPER_OR_INTERFACE_ANNOTATION
+          && currentClassContext != null) {
+        // We may have merged the type down to the current class type.
+        DexString classDescriptor = currentClassContext.type.descriptor;
+        if (!originalDescriptor.equals(classDescriptor.toString())
+            && type.descriptor.equals(classDescriptor)) {
+          lastWrittenType = null;
+          return type;
+        }
+      }
+      lastWrittenType = type;
+      return type;
+    }
+
+    private DexType parsedInnerTypeName(DexType enclosingType, String name) {
+      if (enclosingType == null) {
+        // We are writing inner type names
+        return null;
+      }
+      assert enclosingType.isClassType();
+      String enclosingDescriptor = enclosingType.toDescriptorString();
+      DexType type =
+          appView
+              .dexItemFactory()
+              .createType(
+                  getDescriptorFromClassBinaryName(
+                      getClassBinaryNameFromDescriptor(enclosingDescriptor)
+                          + DescriptorUtils.INNER_CLASS_SEPARATOR
+                          + name));
+      return appView.graphLense().lookupType(type);
+    }
+
+    //
+    // Parser:
+    //
+
+    private ClassSignature parseClassSignature() {
+      // ClassSignature ::= FormalTypeParameters? SuperclassSignature SuperinterfaceSignature*.
+
+      parseOptFormalTypeParameters();
+
+      // SuperclassSignature ::= ClassTypeSignature.
+      ClassTypeSignature superClassSignature =
+          parseClassTypeSignature(ParserPosition.CLASS_SUPER_OR_INTERFACE_ANNOTATION);
+
+      ImmutableList.Builder<ClassTypeSignature> builder = ImmutableList.builder();
+      while (symbol > 0) {
+        // SuperinterfaceSignature ::= ClassTypeSignature.
+        builder.add(parseClassTypeSignature(ParserPosition.CLASS_SUPER_OR_INTERFACE_ANNOTATION));
+      }
+
+      return new ClassSignature(superClassSignature, builder.build());
+    }
+
+    private void parseOptFormalTypeParameters() {
+      // FormalTypeParameters ::= "<" FormalTypeParameter+ ">".
+
+      if (symbol == '<') {
+        scanSymbol();
+
+        updateFormalTypeParameter();
+
+        while ((symbol != '>') && (symbol > 0)) {
+          updateFormalTypeParameter();
+        }
+
+        expect('>');
+      }
+    }
+
+    private void updateFormalTypeParameter() {
+      // FormalTypeParameter ::= Identifier ClassBound InterfaceBound*.
+      scanIdentifier();
+      assert identifier != null;
+
+      // ClassBound ::= ":" FieldTypeSignature?.
+      expect(':');
+
+      if (symbol == 'L' || symbol == '[' || symbol == 'T') {
+        parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION);
+      }
+
+      while (symbol == ':') {
+        // InterfaceBound ::= ":" FieldTypeSignature.
+        scanSymbol();
+        parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION);
+      }
+    }
+
+    private FieldTypeSignature parseFieldTypeSignature(ParserPosition parserPosition) {
+      // FieldTypeSignature ::= ClassTypeSignature | ArrayTypeSignature | TypeVariableSignature.
+      switch (symbol) {
+        case 'L':
+          return parseClassTypeSignature(parserPosition);
+        case '[':
+          // ArrayTypeSignature ::= "[" TypeSignature.
+          scanSymbol();
+          TypeSignature baseTypeSignature = updateTypeSignature(parserPosition);
+          return baseTypeSignature.toArrayTypeSignature(appView).asFieldTypeSignature();
+        case 'T':
+          return updateTypeVariableSignature();
+        default:
+          parseError("Expected L, [ or T", pos);
+      }
+      throw new Unreachable("Either FieldTypeSignature is returned or a parse error is thrown.");
+    }
+
+    private ClassTypeSignature parseClassTypeSignature(ParserPosition parserPosition) {
+      // ClassTypeSignature ::=
+      //   "L" (Identifier "/")* Identifier TypeArguments? ("." Identifier TypeArguments?)* ";".
+      expect('L');
+
+      StringBuilder qualIdent = new StringBuilder();
+      scanIdentifier();
+      assert identifier != null;
+      while (symbol == '/') {
+        qualIdent.append(identifier).append(symbol);
+        scanSymbol();
+        scanIdentifier();
+        assert identifier != null;
+      }
+
+      qualIdent.append(this.identifier);
+      DexType parsedEnclosingType = parsedTypeName(qualIdent.toString(), parserPosition);
+
+      List<FieldTypeSignature> typeArguments = updateOptTypeArguments();
+      ClassTypeSignature outerMostTypeSignature =
+          new ClassTypeSignature(parsedEnclosingType, typeArguments);
+
+      ClassTypeSignature outerTypeSignature = outerMostTypeSignature;
+      ClassTypeSignature innerTypeSignature;
+      while (symbol == '.') {
+        // Deal with Member Classes.
+        scanSymbol();
+        scanIdentifier();
+        assert identifier != null;
+        parsedEnclosingType = parsedInnerTypeName(parsedEnclosingType, identifier);
+        typeArguments = updateOptTypeArguments();
+        innerTypeSignature = new ClassTypeSignature(parsedEnclosingType, typeArguments);
+        ClassTypeSignature.link(outerTypeSignature, innerTypeSignature);
+        outerTypeSignature = innerTypeSignature;
+      }
+
+      expect(';');
+      return outerMostTypeSignature;
+    }
+
+    private List<FieldTypeSignature> updateOptTypeArguments() {
+      ImmutableList.Builder<FieldTypeSignature> builder = ImmutableList.builder();
+      // OptTypeArguments ::= "<" TypeArgument+ ">".
+      if (symbol == '<') {
+        scanSymbol();
+
+        builder.add(updateTypeArgument());
+        while ((symbol != '>') && (symbol > 0)) {
+          builder.add(updateTypeArgument());
+        }
+
+        expect('>');
+      }
+      return builder.build();
+    }
+
+    private FieldTypeSignature updateTypeArgument() {
+      // TypeArgument ::= (["+" | "-"] FieldTypeSignature) | "*".
+      if (symbol == '*') {
+        scanSymbol();
+        throw new Unimplemented("GenericSignature.TypeArgument *");
+      } else if (symbol == '+') {
+        scanSymbol();
+        return parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION);
+      } else if (symbol == '-') {
+        scanSymbol();
+        return parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION);
+      } else {
+        return parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION);
+      }
+    }
+
+    private TypeVariableSignature updateTypeVariableSignature() {
+      // TypeVariableSignature ::= "T" Identifier ";".
+      expect('T');
+
+      scanIdentifier();
+      assert identifier != null;
+
+      expect(';');
+      return new TypeVariableSignature(identifier);
+    }
+
+    private TypeSignature updateTypeSignature(ParserPosition parserPosition) {
+      switch (symbol) {
+        case 'B':
+        case 'C':
+        case 'D':
+        case 'F':
+        case 'I':
+        case 'J':
+        case 'S':
+        case 'Z':
+          DexType type = appView.dexItemFactory().createType(String.valueOf(symbol));
+          BaseTypeSignature baseTypeSignature = new BaseTypeSignature(type);
+          scanSymbol();
+          return baseTypeSignature;
+        default:
+          // Not an elementary type, but a FieldTypeSignature.
+          return parseFieldTypeSignature(parserPosition);
+      }
+    }
+
+    private MethodTypeSignature parseMethodTypeSignature() {
+      // MethodTypeSignature ::=
+      //     FormalTypeParameters? "(" TypeSignature* ")" ReturnType ThrowsSignature*.
+      parseOptFormalTypeParameters();
+
+      expect('(');
+
+      ImmutableList.Builder<TypeSignature> parameterSignatureBuilder = ImmutableList.builder();
+      while (symbol != ')' && (symbol > 0)) {
+        parameterSignatureBuilder.add(updateTypeSignature(ParserPosition.MEMBER_ANNOTATION));
+      }
+
+      expect(')');
+
+      TypeSignature returnType = updateReturnType();
+
+      ImmutableList.Builder<TypeSignature> throwsSignatureBuilder = ImmutableList.builder();
+      if (symbol == '^') {
+        do {
+          scanSymbol();
+
+          // ThrowsSignature ::= ("^" ClassTypeSignature) | ("^" TypeVariableSignature).
+          if (symbol == 'T') {
+            throwsSignatureBuilder.add(updateTypeVariableSignature());
+          } else {
+            throwsSignatureBuilder.add(parseClassTypeSignature(ParserPosition.MEMBER_ANNOTATION));
+          }
+        } while (symbol == '^');
+      }
+
+      return new MethodTypeSignature(
+          parameterSignatureBuilder.build(), returnType, throwsSignatureBuilder.build());
+    }
+
+    private TypeSignature updateReturnType() {
+      // ReturnType ::= TypeSignature | "V".
+      if (symbol != 'V') {
+        return updateTypeSignature(ParserPosition.MEMBER_ANNOTATION);
+      } else {
+        scanSymbol();
+        return new BaseTypeSignature(appView.dexItemFactory().voidType);
+      }
+    }
+
+    //
+    // Scanner:
+    //
+
+    private void scanSymbol() {
+      if (!eof) {
+        assert buffer != null;
+        if (pos < buffer.length) {
+          symbol = buffer[pos];
+          pos++;
+        } else {
+          symbol = 0;
+          eof = true;
+        }
+      } else {
+        parseError("Unexpected end of signature", pos);
+      }
+    }
+
+    private void expect(char c) {
+      if (eof) {
+        parseError("Unexpected end of signature", pos);
+      }
+      if (symbol == c) {
+        scanSymbol();
+      } else {
+        parseError("Expected " + c, pos - 1);
+      }
+    }
+
+    private boolean isStopSymbol(char ch) {
+      switch (ch) {
+        case ':':
+        case '/':
+        case ';':
+        case '<':
+        case '.':
+          return true;
+        default:
+          return false;
+      }
+    }
+
+    // PRE: symbol is the first char of the identifier.
+    // POST: symbol = the next symbol AFTER the identifier.
+    private void scanIdentifier() {
+      if (!eof && pos < buffer.length) {
+        StringBuilder identifierBuilder = new StringBuilder(32);
+        if (!isStopSymbol(symbol)) {
+          identifierBuilder.append(symbol);
+
+          char[] bufferLocal = buffer;
+          assert bufferLocal != null;
+          do {
+            char ch = bufferLocal[pos];
+            if (((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z'))
+                || !isStopSymbol(ch)) {
+              identifierBuilder.append(bufferLocal[pos]);
+              pos++;
+            } else {
+              identifier = identifierBuilder.toString();
+              scanSymbol();
+              return;
+            }
+          } while (pos != bufferLocal.length);
+          identifier = identifierBuilder.toString();
+          symbol = 0;
+          eof = true;
+        } else {
+          // Identifier starts with incorrect char.
+          symbol = 0;
+          eof = true;
+          parseError();
+        }
+      } else {
+        parseError("Unexpected end of signature", pos);
+      }
+    }
+
+    private void parseError() {
+      parseError("Unexpected", pos);
+    }
+
+    private void parseError(String message, int pos) {
+      String arrow = CharBuffer.allocate(pos).toString().replace('\0', ' ') + '^';
+      throw new GenericSignatureFormatError(
+          message + " at position " + (pos + 1) + System.lineSeparator()
+              + String.valueOf(buffer) + System.lineSeparator()
+              + arrow);
+    }
+  }
+}
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 e01f0ee..8588a9d 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -322,10 +322,8 @@
 
   public static <T extends DexReference, S> ImmutableMap<T, S> rewriteReferenceKeys(
       Map<T, S> original, Function<T, T> rewrite) {
-    ImmutableMap.Builder<T, S> builder = new ImmutableMap.Builder<>();
-    for (T item : original.keySet()) {
-      builder.put(rewrite.apply(item), original.get(item));
-    }
+    ImmutableMap.Builder<T, S> builder = ImmutableMap.builder();
+    original.forEach((item, value) -> builder.put(rewrite.apply(item), value));
     return builder.build();
   }
 
@@ -476,6 +474,7 @@
   // This lens clears all code rewriting (lookup methods mimics identity lens behavior) but still
   // relies on the previous lens for names (getRenamed/Original methods).
   public static class ClearCodeRewritingGraphLens extends IdentityGraphLense {
+
     private final GraphLense previous;
 
     public ClearCodeRewritingGraphLens(GraphLense previous) {
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 41499ea..597e6e0 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.cf.code.CfJsrRet;
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.cf.code.CfLogicalBinop;
@@ -148,12 +149,30 @@
     return code;
   }
 
+  public static class DebugParsingOptions {
+    public final boolean lineInfo;
+    public final boolean localInfo;
+    public final int asmReaderOptions;
+
+    public DebugParsingOptions(boolean lineInfo, boolean localInfo, int asmReaderOptions) {
+      this.lineInfo = lineInfo;
+      this.localInfo = localInfo;
+      this.asmReaderOptions = asmReaderOptions;
+    }
+  }
+
   public void parseCode(ReparseContext context, boolean useJsrInliner) {
-    int parsingOptions = getParsingOptions(application, reachabilitySensitive);
+    DebugParsingOptions parsingOptions = getParsingOptions(application, reachabilitySensitive);
+
     ClassCodeVisitor classVisitor =
         new ClassCodeVisitor(
-            context.owner, createCodeLocator(context), application, useJsrInliner, origin);
-    new ClassReader(context.classCache).accept(classVisitor, parsingOptions);
+            context.owner,
+            createCodeLocator(context),
+            application,
+            useJsrInliner,
+            origin,
+            parsingOptions);
+    new ClassReader(context.classCache).accept(classVisitor, parsingOptions.asmReaderOptions);
   }
 
   private void setCode(CfCode code) {
@@ -261,19 +280,22 @@
     private final JarApplicationReader application;
     private boolean usrJsrInliner;
     private final Origin origin;
+    private final DebugParsingOptions debugParsingOptions;
 
     ClassCodeVisitor(
         DexClass clazz,
         BiFunction<String, String, LazyCfCode> codeLocator,
         JarApplicationReader application,
         boolean useJsrInliner,
-        Origin origin) {
+        Origin origin,
+        DebugParsingOptions debugParsingOptions) {
       super(InternalOptions.ASM_VERSION);
       this.clazz = clazz;
       this.codeLocator = codeLocator;
       this.application = application;
       this.usrJsrInliner = useJsrInliner;
       this.origin = origin;
+      this.debugParsingOptions = debugParsingOptions;
     }
 
     @Override
@@ -285,7 +307,7 @@
         if (code != null) {
           DexMethod method = application.getMethod(clazz.type, name, desc);
           MethodCodeVisitor methodVisitor =
-              new MethodCodeVisitor(application, method, code, origin);
+              new MethodCodeVisitor(application, method, code, origin, debugParsingOptions);
           if (!usrJsrInliner) {
             return methodVisitor;
           }
@@ -299,6 +321,7 @@
   private static class MethodCodeVisitor extends MethodVisitor {
     private final JarApplicationReader application;
     private final DexItemFactory factory;
+    private final DebugParsingOptions debugParsingOptions;
     private int maxStack;
     private int maxLocals;
     private List<CfInstruction> instructions;
@@ -311,8 +334,13 @@
     private final Origin origin;
 
     MethodCodeVisitor(
-        JarApplicationReader application, DexMethod method, LazyCfCode code, Origin origin) {
+        JarApplicationReader application,
+        DexMethod method,
+        LazyCfCode code,
+        Origin origin,
+        DebugParsingOptions debugParsingOptions) {
       super(InternalOptions.ASM_VERSION);
+      this.debugParsingOptions = debugParsingOptions;
       assert code != null;
       this.application = application;
       this.factory = application.getFactory();
@@ -680,7 +708,10 @@
           type = ValueType.OBJECT;
           break;
         case Opcodes.RET:
-          throw new JsrEncountered("RET should be handled by the ASM jsr inliner");
+          {
+            instructions.add(new CfJsrRet(var));
+            return;
+          }
         default:
           throw new Unreachable("Unexpected VarInsn opcode: " + opcode);
       }
@@ -917,14 +948,16 @@
     @Override
     public void visitLocalVariable(
         String name, String desc, String signature, Label start, Label end, int index) {
-      DebugLocalInfo debugLocalInfo =
-          canonicalize(
-              new DebugLocalInfo(
-                  factory.createString(name),
-                  factory.createType(desc),
-                  signature == null ? null : factory.createString(signature)));
-      localVariables.add(
-          new LocalVariableInfo(index, debugLocalInfo, getLabel(start), getLabel(end)));
+      if (debugParsingOptions.localInfo) {
+        DebugLocalInfo debugLocalInfo =
+            canonicalize(
+                new DebugLocalInfo(
+                    factory.createString(name),
+                    factory.createType(desc),
+                    signature == null ? null : factory.createString(signature)));
+        localVariables.add(
+            new LocalVariableInfo(index, debugLocalInfo, getLabel(start), getLabel(end)));
+      }
     }
 
     private DebugLocalInfo canonicalize(DebugLocalInfo debugLocalInfo) {
@@ -933,7 +966,9 @@
 
     @Override
     public void visitLineNumber(int line, Label start) {
-      instructions.add(new CfPosition(getLabel(start), new Position(line, null, method, null)));
+      if (debugParsingOptions.lineInfo) {
+        instructions.add(new CfPosition(getLabel(start), new Position(line, null, method, null)));
+      }
     }
 
     @Override
@@ -945,7 +980,7 @@
     }
   }
 
-  private static int getParsingOptions(
+  private static DebugParsingOptions getParsingOptions(
       JarApplicationReader application, boolean reachabilitySensitive) {
     int parsingOptions =
         application.options.testing.readInputStackMaps
@@ -953,18 +988,24 @@
             : ClassReader.SKIP_FRAMES;
 
     ProguardConfiguration configuration = application.options.getProguardConfiguration();
-    if (configuration != null && !configuration.isKeepParameterNames()) {
-      ProguardKeepAttributes keep =
-          application.options.getProguardConfiguration().getKeepAttributes();
-      if (!application.options.getProguardConfiguration().isKeepParameterNames()
-          && !keep.localVariableTable
-          && !keep.localVariableTypeTable
-          && !keep.lineNumberTable
-          && !reachabilitySensitive) {
-        parsingOptions |= ClassReader.SKIP_DEBUG;
-      }
+    if (configuration == null) {
+      return new DebugParsingOptions(true, true, parsingOptions);
     }
-    return parsingOptions;
+    ProguardKeepAttributes keep =
+        application.options.getProguardConfiguration().getKeepAttributes();
+
+    boolean localsInfo =
+        configuration.isKeepParameterNames()
+            || keep.localVariableTable
+            || keep.localVariableTypeTable
+            || reachabilitySensitive;
+    boolean lineInfo = keep.lineNumberTable;
+
+    if (!localsInfo && !lineInfo) {
+      parsingOptions |= ClassReader.SKIP_DEBUG;
+    }
+
+    return new DebugParsingOptions(lineInfo, localsInfo, parsingOptions);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java b/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java
new file mode 100644
index 0000000..bfc0eb4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java
@@ -0,0 +1,67 @@
+// 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.graph.LookupResult.LookupResultSuccess.LookupResultCollectionState;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+public class LookupCompletenessHelper {
+
+  private final PinnedPredicate pinnedPredicate;
+
+  private Set<DexType> pinnedInstantiations;
+
+  LookupCompletenessHelper(PinnedPredicate pinnedPredicate) {
+    this.pinnedPredicate = pinnedPredicate;
+  }
+
+  void checkClass(DexClass clazz) {
+    if (pinnedPredicate.isPinned(clazz.type)) {
+      if (pinnedInstantiations == null) {
+        pinnedInstantiations = Sets.newIdentityHashSet();
+      }
+      pinnedInstantiations.add(clazz.type);
+    }
+  }
+
+  LookupResultCollectionState computeCollectionState(
+      DexMethod method, AppView<? extends AppInfoWithClassHierarchy> appView) {
+    assert pinnedInstantiations == null || !pinnedInstantiations.isEmpty();
+    if (pinnedInstantiations == null) {
+      return LookupResultCollectionState.Complete;
+    }
+    WorkList<DexType> workList = WorkList.newIdentityWorkList(pinnedInstantiations);
+    while (workList.hasNext()) {
+      if (isMethodKeptInSuperTypeOrIsLibrary(workList, method, appView)) {
+        return LookupResultCollectionState.Incomplete;
+      }
+    }
+    return LookupResultCollectionState.Complete;
+  }
+
+  private boolean isMethodKeptInSuperTypeOrIsLibrary(
+      WorkList<DexType> workList,
+      DexMethod method,
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    while (workList.hasNext()) {
+      DexClass parent = appView.definitionFor(workList.next());
+      if (parent == null) {
+        continue;
+      }
+      DexEncodedMethod methodInClass = parent.lookupVirtualMethod(method);
+      if (methodInClass != null
+          && (parent.isNotProgramClass() || pinnedPredicate.isPinned(methodInClass.method))) {
+        return true;
+      }
+      if (parent.superType != null) {
+        workList.addIfNotSeen(parent.superType);
+      }
+      workList.addIfNotSeen(parent.interfaces.values);
+    }
+    return false;
+  }
+}
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 7a923de..fd71a8d 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupResult.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupResult.java
@@ -75,6 +75,10 @@
       return state == LookupResultCollectionState.Incomplete;
     }
 
+    public boolean isComplete() {
+      return state == LookupResultCollectionState.Complete;
+    }
+
     public enum LookupResultCollectionState {
       Complete,
       Incomplete,
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 af2bdd4..53510bd 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -141,8 +141,11 @@
         GraphLense lens) {
       objectAllocationInfos.classesWithAllocationSiteTracking.forEach(
           (clazz, allocationSitesForClass) -> {
-            DexProgramClass rewrittenClass =
-                asProgramClassOrNull(definitions.definitionFor(lens.lookupType(clazz.type)));
+            DexType type = lens.lookupType(clazz.type);
+            if (type.isPrimitiveType()) {
+              return;
+            }
+            DexProgramClass rewrittenClass = asProgramClassOrNull(definitions.definitionFor(type));
             assert rewrittenClass != null;
             assert !classesWithAllocationSiteTracking.containsKey(rewrittenClass);
             classesWithAllocationSiteTracking.put(
@@ -152,8 +155,11 @@
           });
       objectAllocationInfos.classesWithoutAllocationSiteTracking.forEach(
           clazz -> {
-            DexProgramClass rewrittenClass =
-                asProgramClassOrNull(definitions.definitionFor(lens.lookupType(clazz.type)));
+            DexType type = lens.lookupType(clazz.type);
+            if (type.isPrimitiveType()) {
+              return;
+            }
+            DexProgramClass rewrittenClass = asProgramClassOrNull(definitions.definitionFor(type));
             assert rewrittenClass != null;
             assert !classesWithAllocationSiteTracking.containsKey(rewrittenClass);
             assert !classesWithoutAllocationSiteTracking.contains(rewrittenClass);
diff --git a/src/main/java/com/android/tools/r8/graph/PinnedPredicate.java b/src/main/java/com/android/tools/r8/graph/PinnedPredicate.java
new file mode 100644
index 0000000..d81becc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/PinnedPredicate.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+@FunctionalInterface
+public interface PinnedPredicate {
+
+  boolean isPinned(DexReference reference);
+}
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 4c5e239..bb10b76 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -5,7 +5,9 @@
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.LookupResult.LookupResultSuccess.LookupResultCollectionState;
+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 java.util.Collection;
 import java.util.Collections;
@@ -79,16 +81,25 @@
   public abstract LookupResult lookupVirtualDispatchTargets(
       DexProgramClass context,
       AppView<? extends AppInfoWithClassHierarchy> appView,
-      InstantiatedSubTypeInfo instantiatedInfo);
+      InstantiatedSubTypeInfo instantiatedInfo,
+      PinnedPredicate pinnedPredicate);
 
   public final LookupResult lookupVirtualDispatchTargets(
       DexProgramClass context, AppView<AppInfoWithLiveness> appView) {
-    return lookupVirtualDispatchTargets(context, appView, appView.appInfo());
+    AppInfoWithLiveness appInfoWithLiveness = appView.appInfo();
+    return lookupVirtualDispatchTargets(
+        context, appView, appInfoWithLiveness, appInfoWithLiveness::isPinned);
   }
 
   public abstract DexClassAndMethod lookupVirtualDispatchTarget(
+      InstantiatedObject instance, AppView<? extends AppInfoWithClassHierarchy> appView);
+
+  public abstract DexClassAndMethod lookupVirtualDispatchTarget(
       DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView);
 
+  public abstract DexClassAndMethod lookupVirtualDispatchTarget(
+      LambdaDescriptor lambdaInstance, AppView<? extends AppInfoWithClassHierarchy> appView);
+
   /** Result for a resolution that succeeds with a known declaration/definition. */
   public static class SingleResolutionResult extends ResolutionResult {
     private final DexClass initialResolutionHolder;
@@ -323,7 +334,8 @@
     public LookupResult lookupVirtualDispatchTargets(
         DexProgramClass context,
         AppView<? extends AppInfoWithClassHierarchy> appView,
-        InstantiatedSubTypeInfo instantiatedInfo) {
+        InstantiatedSubTypeInfo instantiatedInfo,
+        PinnedPredicate pinnedPredicate) {
       // Check that the initial resolution holder is accessible from the context.
       assert appView.isSubtype(initialResolutionHolder.type, resolvedHolder.type).isTrue()
           : initialResolutionHolder.type + " is not a subtype of " + resolvedHolder.type;
@@ -334,14 +346,23 @@
         // If the resolved reference is private there is no dispatch.
         // This is assuming that the method is accessible, which implies self/nest access.
         // Only include if the target has code or is native.
+        boolean isIncomplete =
+            pinnedPredicate.isPinned(resolvedHolder.type)
+                && pinnedPredicate.isPinned(resolvedMethod.method);
         return LookupResult.createResult(
-            Collections.singleton(resolvedMethod), LookupResultCollectionState.Complete);
+            Collections.singleton(resolvedMethod),
+            isIncomplete
+                ? LookupResultCollectionState.Incomplete
+                : LookupResultCollectionState.Complete);
       }
       assert resolvedMethod.isNonPrivateVirtualMethod();
       Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
+      LookupCompletenessHelper incompleteness = new LookupCompletenessHelper(pinnedPredicate);
+      // TODO(b/150171154): Use instantiationHolder below.
       instantiatedInfo.forEachInstantiatedSubType(
           resolvedHolder.type,
           subClass -> {
+            incompleteness.checkClass(subClass);
             DexClassAndMethod dexClassAndMethod =
                 lookupVirtualDispatchTarget(subClass, appView, resolvedHolder.type);
             if (dexClassAndMethod != null) {
@@ -353,7 +374,8 @@
             // TODO(b/148769279): We need to look at the call site to see if it overrides
             //   the resolved method or not.
           });
-      return LookupResult.createResult(result, LookupResultCollectionState.Complete);
+      return LookupResult.createResult(
+          result, incompleteness.computeCollectionState(resolvedMethod.method, appView));
     }
 
     private static void addVirtualDispatchTarget(
@@ -404,10 +426,38 @@
      */
     @Override
     public DexClassAndMethod lookupVirtualDispatchTarget(
+        InstantiatedObject instance, AppView<? extends AppInfoWithClassHierarchy> appView) {
+      return instance.isClass()
+          ? lookupVirtualDispatchTarget(instance.asClass(), appView)
+          : lookupVirtualDispatchTarget(instance.asLambda(), appView);
+    }
+
+    @Override
+    public DexClassAndMethod lookupVirtualDispatchTarget(
         DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView) {
       return lookupVirtualDispatchTarget(dynamicInstance, appView, initialResolutionHolder.type);
     }
 
+    @Override
+    public DexClassAndMethod lookupVirtualDispatchTarget(
+        LambdaDescriptor lambdaInstance, AppView<? extends AppInfoWithClassHierarchy> appView) {
+      if (lambdaInstance.getMainMethod().match(resolvedMethod)) {
+        DexMethod method = lambdaInstance.implHandle.asMethod();
+        DexClass holder = appView.definitionFor(method.holder);
+        if (holder == null) {
+          assert false;
+          return null;
+        }
+        DexEncodedMethod encodedMethod = appView.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 lookupMaximallySpecificDispatchTarget(lambdaInstance, appView);
+    }
+
     private DexClassAndMethod lookupVirtualDispatchTarget(
         DexProgramClass dynamicInstance,
         AppView<? extends AppInfoWithClassHierarchy> appView,
@@ -452,6 +502,13 @@
           .lookupMaximallySpecificMethod(dynamicInstance, resolvedMethod.method);
     }
 
+    private DexClassAndMethod lookupMaximallySpecificDispatchTarget(
+        LambdaDescriptor lambdaDescriptor, AppView<? extends AppInfoWithClassHierarchy> appView) {
+      return appView
+          .appInfo()
+          .lookupMaximallySpecificMethod(lambdaDescriptor, resolvedMethod.method);
+    }
+
     /**
      * C contains a declaration for an instance method m that overrides (§5.4.5) the resolved
      * method, then m is the method to be invoked. If the candidate is not a valid override, we
@@ -541,15 +598,28 @@
     public LookupResult lookupVirtualDispatchTargets(
         DexProgramClass context,
         AppView<? extends AppInfoWithClassHierarchy> appView,
-        InstantiatedSubTypeInfo instantiatedInfo) {
+        InstantiatedSubTypeInfo instantiatedInfo,
+        PinnedPredicate pinnedPredicate) {
       return LookupResult.getIncompleteEmptyResult();
     }
 
     @Override
     public DexClassAndMethod lookupVirtualDispatchTarget(
+        InstantiatedObject instance, AppView<? extends AppInfoWithClassHierarchy> appView) {
+      return null;
+    }
+
+    @Override
+    public DexClassAndMethod lookupVirtualDispatchTarget(
         DexProgramClass dynamicInstance, AppView<? extends AppInfoWithClassHierarchy> appView) {
       return null;
     }
+
+    @Override
+    public DexClassAndMethod lookupVirtualDispatchTarget(
+        LambdaDescriptor lambdaInstance, AppView<? extends AppInfoWithClassHierarchy> appView) {
+      return null;
+    }
   }
 
   /** Singleton result for the special case resolving the array clone() method. */
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
index 3beb9ba..663524d 100644
--- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
+++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
@@ -4,18 +4,63 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceLinkedOpenHashMap;
+import com.google.common.collect.Ordering;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceRBTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import it.unimi.dsi.fastutil.ints.IntBidirectionalIterator;
 import java.util.function.Consumer;
 
 public class RewrittenPrototypeDescription {
 
-  public static class RemovedArgumentInfo {
+  public interface ArgumentInfo {
+
+    @SuppressWarnings("ConstantConditions")
+    static ArgumentInfo combine(ArgumentInfo arg1, ArgumentInfo arg2) {
+      if (arg1 == null) {
+        assert arg2 != null;
+        return arg2;
+      }
+      if (arg2 == null) {
+        assert arg1 != null;
+        return arg1;
+      }
+      return arg1.combine(arg2);
+    }
+
+    ArgumentInfo NO_INFO =
+        info -> {
+          assert false : "ArgumentInfo NO_INFO should not be combined";
+          return info;
+        };
+
+    default boolean isRemovedArgumentInfo() {
+      return false;
+    }
+
+    default RemovedArgumentInfo asRemovedArgumentInfo() {
+      return null;
+    }
+
+    default boolean isRewrittenTypeInfo() {
+      return false;
+    }
+
+    default RewrittenTypeInfo asRewrittenTypeInfo() {
+      return null;
+    }
+
+    // ArgumentInfo are combined with `this` first, and the `info` argument second.
+    ArgumentInfo combine(ArgumentInfo info);
+  }
+
+  public static class RemovedArgumentInfo implements ArgumentInfo {
 
     public static class Builder {
 
@@ -61,91 +106,223 @@
     public boolean isNeverUsed() {
       return !isAlwaysNull;
     }
+
+    @Override
+    public boolean isRemovedArgumentInfo() {
+      return true;
+    }
+
+    @Override
+    public RemovedArgumentInfo asRemovedArgumentInfo() {
+      return this;
+    }
+
+    @Override
+    public ArgumentInfo combine(ArgumentInfo info) {
+      assert false : "Once the argument is removed one cannot modify it any further.";
+      return this;
+    }
   }
 
-  public static class RemovedArgumentInfoCollection {
+  public static class RewrittenTypeInfo implements ArgumentInfo {
 
-    private static final RemovedArgumentInfoCollection EMPTY = new RemovedArgumentInfoCollection();
+    private final DexType oldType;
+    private final DexType newType;
 
-    private final Int2ReferenceSortedMap<RemovedArgumentInfo> removedArguments;
+    static RewrittenTypeInfo toVoid(DexType oldReturnType, AppView<?> appView) {
+      return new RewrittenTypeInfo(oldReturnType, appView.dexItemFactory().voidType);
+    }
+
+    public RewrittenTypeInfo(DexType oldType, DexType newType) {
+      this.oldType = oldType;
+      this.newType = newType;
+    }
+
+    public DexType getNewType() {
+      return newType;
+    }
+
+    public DexType getOldType() {
+      return oldType;
+    }
+
+    boolean hasBeenChangedToReturnVoid(AppView<?> appView) {
+      return newType == appView.dexItemFactory().voidType;
+    }
+
+    public boolean defaultValueHasChanged() {
+      if (newType.isPrimitiveType()) {
+        if (oldType.isPrimitiveType()) {
+          return ValueType.fromDexType(newType) != ValueType.fromDexType(oldType);
+        }
+        return true;
+      } else if (oldType.isPrimitiveType()) {
+        return true;
+      }
+      // All reference types uses null as default value.
+      assert newType.isReferenceType();
+      assert oldType.isReferenceType();
+      return false;
+    }
+
+    public TypeLatticeElement defaultValueLatticeElement(AppView<?> appView) {
+      if (newType.isPrimitiveType()) {
+        return TypeLatticeElement.fromDexType(newType, null, appView);
+      }
+      return TypeLatticeElement.getNull();
+    }
+
+    @Override
+    public boolean isRewrittenTypeInfo() {
+      return true;
+    }
+
+    @Override
+    public RewrittenTypeInfo asRewrittenTypeInfo() {
+      return this;
+    }
+
+    @Override
+    public ArgumentInfo combine(ArgumentInfo info) {
+      if (info.isRemovedArgumentInfo()) {
+        return info;
+      }
+      assert info.isRewrittenTypeInfo();
+      RewrittenTypeInfo rewrittenTypeInfo = info.asRewrittenTypeInfo();
+      assert newType == rewrittenTypeInfo.oldType;
+      return new RewrittenTypeInfo(oldType, rewrittenTypeInfo.newType);
+    }
+  }
+
+  public static class ArgumentInfoCollection {
+
+    private static final ArgumentInfoCollection EMPTY = new ArgumentInfoCollection();
+
+    private final Int2ReferenceSortedMap<ArgumentInfo> argumentInfos;
 
     // Specific constructor for empty.
-    private RemovedArgumentInfoCollection() {
-      this.removedArguments = new Int2ReferenceLinkedOpenHashMap<>();
+    private ArgumentInfoCollection() {
+      this.argumentInfos = new Int2ReferenceRBTreeMap<>();
     }
 
-    private RemovedArgumentInfoCollection(
-        Int2ReferenceSortedMap<RemovedArgumentInfo> removedArguments) {
-      assert removedArguments != null : "should use empty.";
-      assert !removedArguments.isEmpty() : "should use empty.";
-      this.removedArguments = removedArguments;
+    private ArgumentInfoCollection(Int2ReferenceSortedMap<ArgumentInfo> argumentInfos) {
+      assert argumentInfos != null : "should use empty.";
+      assert !argumentInfos.isEmpty() : "should use empty.";
+      this.argumentInfos = argumentInfos;
     }
 
-    public static RemovedArgumentInfoCollection empty() {
+    public static ArgumentInfoCollection empty() {
       return EMPTY;
     }
 
-    public RemovedArgumentInfo getArgumentInfo(int argIndex) {
-      return removedArguments.get(argIndex);
+    public boolean isEmpty() {
+      return this == EMPTY;
     }
 
     public boolean hasRemovedArguments() {
-      return !removedArguments.isEmpty();
+      for (ArgumentInfo value : argumentInfos.values()) {
+        if (value.isRemovedArgumentInfo()) {
+          return true;
+        }
+      }
+      return false;
     }
 
-    public boolean isArgumentRemoved(int argumentIndex) {
-      return removedArguments.containsKey(argumentIndex);
+    public int numberOfRemovedArguments() {
+      int removed = 0;
+      for (ArgumentInfo value : argumentInfos.values()) {
+        if (value.isRemovedArgumentInfo()) {
+          removed++;
+        }
+      }
+      return removed;
+    }
+
+    public ArgumentInfo getArgumentInfo(int argumentIndex) {
+      return argumentInfos.getOrDefault(argumentIndex, ArgumentInfo.NO_INFO);
+    }
+
+    public static Builder builder() {
+      return new Builder();
+    }
+
+    public static class Builder {
+
+      private Int2ReferenceSortedMap<ArgumentInfo> argumentInfos;
+
+      public void addArgumentInfo(int argIndex, ArgumentInfo argInfo) {
+        if (argumentInfos == null) {
+          argumentInfos = new Int2ReferenceRBTreeMap<>();
+        }
+        assert !argumentInfos.containsKey(argIndex);
+        argumentInfos.put(argIndex, argInfo);
+      }
+
+      public ArgumentInfoCollection build() {
+        if (argumentInfos == null || argumentInfos.isEmpty()) {
+          return EMPTY;
+        }
+        return new ArgumentInfoCollection(argumentInfos);
+      }
     }
 
     public DexType[] rewriteParameters(DexEncodedMethod encodedMethod) {
       // Currently not allowed to remove the receiver of an instance method. This would involve
       // changing invoke-direct/invoke-virtual into invoke-static.
-      assert encodedMethod.isStatic() || !isArgumentRemoved(0);
+      assert encodedMethod.isStatic() || !getArgumentInfo(0).isRemovedArgumentInfo();
       DexType[] params = encodedMethod.method.proto.parameters.values;
-      if (!hasRemovedArguments()) {
+      if (isEmpty()) {
         return params;
       }
       DexType[] newParams = new DexType[params.length - numberOfRemovedArguments()];
       int offset = encodedMethod.isStatic() ? 0 : 1;
       int newParamIndex = 0;
-      for (int oldParamIndex = 0; oldParamIndex < params.length; ++oldParamIndex) {
-        if (!isArgumentRemoved(oldParamIndex + offset)) {
+      for (int oldParamIndex = 0; oldParamIndex < params.length; oldParamIndex++) {
+        ArgumentInfo argInfo = argumentInfos.get(oldParamIndex + offset);
+        if (argInfo == null) {
           newParams[newParamIndex++] = params[oldParamIndex];
+        } else if (argInfo.isRewrittenTypeInfo()) {
+          RewrittenTypeInfo rewrittenTypeInfo = argInfo.asRewrittenTypeInfo();
+          assert params[oldParamIndex] == rewrittenTypeInfo.oldType;
+          newParams[newParamIndex++] = rewrittenTypeInfo.newType;
         }
       }
       return newParams;
     }
 
-    public int numberOfRemovedArguments() {
-      return removedArguments != null ? removedArguments.size() : 0;
-    }
-
-    public RemovedArgumentInfoCollection combine(RemovedArgumentInfoCollection info) {
-      if (hasRemovedArguments()) {
-        if (!info.hasRemovedArguments()) {
+    public ArgumentInfoCollection combine(ArgumentInfoCollection info) {
+      if (isEmpty()) {
+        return info;
+      } else {
+        if (info.isEmpty()) {
           return this;
         }
-      } else {
-        return info;
       }
 
-      Int2ReferenceSortedMap<RemovedArgumentInfo> newRemovedArguments =
-          new Int2ReferenceLinkedOpenHashMap<>();
-      newRemovedArguments.putAll(removedArguments);
-      IntBidirectionalIterator iterator = removedArguments.keySet().iterator();
+      Int2ReferenceSortedMap<ArgumentInfo> newArgInfos = new Int2ReferenceRBTreeMap<>();
+      newArgInfos.putAll(argumentInfos);
+      IntBidirectionalIterator iterator = argumentInfos.keySet().iterator();
       int offset = 0;
-      for (int pendingArgIndex : info.removedArguments.keySet()) {
-        int nextArgindex = peekNextOrMax(iterator);
-        while (nextArgindex <= pendingArgIndex + offset) {
+      int nextArgIndex;
+      for (int pendingArgIndex : info.argumentInfos.keySet()) {
+        nextArgIndex = peekNextOrMax(iterator);
+        while (nextArgIndex <= pendingArgIndex + offset) {
           iterator.nextInt();
-          nextArgindex = peekNextOrMax(iterator);
-          offset++;
+          ArgumentInfo argumentInfo = argumentInfos.get(nextArgIndex);
+          nextArgIndex = peekNextOrMax(iterator);
+          if (argumentInfo.isRemovedArgumentInfo()) {
+            offset++;
+          }
         }
-        assert !newRemovedArguments.containsKey(pendingArgIndex + offset);
-        newRemovedArguments.put(
-            pendingArgIndex + offset, info.removedArguments.get(pendingArgIndex));
+        ArgumentInfo newArgInfo =
+            nextArgIndex == pendingArgIndex + offset
+                ? ArgumentInfo.combine(
+                    argumentInfos.get(nextArgIndex), info.argumentInfos.get(pendingArgIndex))
+                : info.argumentInfos.get(pendingArgIndex);
+        newArgInfos.put(pendingArgIndex + offset, newArgInfo);
       }
-      return new RemovedArgumentInfoCollection(newRemovedArguments);
+      assert Ordering.natural().isOrdered(newArgInfos.keySet());
+      return new ArgumentInfoCollection(newArgInfos);
     }
 
     static int peekNextOrMax(IntBidirectionalIterator iterator) {
@@ -163,61 +340,46 @@
         return builder -> {
           int firstArgumentIndex = BooleanUtils.intValue(!method.isStatic());
           builder.removeParameterAnnotations(
-              oldIndex -> isArgumentRemoved(oldIndex + firstArgumentIndex));
+              oldIndex -> getArgumentInfo(oldIndex + firstArgumentIndex).isRemovedArgumentInfo());
         };
       }
       return null;
     }
-
-    public static Builder builder() {
-      return new Builder();
-    }
-
-    public static class Builder {
-      private Int2ReferenceSortedMap<RemovedArgumentInfo> removedArguments;
-
-      public Builder addRemovedArgument(int argIndex, RemovedArgumentInfo argInfo) {
-        if (removedArguments == null) {
-          removedArguments = new Int2ReferenceLinkedOpenHashMap<>();
-        }
-        assert !removedArguments.containsKey(argIndex);
-        removedArguments.put(argIndex, argInfo);
-        return this;
-      }
-
-      public RemovedArgumentInfoCollection build() {
-        if (removedArguments == null || removedArguments.isEmpty()) {
-          return EMPTY;
-        }
-        return new RemovedArgumentInfoCollection(removedArguments);
-      }
-    }
   }
 
   private static final RewrittenPrototypeDescription none = new RewrittenPrototypeDescription();
 
-  private final boolean hasBeenChangedToReturnVoid;
   private final boolean extraNullParameter;
-  private final RemovedArgumentInfoCollection removedArgumentsInfo;
+  private final ArgumentInfoCollection argumentInfoCollection;
+  private final RewrittenTypeInfo rewrittenReturnInfo;
 
   private RewrittenPrototypeDescription() {
-    this(false, false, RemovedArgumentInfoCollection.empty());
+    this(false, null, ArgumentInfoCollection.empty());
   }
 
   private RewrittenPrototypeDescription(
-      boolean hasBeenChangedToReturnVoid,
       boolean extraNullParameter,
-      RemovedArgumentInfoCollection removedArgumentsInfo) {
-    assert removedArgumentsInfo != null;
+      RewrittenTypeInfo rewrittenReturnInfo,
+      ArgumentInfoCollection argumentsInfo) {
+    assert argumentsInfo != null;
     this.extraNullParameter = extraNullParameter;
-    this.hasBeenChangedToReturnVoid = hasBeenChangedToReturnVoid;
-    this.removedArgumentsInfo = removedArgumentsInfo;
+    this.rewrittenReturnInfo = rewrittenReturnInfo;
+    this.argumentInfoCollection = argumentsInfo;
   }
 
   public static RewrittenPrototypeDescription createForUninstantiatedTypes(
-      boolean hasBeenChangedToReturnVoid, RemovedArgumentInfoCollection removedArgumentsInfo) {
-    return new RewrittenPrototypeDescription(
-        hasBeenChangedToReturnVoid, false, removedArgumentsInfo);
+      DexMethod method,
+      AppView<AppInfoWithLiveness> appView,
+      ArgumentInfoCollection removedArgumentsInfo) {
+    DexType returnType = method.proto.returnType;
+    RewrittenTypeInfo returnInfo =
+        returnType.isAlwaysNull(appView) ? RewrittenTypeInfo.toVoid(returnType, appView) : null;
+    return new RewrittenPrototypeDescription(false, returnInfo, removedArgumentsInfo);
+  }
+
+  public static RewrittenPrototypeDescription createForRewrittenTypes(
+      RewrittenTypeInfo returnInfo, ArgumentInfoCollection rewrittenArgumentsInfo) {
+    return new RewrittenPrototypeDescription(false, returnInfo, rewrittenArgumentsInfo);
   }
 
   public static RewrittenPrototypeDescription none() {
@@ -225,21 +387,27 @@
   }
 
   public boolean isEmpty() {
-    return !extraNullParameter
-        && !hasBeenChangedToReturnVoid
-        && !getRemovedArgumentInfoCollection().hasRemovedArguments();
+    return !extraNullParameter && rewrittenReturnInfo == null && argumentInfoCollection.isEmpty();
   }
 
   public boolean hasExtraNullParameter() {
     return extraNullParameter;
   }
 
-  public boolean hasBeenChangedToReturnVoid() {
-    return hasBeenChangedToReturnVoid;
+  public boolean hasBeenChangedToReturnVoid(AppView<?> appView) {
+    return rewrittenReturnInfo != null && rewrittenReturnInfo.hasBeenChangedToReturnVoid(appView);
   }
 
-  public RemovedArgumentInfoCollection getRemovedArgumentInfoCollection() {
-    return removedArgumentsInfo;
+  public ArgumentInfoCollection getArgumentInfoCollection() {
+    return argumentInfoCollection;
+  }
+
+  public boolean hasRewrittenReturnInfo() {
+    return rewrittenReturnInfo != null;
+  }
+
+  public RewrittenTypeInfo getRewrittenReturnInfo() {
+    return rewrittenReturnInfo;
   }
 
   /**
@@ -252,7 +420,6 @@
    * <p>Note that the current implementation always returns null at this point.
    */
   public ConstInstruction getConstantReturn(IRCode code, Position position) {
-    assert hasBeenChangedToReturnVoid;
     ConstInstruction instruction = code.createConstNull();
     instruction.setPosition(position);
     return instruction;
@@ -263,27 +430,32 @@
       return encodedMethod.method.proto;
     }
     DexType newReturnType =
-        hasBeenChangedToReturnVoid
-            ? dexItemFactory.voidType
+        rewrittenReturnInfo != null
+            ? rewrittenReturnInfo.newType
             : encodedMethod.method.proto.returnType;
-    DexType[] newParameters = removedArgumentsInfo.rewriteParameters(encodedMethod);
+    DexType[] newParameters = argumentInfoCollection.rewriteParameters(encodedMethod);
     return dexItemFactory.createProto(newReturnType, newParameters);
   }
 
-  public RewrittenPrototypeDescription withConstantReturn() {
-    return !hasBeenChangedToReturnVoid
-        ? new RewrittenPrototypeDescription(true, extraNullParameter, removedArgumentsInfo)
+  public RewrittenPrototypeDescription withConstantReturn(
+      DexType oldReturnType, AppView<?> appView) {
+    assert rewrittenReturnInfo == null;
+    return !hasBeenChangedToReturnVoid(appView)
+        ? new RewrittenPrototypeDescription(
+            extraNullParameter,
+            RewrittenTypeInfo.toVoid(oldReturnType, appView),
+            argumentInfoCollection)
         : this;
   }
 
-  public RewrittenPrototypeDescription withRemovedArguments(RemovedArgumentInfoCollection other) {
+  public RewrittenPrototypeDescription withRemovedArguments(ArgumentInfoCollection other) {
     return new RewrittenPrototypeDescription(
-        hasBeenChangedToReturnVoid, extraNullParameter, removedArgumentsInfo.combine(other));
+        extraNullParameter, rewrittenReturnInfo, argumentInfoCollection.combine(other));
   }
 
   public RewrittenPrototypeDescription withExtraNullParameter() {
     return !extraNullParameter
-        ? new RewrittenPrototypeDescription(hasBeenChangedToReturnVoid, true, removedArgumentsInfo)
+        ? new RewrittenPrototypeDescription(true, rewrittenReturnInfo, argumentInfoCollection)
         : this;
   }
 }
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 a9c7cac..c934275 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
@@ -33,10 +33,11 @@
 
   final AppView<AppInfoWithLiveness> appView;
   final DexProgramClass clazz;
-  private final IRCode code;
+  final IRCode code;
   final OptimizationFeedback feedback;
   final DexEncodedMethod method;
 
+  private DominatorTree dominatorTree;
   private Map<BasicBlock, AbstractFieldSet> fieldsMaybeReadBeforeBlockInclusiveCache;
 
   FieldValueAnalysis(
@@ -54,6 +55,13 @@
     this.method = method;
   }
 
+  DominatorTree getOrCreateDominatorTree() {
+    if (dominatorTree == null) {
+      dominatorTree = new DominatorTree(code, Assumption.NO_UNREACHABLE_BLOCKS);
+    }
+    return dominatorTree;
+  }
+
   private Map<BasicBlock, AbstractFieldSet> getOrCreateFieldsMaybeReadBeforeBlockInclusive() {
     if (fieldsMaybeReadBeforeBlockInclusiveCache == null) {
       fieldsMaybeReadBeforeBlockInclusiveCache = createFieldsMaybeReadBeforeBlockInclusive();
@@ -66,7 +74,6 @@
   /** This method analyzes initializers with the purpose of computing field optimization info. */
   void computeFieldOptimizationInfo(ClassInitializerDefaultsResult classInitializerDefaultsResult) {
     AppInfoWithLiveness appInfo = appView.appInfo();
-    DominatorTree dominatorTree = null;
 
     // 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.
@@ -97,10 +104,7 @@
       }
       FieldInstruction fieldPut = fieldPuts.getFirst();
       if (!isStraightLineCode) {
-        if (dominatorTree == null) {
-          dominatorTree = new DominatorTree(code, Assumption.NO_UNREACHABLE_BLOCKS);
-        }
-        if (!dominatorTree.dominatesAllOf(fieldPut.getBlock(), normalExitBlocks)) {
+        if (!getOrCreateDominatorTree().dominatesAllOf(fieldPut.getBlock(), normalExitBlocks)) {
           continue;
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
index 447834f..e540a06 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -15,9 +16,13 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.IRCodeUtils;
+import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
@@ -35,14 +40,21 @@
 
   private final InstanceFieldInitializationInfoFactory factory;
 
+  private final DexEncodedMethod parentConstructor;
+  private final InvokeDirect parentConstructorCall;
+
   private InstanceFieldValueAnalysis(
       AppView<AppInfoWithLiveness> appView,
       IRCode code,
       OptimizationFeedback feedback,
       DexProgramClass clazz,
-      DexEncodedMethod method) {
+      DexEncodedMethod method,
+      DexEncodedMethod parentConstructor,
+      InvokeDirect parentConstructorCall) {
     super(appView, code, feedback, clazz, method);
-    factory = appView.instanceFieldInitializationInfoFactory();
+    this.factory = appView.instanceFieldInitializationInfoFactory();
+    this.parentConstructor = parentConstructor;
+    this.parentConstructorCall = parentConstructorCall;
   }
 
   /**
@@ -58,13 +70,35 @@
     assert appView.appInfo().hasLiveness();
     assert appView.enableWholeProgramOptimizations();
     assert method.isInstanceInitializer();
+
     DexProgramClass clazz = appView.definitionFor(method.method.holder).asProgramClass();
     if (!appView.options().enableValuePropagationForInstanceFields) {
       return EmptyInstanceFieldInitializationInfoCollection.getInstance();
     }
+
+    InvokeDirect parentConstructorCall =
+        IRCodeUtils.getUniqueConstructorInvoke(code.getThis(), appView.dexItemFactory());
+    if (parentConstructorCall == null) {
+      return EmptyInstanceFieldInitializationInfoCollection.getInstance();
+    }
+
+    DexEncodedMethod parentConstructor =
+        parentConstructorCall.lookupSingleTarget(appView, clazz.type);
+    if (parentConstructor == null) {
+      return EmptyInstanceFieldInitializationInfoCollection.getInstance();
+    }
+
     InstanceFieldValueAnalysis analysis =
-        new InstanceFieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method);
+        new InstanceFieldValueAnalysis(
+            appView.withLiveness(),
+            code,
+            feedback,
+            clazz,
+            method,
+            parentConstructor,
+            parentConstructorCall);
     analysis.computeFieldOptimizationInfo(classInitializerDefaultsResult);
+    analysis.analyzeParentConstructorCall();
     return analysis.builder.build();
   }
 
@@ -75,12 +109,34 @@
 
   @Override
   void updateFieldOptimizationInfo(DexEncodedField field, FieldInstruction fieldPut, Value value) {
-    if (fieldMaybeWrittenBetweenInstructionAndMethodExit(field, fieldPut)) {
-      return;
+    if (fieldNeverWrittenBetweenInstancePutAndMethodExit(field, fieldPut.asInstancePut())) {
+      recordFieldIsInitializedWithValue(field, value);
     }
+  }
 
-    // If this instance field is initialized with an argument or a constant, then record this in the
-    // instance field initialization info.
+  private void analyzeParentConstructorCall() {
+    InstanceFieldInitializationInfoCollection infos =
+        parentConstructor
+            .getOptimizationInfo()
+            .getInstanceInitializerInfo()
+            .fieldInitializationInfos();
+    infos.forEach(
+        appView,
+        (field, info) -> {
+          if (fieldNeverWrittenBetweenParentConstructorCallAndMethodExit(field)) {
+            if (info.isArgumentInitializationInfo()) {
+              int argumentIndex = info.asArgumentInitializationInfo().getArgumentIndex();
+              recordFieldIsInitializedWithValue(
+                  field, parentConstructorCall.arguments().get(argumentIndex));
+            } else {
+              assert info.isSingleValue() || info.isTypeInitializationInfo();
+              builder.recordInitializationInfo(field, info);
+            }
+          }
+        });
+  }
+
+  private void recordFieldIsInitializedWithValue(DexEncodedField field, Value value) {
     Value root = value.getAliasedValue();
     if (root.isDefinedByInstructionSatisfying(Instruction::isArgument)) {
       Argument argument = root.definition.asArgument();
@@ -109,13 +165,60 @@
     }
   }
 
-  private boolean fieldMaybeWrittenBetweenInstructionAndMethodExit(
-      DexEncodedField field, FieldInstruction fieldPut) {
-    if (field.isFinal()
-        || appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)) {
-      return false;
+  private boolean fieldNeverWrittenBetweenInstancePutAndMethodExit(
+      DexEncodedField field, InstancePut instancePut) {
+    if (field.isFinal()) {
+      return true;
     }
-    // Otherwise, conservatively return true.
-    return true;
+
+    if (appView.appInfo().isFieldOnlyWrittenInMethod(field, method)) {
+      return true;
+    }
+
+    if (appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)) {
+      if (parentConstructorCall.getInvokedMethod().holder != clazz.type) {
+        // The field is only written in instance initializers of the enclosing class, and the
+        // constructor call targets a constructor in the super class.
+        return true;
+      }
+
+      // The parent constructor call in this initializer targets another initializer on the same
+      // class (constructor forwarding), which could potentially assign this field. Therefore, we
+      // need to check that the instance-put instruction comes after the parent constructor call.
+      BasicBlock instancePutBlock = instancePut.getBlock();
+      BasicBlock parentConstructorCallBlock = parentConstructorCall.getBlock();
+
+      if (instancePutBlock != parentConstructorCallBlock) {
+        // Check that the parent constructor call dominates the instance-put instruction.
+        return getOrCreateDominatorTree().dominatedBy(instancePutBlock, parentConstructorCallBlock);
+      }
+
+      // Check that the parent constructor call comes before the instance-put instruction in the
+      // block.
+      for (Instruction instruction : instancePutBlock.getInstructions()) {
+        if (instruction == instancePut) {
+          return false;
+        }
+        if (instruction == parentConstructorCall) {
+          return true;
+        }
+      }
+      throw new Unreachable();
+    }
+
+    // Otherwise, conservatively return false.
+    return false;
+  }
+
+  private boolean fieldNeverWrittenBetweenParentConstructorCallAndMethodExit(
+      DexEncodedField field) {
+    if (field.isFinal()) {
+      return true;
+    }
+    if (appView.appInfo().isFieldOnlyWrittenInMethod(field, parentConstructor)) {
+      return true;
+    }
+    // Otherwise, conservatively return false.
+    return false;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
index eff9681..0499222 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
@@ -145,9 +145,12 @@
   }
 
   @Override
-  public ClassTypeLatticeElement fixupClassTypeReferences(
+  public TypeLatticeElement fixupClassTypeReferences(
       Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
     DexType mappedType = mapping.apply(type);
+    if (mappedType.isPrimitiveType()) {
+      return PrimitiveTypeLatticeElement.fromDexType(mappedType, false);
+    }
     if (mappedType != type) {
       return create(mappedType, nullability, appView);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
index 3af208a..6449294 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
@@ -64,7 +64,10 @@
         affectedValues.addAll(phi.affectedValues());
       }
     }
-    assert new TypeAnalysis(appView).verifyValuesUpToDate(affectedPhis);
+
+    // TODO(b/150409786): Move all code rewriting passes into the lens code rewriter.
+    // assert new TypeAnalysis(appView).verifyValuesUpToDate(affectedPhis);
+
     // Now that the types of all transitively type affected phis have been reset, we can
     // perform a narrowing, starting from the values that are affected by those phis.
     if (!affectedValues.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 4a58946..1574275 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -193,7 +193,7 @@
   @Override
   public AbstractValue getAbstractValue(AppView<?> appView, DexType context) {
     if (!instructionMayHaveSideEffects(appView, context)) {
-      return appView.abstractValueFactory().createSingleConstClassValue(context);
+      return appView.abstractValueFactory().createSingleConstClassValue(clazz);
     }
     return UnknownValue.getInstance();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index e620cb1..952bd5c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -545,11 +545,16 @@
   }
 
   public boolean isConsistentSSA() {
+    isConsistentSSABeforeTypesAreCorrect();
+    assert verifyNoImpreciseOrBottomTypes();
+    return true;
+  }
+
+  public boolean isConsistentSSABeforeTypesAreCorrect() {
     assert isConsistentGraph();
     assert consistentDefUseChains();
     assert validThrowingInstructions();
     assert noCriticalEdges();
-    assert verifyNoImpreciseOrBottomTypes();
     assert verifyNoValueWithOnlyAssumeInstructionAsUsers();
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
index f6348ac..107e06e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.utils.DequeUtils;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
@@ -16,6 +17,28 @@
 
 public class IRCodeUtils {
 
+  public static InvokeDirect getUniqueConstructorInvoke(
+      Value value, DexItemFactory dexItemFactory) {
+    InvokeDirect result = null;
+    for (Instruction user : value.uniqueUsers()) {
+      if (user.isInvokeDirect()) {
+        InvokeDirect invoke = user.asInvokeDirect();
+        if (!dexItemFactory.isConstructor(invoke.getInvokedMethod())) {
+          continue;
+        }
+        if (invoke.getReceiver() != value) {
+          continue;
+        }
+        if (result != null) {
+          // Does not have a unique constructor invoke.
+          return null;
+        }
+        result = invoke;
+      }
+    }
+    return result;
+  }
+
   /**
    * Finds the single assignment to the fields in {@param fields} in {@param code}. Note that this
    * does not guarantee that the assignments found dominate all the normal exits.
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 32484c3..cd71750 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
@@ -34,9 +34,23 @@
 public class InstancePut extends FieldInstruction {
 
   public InstancePut(DexField field, Value object, Value value) {
+    this(field, object, value, false);
+  }
+
+  // During structural changes, IRCode is not valid from IR building until the point where
+  // several passes, such as the lens code rewriter, has been run. At this point, it can happen,
+  // for example in the context of enum unboxing, that some InstancePut have temporarily
+  // a primitive type as the object. Skip assertions in this case.
+  public static InstancePut createPotentiallyInvalid(DexField field, Value object, Value value) {
+    return new InstancePut(field, object, value, true);
+  }
+
+  private InstancePut(DexField field, Value object, Value value, boolean skipAssertion) {
     super(field, null, Arrays.asList(object, value));
-    assert object().verifyCompatible(ValueType.OBJECT);
-    assert value().verifyCompatible(ValueType.fromDexType(field.type));
+    if (!skipAssertion) {
+      assert object().verifyCompatible(ValueType.OBJECT);
+      assert value().verifyCompatible(ValueType.fromDexType(field.type));
+    }
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index ec77df1..15834d9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -103,6 +103,10 @@
     return inValues;
   }
 
+  public Value getArgument(int index) {
+    return arguments().get(index);
+  }
+
   public int requiredArgumentRegisters() {
     int registers = 0;
     for (Value inValue : inValues) {
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 cd88da6..a8fa713 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
@@ -38,24 +38,7 @@
   }
 
   public InvokeDirect getUniqueConstructorInvoke(DexItemFactory dexItemFactory) {
-    InvokeDirect result = null;
-    for (Instruction user : outValue().uniqueUsers()) {
-      if (user.isInvokeDirect()) {
-        InvokeDirect invoke = user.asInvokeDirect();
-        if (!dexItemFactory.isConstructor(invoke.getInvokedMethod())) {
-          continue;
-        }
-        if (invoke.getReceiver() != outValue()) {
-          continue;
-        }
-        if (result != null) {
-          // Does not have a unique constructor invoke.
-          return null;
-        }
-        result = invoke;
-      }
-    }
-    return result;
+    return IRCodeUtils.getUniqueConstructorInvoke(outValue(), dexItemFactory);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 5566552..bdaa741 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
@@ -507,6 +507,15 @@
     return !users.isEmpty();
   }
 
+  public boolean hasUserThatMatches(Predicate<Instruction> predicate) {
+    for (Instruction user : uniqueUsers()) {
+      if (predicate.test(user)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   public int numberOfUsers() {
     int size = users.size();
     if (size <= 1) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 8f6ea1e..1f1d2e5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -385,7 +385,7 @@
     inPrelude = true;
     state.buildPrelude(canonicalPositions.getPreamblePosition());
     setLocalVariableLists();
-    builder.buildArgumentsWithUnusedArgumentStubs(0, method, state::write);
+    builder.buildArgumentsWithRewrittenPrototypeChanges(0, method, state::write);
     // Add debug information for all locals at the initial label.
     Int2ReferenceMap<DebugLocalInfo> locals = getLocalVariables(0).locals;
     if (!locals.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 2436ed1..f19ab4d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -137,7 +137,7 @@
     if (code.incomingRegisterSize == 0) {
       return;
     }
-    builder.buildArgumentsWithUnusedArgumentStubs(
+    builder.buildArgumentsWithRewrittenPrototypeChanges(
         code.registerSize - code.incomingRegisterSize,
         method,
         DexSourceCode::doNothingWriteConsumer);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 0e24fe9..3c33035 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -34,8 +34,10 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfo;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -192,6 +194,7 @@
   }
 
   private static class MoveExceptionWorklistItem extends WorklistItem {
+
     private final DexType guard;
     private final int sourceOffset;
     private final int targetOffset;
@@ -206,6 +209,7 @@
   }
 
   private static class SplitBlockWorklistItem extends WorklistItem {
+
     private final int sourceOffset;
     private final int targetOffset;
     private final Position position;
@@ -224,9 +228,8 @@
   }
 
   /**
-   * Representation of lists of values that can be used as keys in maps. A list of
-   * values is equal to another list of values if it contains exactly the same values
-   * in the same order.
+   * Representation of lists of values that can be used as keys in maps. A list of values is equal
+   * to another list of values if it contains exactly the same values in the same order.
    */
   private static class ValueList {
 
@@ -267,6 +270,7 @@
   }
 
   public static class BlockInfo {
+
     BasicBlock block = new BasicBlock();
     IntSet normalPredecessors = new IntArraySet();
     IntSet normalSuccessors = new IntArraySet();
@@ -423,11 +427,10 @@
   // then the IR does not necessarily contain a const-string instruction).
   private final IRMetadata metadata = new IRMetadata();
 
-  public static IRBuilder create(DexEncodedMethod method,
-      AppView<?> appView,
-      SourceCode source,
-      Origin origin) {
-    return new IRBuilder(method,
+  public static IRBuilder create(
+      DexEncodedMethod method, AppView<?> appView, SourceCode source, Origin origin) {
+    return new IRBuilder(
+        method,
         appView,
         source,
         origin,
@@ -435,33 +438,29 @@
         new ValueNumberGenerator());
   }
 
-  public static IRBuilder createForInlining(DexEncodedMethod method,
+  public static IRBuilder createForInlining(
+      DexEncodedMethod method,
       AppView<?> appView,
       SourceCode source,
       Origin origin,
       MethodProcessor processor,
       ValueNumberGenerator valueNumberGenerator) {
-    RewrittenPrototypeDescription protoChanges = processor.shouldApplyCodeRewritings(method) ?
-        lookupPrototypeChanges(appView, method.method) :
-        RewrittenPrototypeDescription.none();
-    return new IRBuilder(method,
-        appView,
-        source,
-        origin,
-        protoChanges,
-        valueNumberGenerator);
+    RewrittenPrototypeDescription protoChanges =
+        processor.shouldApplyCodeRewritings(method)
+            ? lookupPrototypeChanges(appView, method.method)
+            : RewrittenPrototypeDescription.none();
+    return new IRBuilder(method, appView, source, origin, protoChanges, valueNumberGenerator);
   }
 
-  private static RewrittenPrototypeDescription lookupPrototypeChanges(AppView<?> appView,
-      DexMethod method) {
-    RewrittenPrototypeDescription prototypeChanges = appView.graphLense()
-        .lookupPrototypeChanges(method);
-    if (Log.ENABLED
-        && prototypeChanges.getRemovedArgumentInfoCollection().hasRemovedArguments()) {
+  private static RewrittenPrototypeDescription lookupPrototypeChanges(
+      AppView<?> appView, DexMethod method) {
+    RewrittenPrototypeDescription prototypeChanges =
+        appView.graphLense().lookupPrototypeChanges(method);
+    if (Log.ENABLED && prototypeChanges.getArgumentInfoCollection().hasRemovedArguments()) {
       Log.info(
           IRBuilder.class,
           "Removed "
-              + prototypeChanges.getRemovedArgumentInfoCollection().numberOfRemovedArguments()
+              + prototypeChanges.getArgumentInfoCollection().numberOfRemovedArguments()
               + " arguments from "
               + method.toSourceString());
     }
@@ -521,10 +520,9 @@
     currentBlock = block;
   }
 
-  public void buildArgumentsWithUnusedArgumentStubs(
+  public void buildArgumentsWithRewrittenPrototypeChanges(
       int register, DexEncodedMethod method, BiConsumer<Integer, DexType> writeCallback) {
-    RemovedArgumentInfoCollection removedArgumentsInfo =
-        prototypeChanges.getRemovedArgumentInfoCollection();
+    ArgumentInfoCollection argumentsInfo = prototypeChanges.getArgumentInfoCollection();
 
     // Fill in the Argument instructions (incomingRegisterSize last registers) in the argument
     // block.
@@ -539,24 +537,37 @@
 
     int numberOfArguments =
         method.method.proto.parameters.values.length
-            + removedArgumentsInfo.numberOfRemovedArguments()
+            + argumentsInfo.numberOfRemovedArguments()
             + (method.isStatic() ? 0 : 1);
 
     int usedArgumentIndex = 0;
     while (argumentIndex < numberOfArguments) {
       TypeLatticeElement type;
-      if (removedArgumentsInfo.isArgumentRemoved(argumentIndex)) {
-        RemovedArgumentInfo argumentInfo = removedArgumentsInfo.getArgumentInfo(argumentIndex);
-        writeCallback.accept(register, argumentInfo.getType());
+      ArgumentInfo argumentInfo = argumentsInfo.getArgumentInfo(argumentIndex);
+      if (argumentInfo.isRemovedArgumentInfo()) {
+        RemovedArgumentInfo removedArgumentInfo = argumentInfo.asRemovedArgumentInfo();
+        writeCallback.accept(register, removedArgumentInfo.getType());
         type =
             TypeLatticeElement.fromDexType(
-                argumentInfo.getType(), Nullability.maybeNull(), appView);
-        addConstantOrUnusedArgument(register, argumentInfo);
+                removedArgumentInfo.getType(), Nullability.maybeNull(), appView);
+        addConstantOrUnusedArgument(register, removedArgumentInfo);
       } else {
-        DexType dexType = method.method.proto.parameters.values[usedArgumentIndex++];
-        writeCallback.accept(register, dexType);
-        type = TypeLatticeElement.fromDexType(dexType, Nullability.maybeNull(), appView);
-        if (dexType.isBooleanType()) {
+        DexType argType;
+        if (argumentInfo.isRewrittenTypeInfo()) {
+          RewrittenTypeInfo argumentRewrittenTypeInfo = argumentInfo.asRewrittenTypeInfo();
+          assert method.method.proto.parameters.values[usedArgumentIndex]
+              == argumentRewrittenTypeInfo.getNewType();
+          // The old type is used to prevent that a changed value from reference to primitive
+          // type breaks IR building. Rewriting from the old to the new type will be done in the
+          // IRConverter (typically through the lensCodeRewriter).
+          argType = argumentRewrittenTypeInfo.getOldType();
+        } else {
+          argType = method.method.proto.parameters.values[usedArgumentIndex];
+        }
+        usedArgumentIndex++;
+        writeCallback.accept(register, argType);
+        type = TypeLatticeElement.fromDexType(argType, Nullability.maybeNull(), appView);
+        if (argType.isBooleanType()) {
           addBooleanNonThisArgument(register);
         } else {
           addNonThisArgument(register, type);
@@ -572,7 +583,6 @@
    * Build the high-level IR in SSA form.
    *
    * @param context Under what context this IRCode is built. Either the current method or caller.
-   *
    * @return The list of basic blocks. First block is the main entry.
    */
   public IRCode build(DexEncodedMethod context) {
@@ -935,14 +945,14 @@
   }
 
   public void addNonThisArgument(int register, TypeLatticeElement typeLattice) {
-      DebugLocalInfo local = getOutgoingLocal(register);
-      Value value = writeRegister(register, typeLattice, ThrowingInfo.NO_THROW, local);
+    DebugLocalInfo local = getOutgoingLocal(register);
+    Value value = writeRegister(register, typeLattice, ThrowingInfo.NO_THROW, local);
     addNonThisArgument(new Argument(value, currentBlock.size(), false));
   }
 
   public void addBooleanNonThisArgument(int register) {
-      DebugLocalInfo local = getOutgoingLocal(register);
-      Value value = writeRegister(register, getInt(), ThrowingInfo.NO_THROW, local);
+    DebugLocalInfo local = getOutgoingLocal(register);
+    Value value = writeRegister(register, getInt(), ThrowingInfo.NO_THROW, local);
     addNonThisArgument(new Argument(value, currentBlock.size(), true));
   }
 
@@ -1761,11 +1771,14 @@
 
   public void addReturn(int value) {
     if (method.method.proto.returnType == appView.dexItemFactory().voidType) {
-      assert prototypeChanges.hasBeenChangedToReturnVoid();
+      assert prototypeChanges.hasBeenChangedToReturnVoid(appView);
       addReturn();
     } else {
       ValueTypeConstraint returnTypeConstraint =
-          ValueTypeConstraint.fromDexType(method.method.proto.returnType);
+          prototypeChanges.hasRewrittenReturnInfo()
+              ? ValueTypeConstraint.fromDexType(
+                  prototypeChanges.getRewrittenReturnInfo().getOldType())
+              : ValueTypeConstraint.fromDexType(method.method.proto.returnType);
       Value in = readRegister(value, returnTypeConstraint);
       addReturn(new Return(in));
     }
@@ -2567,10 +2580,10 @@
   }
 
   /**
-   * Change to control-flow graph to avoid repeated phi operands when all the same values
-   * flow in from multiple predecessors.
+   * Change to control-flow graph to avoid repeated phi operands when all the same values flow in
+   * from multiple predecessors.
    *
-   * <p> As an example:
+   * <p>As an example:
    *
    * <pre>
    *
@@ -2582,7 +2595,7 @@
    *                  v3 = phi(v1, v1, v2)
    * </pre>
    *
-   * <p> Is rewritten to:
+   * <p>Is rewritten to:
    *
    * <pre>
    *              b1          b2         b3
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 94e5cf4..47c2c79 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
@@ -300,8 +300,10 @@
       this.lambdaMerger =
           options.enableLambdaMerging ? new LambdaMerger(appViewWithLiveness) : null;
       this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness);
+      this.enumUnboxer = options.enableEnumUnboxing ? new EnumUnboxer(appViewWithLiveness) : null;
       this.inliner =
-          new Inliner(appViewWithLiveness, mainDexClasses, lambdaMerger, lensCodeRewriter);
+          new Inliner(
+              appViewWithLiveness, mainDexClasses, lambdaMerger, lensCodeRewriter, enumUnboxer);
       this.outliner = new Outliner(appViewWithLiveness);
       this.memberValuePropagation =
           options.enableValuePropagation ? new MemberValuePropagation(appViewWithLiveness) : null;
@@ -331,7 +333,6 @@
               : null;
       this.enumValueOptimizer =
           options.enableEnumValueOptimization ? new EnumValueOptimizer(appViewWithLiveness) : null;
-      this.enumUnboxer = options.enableEnumUnboxing ? new EnumUnboxer(appViewWithLiveness) : null;
     } else {
       this.classInliner = null;
       this.classStaticizer = null;
@@ -451,6 +452,13 @@
     backportedMethodRewriter.synthesizeUtilityClasses(builder, executorService);
   }
 
+  private void synthesizeEnumUnboxingUtilityClass(
+      Builder<?> builder, ExecutorService executorService) throws ExecutionException {
+    if (enumUnboxer != null) {
+      enumUnboxer.synthesizeUtilityClass(builder, this, executorService);
+    }
+  }
+
   private void processCovariantReturnTypeAnnotations(Builder<?> builder) {
     if (covariantReturnTypeAnnotationTransformer != null) {
       covariantReturnTypeAnnotationTransformer.process(builder);
@@ -674,16 +682,12 @@
     // All the code has been processed so the rewriting required by the lenses is done everywhere,
     // we clear lens code rewriting so that the lens rewriter can be re-executed in phase 2 if new
     // lenses with code rewriting are added.
-    graphLenseForIR = appView.clearCodeRewritings();
+    appView.clearCodeRewritings();
 
     if (libraryMethodOverrideAnalysis != null) {
       libraryMethodOverrideAnalysis.finish();
     }
 
-    if (enumUnboxer != null) {
-      enumUnboxer.finishEnumAnalysis();
-    }
-
     // Post processing:
     //   1) Second pass for methods whose collected call site information become more precise.
     //   2) Second inlining pass for dealing with double inline callers.
@@ -694,8 +698,12 @@
     if (inliner != null) {
       postMethodProcessorBuilder.put(inliner);
     }
+    if (enumUnboxer != null) {
+      enumUnboxer.finishAnalysis();
+      enumUnboxer.unboxEnums(postMethodProcessorBuilder);
+    }
     timing.begin("IR conversion phase 2");
-    assert graphLenseForIR == appView.graphLense();
+    graphLenseForIR = appView.graphLense();
     PostMethodProcessor postMethodProcessor =
         postMethodProcessorBuilder.build(appView.withLiveness(), executorService, timing);
     if (postMethodProcessor != null) {
@@ -732,10 +740,11 @@
     desugarInterfaceMethods(builder, IncludeAllResources, executorService);
     feedback.updateVisibleOptimizationInfo();
 
-    printPhase("Twr close resource utility class synthesis");
+    printPhase("Utility classes synthesis");
     synthesizeTwrCloseResourceUtilityClass(builder, executorService);
     synthesizeJava8UtilityClass(builder, executorService);
     handleSynthesizedClassMapping(builder);
+    synthesizeEnumUnboxingUtilityClass(builder, executorService);
 
     printPhase("Lambda merging finalization");
     // TODO(b/127694949): Adapt to PostOptimization.
@@ -1133,6 +1142,10 @@
       timing.end();
     }
 
+    if (enumUnboxer != null && methodProcessor.isPost()) {
+      enumUnboxer.rewriteCode(code);
+    }
+
     if (method.isProcessed()) {
       assert !appView.enableWholeProgramOptimizations()
           || !appView.appInfo().withLiveness().neverReprocess.contains(method.method);
@@ -1170,6 +1183,7 @@
     // check. In the latter case, the type checker should be extended to detect the issue such that
     // we will return with finalizeEmptyThrowingCode() above.
     assert code.verifyTypes(appView);
+    assert code.isConsistentSSA();
 
     assertionsRewriter.run(method, code, timing);
 
@@ -1431,10 +1445,6 @@
 
     previous = printMethod(code, "IR after interface method rewriting (SSA)", previous);
 
-    if (enumUnboxer != null && methodProcessor.isPost()) {
-      enumUnboxer.unboxEnums(code);
-    }
-
     // This pass has to be after interfaceMethodRewriter and BackportedMethodRewriter.
     if (desugaredLibraryAPIConverter != null
         && (!appView.enableWholeProgramOptimizations() || methodProcessor.isPrimary())) {
@@ -1490,6 +1500,7 @@
       codeRewriter.shortenLiveRanges(code);
       timing.end();
     }
+
     timing.begin("Canonicalize idempotent calls");
     idempotentFunctionCallCanonicalizer.canonicalize(code);
     timing.end();
@@ -1665,6 +1676,7 @@
     ConstraintWithTarget state;
     if (!options.enableInlining
         || inliner == null
+        || method.isClassInitializer()
         || method.getOptimizationInfo().isReachabilitySensitive()) {
       state = ConstraintWithTarget.NEVER;
     } else {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index fc50632..d7cfc6b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -28,7 +28,9 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfo;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
 import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
@@ -170,11 +172,53 @@
           if (actualTarget != invokedMethod || invoke.getType() != actualInvokeType) {
             RewrittenPrototypeDescription prototypeChanges =
                 graphLense.lookupPrototypeChanges(actualTarget);
-            RemovedArgumentInfoCollection removedArgumentsInfo =
-                prototypeChanges.getRemovedArgumentInfoCollection();
+
+            List<Value> newInValues;
+            ArgumentInfoCollection argumentInfoCollection =
+                prototypeChanges.getArgumentInfoCollection();
+            if (argumentInfoCollection.isEmpty()) {
+              newInValues = invoke.inValues();
+            } else {
+              if (argumentInfoCollection.hasRemovedArguments()) {
+                if (Log.ENABLED) {
+                  Log.info(
+                      getClass(),
+                      "Invoked method "
+                          + invokedMethod.toSourceString()
+                          + " with "
+                          + argumentInfoCollection.numberOfRemovedArguments()
+                          + " arguments removed");
+                }
+              }
+              newInValues = new ArrayList<>(actualTarget.proto.parameters.size());
+              for (int i = 0; i < invoke.inValues().size(); i++) {
+                ArgumentInfo argumentInfo = argumentInfoCollection.getArgumentInfo(i);
+                if (argumentInfo.isRewrittenTypeInfo()) {
+                  RewrittenTypeInfo argInfo = argumentInfo.asRewrittenTypeInfo();
+                  Value value = invoke.inValues().get(i);
+                  // When converting types the default value may change (for example default value
+                  // of a reference type is null while default value of int is 0).
+                  if (argInfo.defaultValueHasChanged()
+                      && value.isConstNumber()
+                      && value.definition.asConstNumber().isZero()) {
+                    iterator.previous();
+                    // TODO(b/150188380): Add API to insert a const instruction with a type lattice.
+                    Value rewrittenNull =
+                        iterator.insertConstIntInstruction(code, appView.options(), 0);
+                    iterator.next();
+                    rewrittenNull.setTypeLattice(argInfo.defaultValueLatticeElement(appView));
+                    newInValues.add(rewrittenNull);
+                  } else {
+                    newInValues.add(invoke.inValues().get(i));
+                  }
+                } else if (!argumentInfo.isRemovedArgumentInfo()) {
+                  newInValues.add(invoke.inValues().get(i));
+                }
+              }
+            }
 
             ConstInstruction constantReturnMaterializingInstruction = null;
-            if (prototypeChanges.hasBeenChangedToReturnVoid() && invoke.outValue() != null) {
+            if (prototypeChanges.hasBeenChangedToReturnVoid(appView) && invoke.outValue() != null) {
               constantReturnMaterializingInstruction =
                   prototypeChanges.getConstantReturn(code, invoke.getPosition());
               if (invoke.outValue().hasLocalInfo()) {
@@ -190,29 +234,9 @@
             }
 
             Value newOutValue =
-                prototypeChanges.hasBeenChangedToReturnVoid() ? null : makeOutValue(invoke, code);
-
-            List<Value> newInValues;
-            if (removedArgumentsInfo.hasRemovedArguments()) {
-              if (Log.ENABLED) {
-                Log.info(
-                    getClass(),
-                    "Invoked method "
-                        + invokedMethod.toSourceString()
-                        + " with "
-                        + removedArgumentsInfo.numberOfRemovedArguments()
-                        + " arguments removed");
-              }
-              // Remove removed arguments from the invoke.
-              newInValues = new ArrayList<>(actualTarget.proto.parameters.size());
-              for (int i = 0; i < invoke.inValues().size(); i++) {
-                if (!removedArgumentsInfo.isArgumentRemoved(i)) {
-                  newInValues.add(invoke.inValues().get(i));
-                }
-              }
-            } else {
-              newInValues = invoke.inValues();
-            }
+                prototypeChanges.hasBeenChangedToReturnVoid(appView)
+                    ? null
+                    : makeOutValue(invoke, code);
 
             if (prototypeChanges.hasExtraNullParameter()) {
               iterator.previous();
@@ -288,7 +312,8 @@
                 new InvokeStatic(replacementMethod, null, current.inValues()));
           } else if (actualField != field) {
             InstancePut newInstancePut =
-                new InstancePut(actualField, instancePut.object(), instancePut.value());
+                InstancePut.createPotentiallyInvalid(
+                    actualField, instancePut.object(), instancePut.value());
             iterator.replaceCurrentInstruction(newInstancePut);
           }
         } else if (current.isStaticGet()) {
@@ -384,9 +409,8 @@
     }
     if (!affectedPhis.isEmpty()) {
       new DestructivePhiTypeUpdater(appView).recomputeAndPropagateTypes(code, affectedPhis);
-      assert code.verifyTypes(appView);
     }
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSABeforeTypesAreCorrect();
     assert code.hasNoVerticallyMergedClasses(appView);
   }
 
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 529bdc7..6052427 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
@@ -20,6 +20,7 @@
 import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Deque;
+import java.util.IdentityHashMap;
 import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Objects;
@@ -28,7 +29,7 @@
 import java.util.concurrent.ExecutorService;
 import java.util.stream.Collectors;
 
-class PostMethodProcessor implements MethodProcessor {
+public class PostMethodProcessor implements MethodProcessor {
 
   private final AppView<AppInfoWithLiveness> appView;
   private final Map<DexEncodedMethod, Collection<CodeOptimization>> methodsMap;
@@ -56,7 +57,8 @@
     return !processed.contains(method);
   }
 
-  static class Builder {
+  public static class Builder {
+
     private final Collection<CodeOptimization> defaultCodeOptimizations;
     private final Map<DexEncodedMethod, Collection<CodeOptimization>> methodsMap =
         Maps.newIdentityHashMap();
@@ -85,7 +87,7 @@
       put(methodsToRevisit, defaultCodeOptimizations);
     }
 
-    void put(PostOptimization postOptimization) {
+    public void put(PostOptimization postOptimization) {
       Collection<CodeOptimization> codeOptimizations =
           postOptimization.codeOptimizationsForPostProcessing();
       if (codeOptimizations == null) {
@@ -94,6 +96,20 @@
       put(postOptimization.methodsToRevisit(), codeOptimizations);
     }
 
+    // Some optimizations may change methods, creating new instances of the encoded methods with a
+    // new signature. The compiler needs to update the set of methods that must be reprocessed
+    // according to the graph lens.
+    public void mapDexEncodedMethods(AppView<?> appView) {
+      Map<DexEncodedMethod, Collection<CodeOptimization>> newMethodsMap = new IdentityHashMap<>();
+      methodsMap.forEach(
+          (dexEncodedMethod, optimizations) -> {
+            newMethodsMap.put(
+                appView.graphLense().mapDexEncodedMethod(dexEncodedMethod, appView), optimizations);
+          });
+      methodsMap.clear();
+      methodsMap.putAll(newMethodsMap);
+    }
+
     PostMethodProcessor build(
         AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
         throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index f6693eb..403f2f0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -1051,8 +1051,7 @@
 
   private boolean shouldIgnoreFromReports(DexType missing) {
     return appView.rewritePrefix.hasRewrittenType(missing, appView)
-        || DesugaredLibraryWrapperSynthesizer.isSynthesizedWrapper(missing)
-        || DesugaredLibraryAPIConverter.isVivifiedType(missing)
+        || missing.isD8R8SynthesizedClassType()
         || isCompanionClassType(missing)
         || emulatedInterfaces.containsValue(missing)
         || options.desugaredLibraryConfiguration.getCustomConversions().containsValue(missing);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index 56270d5..d440b01 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -35,7 +35,8 @@
   static final LambdaDescriptor MATCH_FAILED = new LambdaDescriptor();
 
   final String uniqueId;
-  final DexString name;
+  final DexMethod mainMethod;
+  public final DexString name;
   final DexProto erasedProto;
   final DexProto enforcedProto;
   public final DexMethodHandle implHandle;
@@ -57,6 +58,11 @@
     captures = null;
     targetAccessFlags = null;
     targetHolder = null;
+    mainMethod = null;
+  }
+
+  public DexMethod getMainMethod() {
+    return mainMethod;
   }
 
   private LambdaDescriptor(
@@ -77,7 +83,7 @@
     assert implHandle != null;
     assert mainInterface != null;
     assert captures != null;
-
+    this.mainMethod = appInfo.dexItemFactory().createMethod(mainInterface, erasedProto, name);
     this.uniqueId = callSite.getHash();
     this.name = name;
     this.erasedProto = erasedProto;
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 7f2cee9..80c36cc 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
@@ -2254,7 +2254,7 @@
     return anyAffectedValues;
   }
 
-  private boolean simplifyIf(IRCode code) {
+  public boolean simplifyIf(IRCode code) {
     for (BasicBlock block : code.blocks) {
       // Skip removed (= unreachable) blocks.
       if (block.getNumber() != 0 && block.getPredecessors().isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index a63cb59..5160e6c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -206,6 +206,8 @@
     if (!iterator.hasNext()) {
       return;
     }
+
+    boolean shouldSimplifyIfs = false;
     do {
       Object2ObjectMap.Entry<Instruction, List<Value>> entry = iterator.next();
       Instruction canonicalizedConstant = entry.getKey();
@@ -252,10 +254,13 @@
       for (Value outValue : entry.getValue()) {
         outValue.replaceUsers(newConst.outValue());
       }
+      shouldSimplifyIfs |= newConst.outValue().hasUserThatMatches(Instruction::isIf);
     } while (iterator.hasNext());
 
-    if (code.removeAllTrivialPhis()) {
-      codeRewriter.simplifyControlFlow(code);
+    shouldSimplifyIfs |= code.removeAllTrivialPhis();
+
+    if (shouldSimplifyIfs) {
+      codeRewriter.simplifyIf(code);
     }
 
     assert code.isConsistentSSA();
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 ee62dc3..0403117 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
@@ -44,6 +44,7 @@
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.PostOptimization;
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.ir.optimize.inliner.DefaultInliningReasonStrategy;
@@ -78,6 +79,7 @@
   private final Set<DexMethod> blacklist;
   private final LambdaMerger lambdaMerger;
   private final LensCodeRewriter lensCodeRewriter;
+  private final EnumUnboxer enumUnboxer;
   final MainDexClasses mainDexClasses;
 
   // State for inlining methods which are known to be called twice.
@@ -90,7 +92,8 @@
       AppView<AppInfoWithLiveness> appView,
       MainDexClasses mainDexClasses,
       LambdaMerger lambdaMerger,
-      LensCodeRewriter lensCodeRewriter) {
+      LensCodeRewriter lensCodeRewriter,
+      EnumUnboxer enumUnboxer) {
     Kotlin.Intrinsics intrinsics = appView.dexItemFactory().kotlin.intrinsics;
     this.appView = appView;
     this.blacklist =
@@ -99,6 +102,7 @@
             : ImmutableSet.of(intrinsics.throwNpe, intrinsics.throwParameterIsNullException);
     this.lambdaMerger = lambdaMerger;
     this.lensCodeRewriter = lensCodeRewriter;
+    this.enumUnboxer = enumUnboxer;
     this.mainDexClasses = mainDexClasses;
   }
 
@@ -587,7 +591,8 @@
         DexEncodedMethod context,
         InliningIRProvider inliningIRProvider,
         LambdaMerger lambdaMerger,
-        LensCodeRewriter lensCodeRewriter) {
+        LensCodeRewriter lensCodeRewriter,
+        EnumUnboxer enumUnboxer) {
       DexItemFactory dexItemFactory = appView.dexItemFactory();
       InternalOptions options = appView.options();
 
@@ -721,6 +726,9 @@
       if (inliningIRProvider.shouldApplyCodeRewritings(code.method)) {
         assert lensCodeRewriter != null;
         lensCodeRewriter.rewrite(code, target);
+        if (enumUnboxer != null) {
+          enumUnboxer.rewriteCode(code);
+        }
       }
       if (lambdaMerger != null) {
         lambdaMerger.rewriteCodeForInlining(target, code, context, inliningIRProvider);
@@ -964,7 +972,13 @@
 
           InlineeWithReason inlinee =
               action.buildInliningIR(
-                  appView, invoke, context, inliningIRProvider, lambdaMerger, lensCodeRewriter);
+                  appView,
+                  invoke,
+                  context,
+                  inliningIRProvider,
+                  lambdaMerger,
+                  lensCodeRewriter,
+                  enumUnboxer);
           if (strategy.willExceedBudget(
               code, invoke, inlinee, block, whyAreYouNotInliningReporter)) {
             assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 70a9795..e10b121 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.graph.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.code.Invoke.Type;
@@ -434,32 +433,7 @@
     ConstraintWithTarget classConstraintWithTarget =
         ConstraintWithTarget.deriveConstraint(
             invocationContext, methodHolder, methodClass.accessFlags, appView);
-    ConstraintWithTarget result =
-        ConstraintWithTarget.meet(methodConstraintWithTarget, classConstraintWithTarget, appView);
-    if (result == ConstraintWithTarget.NEVER) {
-      return result;
-    }
-
-    // For each of the actual potential targets, derive constraints based on the accessibility
-    // of the method itself.
-    LookupResult lookupResult =
-        resolutionResult.lookupVirtualDispatchTargets(
-            appView.definitionForProgramType(invocationContext), appView);
-    if (lookupResult.isLookupResultFailure()) {
-      return ConstraintWithTarget.NEVER;
-    }
-    for (DexEncodedMethod target : lookupResult.asLookupResultSuccess().getMethodTargets()) {
-      methodHolder = graphLense.lookupType(target.method.holder);
-      assert appView.definitionFor(methodHolder) != null;
-      methodConstraintWithTarget =
-          ConstraintWithTarget.deriveConstraint(
-              invocationContext, methodHolder, target.accessFlags, appView);
-      result = ConstraintWithTarget.meet(result, methodConstraintWithTarget, appView);
-      if (result == ConstraintWithTarget.NEVER) {
-        return result;
-      }
-    }
-
-    return result;
+    return ConstraintWithTarget.meet(
+        methodConstraintWithTarget, classConstraintWithTarget, appView);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 002abc9..4ce7ede 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
@@ -14,6 +15,7 @@
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -1296,6 +1298,16 @@
 
   public void identifyOutlineSites(IRCode code) {
     assert !code.method.getCode().isOutlineCode();
+    DexClass clazz = asProgramClassOrNull(appView.definitionFor(code.method.holder()));
+    assert clazz != null;
+    if (clazz == null) {
+      return;
+    }
+    if (appView.options().featureSplitConfiguration != null
+        && appView.options().featureSplitConfiguration.isInFeature(clazz.asProgramClass())) {
+      return;
+    }
+
     for (BasicBlock block : code.blocks) {
       new OutlineSiteIdentifier(code.method, block).process();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index f362ef7..698818f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.DominatorTree;
 import com.android.tools.r8.ir.code.FieldInstruction;
@@ -19,11 +20,14 @@
 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.InvokeDirect;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Phi;
 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.field.InstanceFieldInitializationInfoCollection;
+import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -50,15 +54,15 @@
   private final Set<Value> affectedValues = Sets.newIdentityHashSet();
 
   // Maps keeping track of fields that have an already loaded value at basic block entry.
-  private final Map<BasicBlock, Map<FieldAndObject, FieldInstruction>> activeInstanceFieldsAtEntry =
+  private final Map<BasicBlock, Map<FieldAndObject, FieldValue>> activeInstanceFieldsAtEntry =
       new IdentityHashMap<>();
-  private final Map<BasicBlock, Map<DexField, FieldInstruction>> activeStaticFieldsAtEntry =
+  private final Map<BasicBlock, Map<DexField, FieldValue>> activeStaticFieldsAtEntry =
       new IdentityHashMap<>();
 
   // Maps keeping track of fields with already loaded values for the current block during
   // elimination.
-  private Map<FieldAndObject, FieldInstruction> activeInstanceFields;
-  private Map<DexField, FieldInstruction> activeStaticFields;
+  private Map<FieldAndObject, FieldValue> activeInstanceFieldValues;
+  private Map<DexField, FieldValue> activeStaticFieldValues;
 
   public RedundantFieldLoadElimination(AppView<?> appView, IRCode code) {
     this.appView = appView;
@@ -72,6 +76,45 @@
         && code.metadata().mayHaveFieldGet();
   }
 
+  private interface FieldValue {
+
+    void eliminateRedundantRead(InstructionListIterator it, FieldInstruction redundant);
+  }
+
+  private class ExistingValue implements FieldValue {
+
+    private final Value value;
+
+    private ExistingValue(Value value) {
+      this.value = value;
+    }
+
+    @Override
+    public void eliminateRedundantRead(InstructionListIterator it, FieldInstruction redundant) {
+      affectedValues.addAll(redundant.value().affectedValues());
+      redundant.value().replaceUsers(value);
+      it.removeOrReplaceByDebugLocalRead();
+      value.uniquePhiUsers().forEach(Phi::removeTrivialPhi);
+    }
+  }
+
+  private class MaterializableValue implements FieldValue {
+
+    private final SingleValue value;
+
+    private MaterializableValue(SingleValue value) {
+      assert value.isMaterializableInContext(appView, method.holder());
+      this.value = value;
+    }
+
+    @Override
+    public void eliminateRedundantRead(InstructionListIterator it, FieldInstruction redundant) {
+      affectedValues.addAll(redundant.value().affectedValues());
+      it.replaceCurrentInstruction(
+          value.createMaterializingInstruction(appView.withSubtyping(), code, redundant));
+    }
+  }
+
   private static class FieldAndObject {
     private final DexField field;
     private final Value object;
@@ -98,25 +141,26 @@
   }
 
   private boolean couldBeVolatile(DexField field) {
-    if (!appView.enableWholeProgramOptimizations()) {
+    DexEncodedField definition;
+    if (appView.enableWholeProgramOptimizations()) {
+      definition = appView.appInfo().resolveField(field);
+    } else {
       if (field.holder != method.method.holder) {
         return true;
       }
-      DexEncodedField definition = appView.definitionFor(field);
-      return definition == null || definition.accessFlags.isVolatile();
+      definition = appView.definitionFor(field);
     }
-    DexEncodedField definition = appView.appInfo().resolveField(field);
     return definition == null || definition.accessFlags.isVolatile();
   }
 
   public void run() {
     DexType context = method.method.holder;
     for (BasicBlock block : dominatorTree.getSortedBlocks()) {
-      activeInstanceFields =
+      activeInstanceFieldValues =
           activeInstanceFieldsAtEntry.containsKey(block)
               ? activeInstanceFieldsAtEntry.get(block)
               : new HashMap<>();
-      activeStaticFields =
+      activeStaticFieldValues =
           activeStaticFieldsAtEntry.containsKey(block)
               ? activeStaticFieldsAtEntry.get(block)
               : new IdentityHashMap<>();
@@ -130,8 +174,6 @@
             continue;
           }
 
-          assert !couldBeVolatile(field);
-
           if (instruction.isInstanceGet()) {
             InstanceGet instanceGet = instruction.asInstanceGet();
             if (instanceGet.outValue().hasLocalInfo()) {
@@ -139,11 +181,11 @@
             }
             Value object = instanceGet.object().getAliasedValue();
             FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-            if (activeInstanceFields.containsKey(fieldAndObject)) {
-              FieldInstruction active = activeInstanceFields.get(fieldAndObject);
-              eliminateRedundantRead(it, instanceGet, active);
+            if (activeInstanceFieldValues.containsKey(fieldAndObject)) {
+              FieldValue replacement = activeInstanceFieldValues.get(fieldAndObject);
+              replacement.eliminateRedundantRead(it, instanceGet);
             } else {
-              activeInstanceFields.put(fieldAndObject, instanceGet);
+              activeInstanceFieldValues.put(fieldAndObject, new ExistingValue(instanceGet.value()));
             }
           } else if (instruction.isInstancePut()) {
             InstancePut instancePut = instruction.asInstancePut();
@@ -153,32 +195,34 @@
             // ... but at least we know the field value for this particular object.
             Value object = instancePut.object().getAliasedValue();
             FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-            activeInstanceFields.put(fieldAndObject, instancePut);
+            activeInstanceFieldValues.put(fieldAndObject, new ExistingValue(instancePut.value()));
           } else if (instruction.isStaticGet()) {
             StaticGet staticGet = instruction.asStaticGet();
             if (staticGet.outValue().hasLocalInfo()) {
               continue;
             }
-            if (activeStaticFields.containsKey(field)) {
-              FieldInstruction active = activeStaticFields.get(field);
-              eliminateRedundantRead(it, staticGet, active);
+            if (activeStaticFieldValues.containsKey(field)) {
+              FieldValue replacement = activeStaticFieldValues.get(field);
+              replacement.eliminateRedundantRead(it, staticGet);
             } else {
               // A field get on a different class can cause <clinit> to run and change static
               // field values.
               killActiveFields(staticGet);
-              activeStaticFields.put(field, staticGet);
+              activeStaticFieldValues.put(field, new ExistingValue(staticGet.value()));
             }
           } else if (instruction.isStaticPut()) {
             StaticPut staticPut = instruction.asStaticPut();
             // A field put on a different class can cause <clinit> to run and change static
             // field values.
             killActiveFields(staticPut);
-            activeStaticFields.put(field, staticPut);
+            activeStaticFieldValues.put(field, new ExistingValue(staticPut.value()));
           }
         } else if (instruction.isMonitor()) {
           if (instruction.asMonitor().isEnter()) {
             killAllActiveFields();
           }
+        } else if (instruction.isInvokeDirect()) {
+          handleInvokeDirect(instruction.asInvokeDirect());
         } else if (instruction.isInvokeMethod() || instruction.isInvokeCustom()) {
           killAllActiveFields();
         } else if (instruction.isNewInstance()) {
@@ -235,6 +279,51 @@
     assert code.isConsistentSSA();
   }
 
+  private void handleInvokeDirect(InvokeDirect invoke) {
+    if (!appView.enableWholeProgramOptimizations()) {
+      killAllActiveFields();
+      return;
+    }
+
+    DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.holder());
+    if (singleTarget == null || !singleTarget.isInstanceInitializer()) {
+      killAllActiveFields();
+      return;
+    }
+
+    InstanceInitializerInfo instanceInitializerInfo =
+        singleTarget.getOptimizationInfo().getInstanceInitializerInfo();
+    if (instanceInitializerInfo.mayHaveOtherSideEffectsThanInstanceFieldAssignments()) {
+      killAllActiveFields();
+    }
+
+    InstanceFieldInitializationInfoCollection fieldInitializationInfos =
+        instanceInitializerInfo.fieldInitializationInfos();
+    fieldInitializationInfos.forEach(
+        appView,
+        (field, info) -> {
+          if (!appView.appInfo().withLiveness().mayPropagateValueFor(field.field)) {
+            return;
+          }
+          if (info.isArgumentInitializationInfo()) {
+            Value value =
+                invoke.getArgument(info.asArgumentInitializationInfo().getArgumentIndex());
+            Value object = invoke.getReceiver().getAliasedValue();
+            FieldAndObject fieldAndObject = new FieldAndObject(field.field, object);
+            activeInstanceFieldValues.put(fieldAndObject, new ExistingValue(value));
+          } else if (info.isSingleValue()) {
+            SingleValue value = info.asSingleValue();
+            if (value.isMaterializableInContext(appView, method.holder())) {
+              Value object = invoke.getReceiver().getAliasedValue();
+              FieldAndObject fieldAndObject = new FieldAndObject(field.field, object);
+              activeInstanceFieldValues.put(fieldAndObject, new MaterializableValue(value));
+            }
+          } else {
+            assert info.isTypeInitializationInfo();
+          }
+        });
+  }
+
   private void propagateActiveFieldsFrom(BasicBlock block) {
     for (BasicBlock successor : block.getSuccessors()) {
       // Allow propagation across exceptional edges, just be careful not to propagate if the
@@ -247,16 +336,16 @@
           }
         }
         assert !activeInstanceFieldsAtEntry.containsKey(successor);
-        activeInstanceFieldsAtEntry.put(successor, new HashMap<>(activeInstanceFields));
+        activeInstanceFieldsAtEntry.put(successor, new HashMap<>(activeInstanceFieldValues));
         assert !activeStaticFieldsAtEntry.containsKey(successor);
-        activeStaticFieldsAtEntry.put(successor, new IdentityHashMap<>(activeStaticFields));
+        activeStaticFieldsAtEntry.put(successor, new IdentityHashMap<>(activeStaticFieldValues));
       }
     }
   }
 
   private void killAllActiveFields() {
-    activeInstanceFields.clear();
-    activeStaticFields.clear();
+    activeInstanceFieldValues.clear();
+    activeStaticFieldValues.clear();
   }
 
   private void killActiveFields(FieldInstruction instruction) {
@@ -265,25 +354,25 @@
       // Remove all the field/object pairs that refer to this field to make sure
       // that we are conservative.
       List<FieldAndObject> keysToRemove = new ArrayList<>();
-      for (FieldAndObject key : activeInstanceFields.keySet()) {
+      for (FieldAndObject key : activeInstanceFieldValues.keySet()) {
         if (key.field == field) {
           keysToRemove.add(key);
         }
       }
-      keysToRemove.forEach(activeInstanceFields::remove);
+      keysToRemove.forEach(activeInstanceFieldValues::remove);
     } else if (instruction.isStaticPut()) {
       if (field.holder != code.method.method.holder) {
         // Accessing a static field on a different object could cause <clinit> to run which
         // could modify any static field on any other object.
-        activeStaticFields.clear();
+        activeStaticFieldValues.clear();
       } else {
-        activeStaticFields.remove(field);
+        activeStaticFieldValues.remove(field);
       }
     } else if (instruction.isStaticGet()) {
       if (field.holder != code.method.method.holder) {
         // Accessing a static field on a different object could cause <clinit> to run which
         // could modify any static field on any other object.
-        activeStaticFields.clear();
+        activeStaticFieldValues.clear();
       }
     } else if (instruction.isInstanceGet()) {
       throw new Unreachable();
@@ -299,17 +388,9 @@
     if (instruction.isInstanceGet()) {
       Value object = instruction.asInstanceGet().object().getAliasedValue();
       FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-      activeInstanceFields.remove(fieldAndObject);
+      activeInstanceFieldValues.remove(fieldAndObject);
     } else if (instruction.isStaticGet()) {
-      activeStaticFields.remove(field);
+      activeStaticFieldValues.remove(field);
     }
   }
-
-  private void eliminateRedundantRead(
-      InstructionListIterator it, FieldInstruction redundant, FieldInstruction active) {
-    affectedValues.addAll(redundant.value().affectedValues());
-    redundant.value().replaceUsers(active.value());
-    it.removeOrReplaceByDebugLocalRead();
-    active.value().uniquePhiUsers().forEach(Phi::removeTrivialPhi);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
index 2c8e6b9..d5d51e1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
@@ -81,7 +81,7 @@
       processClasses(clazz);
     }
     if (!switchMaps.isEmpty()) {
-      return appView.appInfo().addSwitchMaps(switchMaps);
+      return appView.appInfo().withSwitchMaps(switchMaps);
     }
     return appView.appInfo();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index a04e7c5..b8ca4bc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -19,8 +19,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection;
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
 import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.TypeChecker;
@@ -62,11 +62,12 @@
 
   public static class UninstantiatedTypeOptimizationGraphLense extends NestedGraphLense {
 
-    private final Map<DexMethod, RemovedArgumentInfoCollection> removedArgumentsInfoPerMethod;
+    private final AppView<?> appView;
+    private final Map<DexMethod, ArgumentInfoCollection> removedArgumentsInfoPerMethod;
 
     UninstantiatedTypeOptimizationGraphLense(
         BiMap<DexMethod, DexMethod> methodMap,
-        Map<DexMethod, RemovedArgumentInfoCollection> removedArgumentsInfoPerMethod,
+        Map<DexMethod, ArgumentInfoCollection> removedArgumentsInfoPerMethod,
         AppView<?> appView) {
       super(
           ImmutableMap.of(),
@@ -76,6 +77,7 @@
           methodMap.inverse(),
           appView.graphLense(),
           appView.dexItemFactory());
+      this.appView = appView;
       this.removedArgumentsInfoPerMethod = removedArgumentsInfoPerMethod;
     }
 
@@ -85,10 +87,9 @@
       RewrittenPrototypeDescription result = previousLense.lookupPrototypeChanges(originalMethod);
       if (originalMethod != method) {
         if (method.proto.returnType.isVoidType() && !originalMethod.proto.returnType.isVoidType()) {
-          result = result.withConstantReturn();
+          result = result.withConstantReturn(originalMethod.proto.returnType, appView);
         }
-        RemovedArgumentInfoCollection removedArgumentsInfo =
-            removedArgumentsInfoPerMethod.get(method);
+        ArgumentInfoCollection removedArgumentsInfo = removedArgumentsInfoPerMethod.get(method);
         if (removedArgumentsInfo != null) {
           result = result.withRemovedArguments(removedArgumentsInfo);
         }
@@ -125,8 +126,7 @@
 
     Map<Wrapper<DexMethod>, Set<DexType>> changedVirtualMethods = new HashMap<>();
     BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
-    Map<DexMethod, RemovedArgumentInfoCollection> removedArgumentsInfoPerMethod =
-        new IdentityHashMap<>();
+    Map<DexMethod, ArgumentInfoCollection> removedArgumentsInfoPerMethod = new IdentityHashMap<>();
 
     TopDownClassHierarchyTraversal.forProgramClasses(appView)
         .visit(
@@ -151,7 +151,7 @@
       Map<Wrapper<DexMethod>, Set<DexType>> changedVirtualMethods,
       BiMap<DexMethod, DexMethod> methodMapping,
       MethodPoolCollection methodPoolCollection,
-      Map<DexMethod, RemovedArgumentInfoCollection> removedArgumentsInfoPerMethod) {
+      Map<DexMethod, ArgumentInfoCollection> removedArgumentsInfoPerMethod) {
     MemberPool<DexMethod> methodPool = methodPoolCollection.get(clazz);
 
     if (clazz.isInterface()) {
@@ -160,7 +160,8 @@
       for (DexEncodedMethod virtualMethod : clazz.virtualMethods()) {
         RewrittenPrototypeDescription prototypeChanges =
             RewrittenPrototypeDescription.createForUninstantiatedTypes(
-                virtualMethod.method.proto.returnType.isAlwaysNull(appView),
+                virtualMethod.method,
+                appView,
                 getRemovedArgumentsInfo(virtualMethod, ALLOW_ARGUMENT_REMOVAL));
         if (!prototypeChanges.isEmpty()) {
           DexMethod newMethod = getNewMethodSignature(virtualMethod, prototypeChanges);
@@ -199,8 +200,7 @@
       RewrittenPrototypeDescription prototypeChanges =
           prototypeChangesPerMethod.getOrDefault(
               encodedMethod, RewrittenPrototypeDescription.none());
-      RemovedArgumentInfoCollection removedArgumentsInfo =
-          prototypeChanges.getRemovedArgumentInfoCollection();
+      ArgumentInfoCollection removedArgumentsInfo = prototypeChanges.getArgumentInfoCollection();
       DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
       if (newMethod != method) {
         Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
@@ -233,8 +233,7 @@
       DexMethod method = encodedMethod.method;
       RewrittenPrototypeDescription prototypeChanges =
           getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
-      RemovedArgumentInfoCollection removedArgumentsInfo =
-          prototypeChanges.getRemovedArgumentInfoCollection();
+      ArgumentInfoCollection removedArgumentsInfo = prototypeChanges.getArgumentInfoCollection();
       DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
       if (newMethod != method) {
         Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
@@ -262,8 +261,7 @@
       DexMethod method = encodedMethod.method;
       RewrittenPrototypeDescription prototypeChanges =
           getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
-      RemovedArgumentInfoCollection removedArgumentsInfo =
-          prototypeChanges.getRemovedArgumentInfoCollection();
+      ArgumentInfoCollection removedArgumentsInfo = prototypeChanges.getArgumentInfoCollection();
       DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
       if (newMethod != method) {
         Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
@@ -298,17 +296,16 @@
       return RewrittenPrototypeDescription.none();
     }
     return RewrittenPrototypeDescription.createForUninstantiatedTypes(
-        encodedMethod.method.proto.returnType.isAlwaysNull(appView),
-        getRemovedArgumentsInfo(encodedMethod, strategy));
+        encodedMethod.method, appView, getRemovedArgumentsInfo(encodedMethod, strategy));
   }
 
-  private RemovedArgumentInfoCollection getRemovedArgumentsInfo(
+  private ArgumentInfoCollection getRemovedArgumentsInfo(
       DexEncodedMethod encodedMethod, Strategy strategy) {
     if (strategy == DISALLOW_ARGUMENT_REMOVAL) {
-      return RemovedArgumentInfoCollection.empty();
+      return ArgumentInfoCollection.empty();
     }
 
-    RemovedArgumentInfoCollection.Builder argInfosBuilder = RemovedArgumentInfoCollection.builder();
+    ArgumentInfoCollection.Builder argInfosBuilder = ArgumentInfoCollection.builder();
     DexProto proto = encodedMethod.method.proto;
     int offset = encodedMethod.isStatic() ? 0 : 1;
     for (int i = 0; i < proto.parameters.size(); ++i) {
@@ -316,7 +313,7 @@
       if (type.isAlwaysNull(appView)) {
         RemovedArgumentInfo removedArg =
             RemovedArgumentInfo.builder().setIsAlwaysNull().setType(type).build();
-        argInfosBuilder.addRemovedArgument(i + offset, removedArg);
+        argInfosBuilder.addArgumentInfo(i + offset, removedArg);
       }
     }
     return argInfosBuilder.build();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index e99ba9a..7c71bd9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -17,8 +17,8 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection;
 import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
@@ -50,12 +50,11 @@
   private final MethodPoolCollection methodPoolCollection;
 
   private final BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
-  private final Map<DexMethod, RemovedArgumentInfoCollection> removedArguments =
-      new IdentityHashMap<>();
+  private final Map<DexMethod, ArgumentInfoCollection> removedArguments = new IdentityHashMap<>();
 
   public static class UnusedArgumentsGraphLense extends NestedGraphLense {
 
-    private final Map<DexMethod, RemovedArgumentInfoCollection> removedArguments;
+    private final Map<DexMethod, ArgumentInfoCollection> removedArguments;
 
     UnusedArgumentsGraphLense(
         Map<DexType, DexType> typeMap,
@@ -65,7 +64,7 @@
         BiMap<DexMethod, DexMethod> originalMethodSignatures,
         GraphLense previousLense,
         DexItemFactory dexItemFactory,
-        Map<DexMethod, RemovedArgumentInfoCollection> removedArguments) {
+        Map<DexMethod, ArgumentInfoCollection> removedArguments) {
       super(
           typeMap,
           methodMap,
@@ -84,7 +83,7 @@
               ? originalMethodSignatures.getOrDefault(method, method)
               : method;
       RewrittenPrototypeDescription result = previousLense.lookupPrototypeChanges(originalMethod);
-      RemovedArgumentInfoCollection removedArguments = this.removedArguments.get(method);
+      ArgumentInfoCollection removedArguments = this.removedArguments.get(method);
       return removedArguments != null ? result.withRemovedArguments(removedArguments) : result;
     }
   }
@@ -167,7 +166,7 @@
     }
 
     DexEncodedMethod removeArguments(
-        DexEncodedMethod method, DexMethod newSignature, RemovedArgumentInfoCollection unused) {
+        DexEncodedMethod method, DexMethod newSignature, ArgumentInfoCollection unused) {
       boolean removed = usedSignatures.remove(equivalence.wrap(method.method));
       assert removed;
 
@@ -208,7 +207,7 @@
     }
 
     DexEncodedMethod removeArguments(
-        DexEncodedMethod method, DexMethod newSignature, RemovedArgumentInfoCollection unused) {
+        DexEncodedMethod method, DexMethod newSignature, ArgumentInfoCollection unused) {
       methodPool.seen(equivalence.wrap(newSignature));
       return method.toTypeSubstitutedMethod(
           newSignature, unused.createParameterAnnotationsRemover(method));
@@ -234,7 +233,7 @@
         continue;
       }
 
-      RemovedArgumentInfoCollection unused = collectUnusedArguments(method);
+      ArgumentInfoCollection unused = collectUnusedArguments(method);
       if (unused != null && unused.hasRemovedArguments()) {
         DexProto newProto = createProtoWithRemovedArguments(method, unused);
         DexMethod newSignature = signatures.getNewSignature(method, newProto);
@@ -259,7 +258,7 @@
     List<DexEncodedMethod> virtualMethods = clazz.virtualMethods();
     for (int i = 0; i < virtualMethods.size(); i++) {
       DexEncodedMethod method = virtualMethods.get(i);
-      RemovedArgumentInfoCollection unused = collectUnusedArguments(method, methodPool);
+      ArgumentInfoCollection unused = collectUnusedArguments(method, methodPool);
       if (unused != null && unused.hasRemovedArguments()) {
         DexProto newProto = createProtoWithRemovedArguments(method, unused);
         DexMethod newSignature = signatures.getNewSignature(method, newProto);
@@ -279,11 +278,11 @@
     }
   }
 
-  private RemovedArgumentInfoCollection collectUnusedArguments(DexEncodedMethod method) {
+  private ArgumentInfoCollection collectUnusedArguments(DexEncodedMethod method) {
     return collectUnusedArguments(method, null);
   }
 
-  private RemovedArgumentInfoCollection collectUnusedArguments(
+  private ArgumentInfoCollection collectUnusedArguments(
       DexEncodedMethod method, MemberPool<DexMethod> methodPool) {
     if (ArgumentRemovalUtils.isPinned(method, appView)
         || appView.appInfo().keepUnusedArguments.contains(method.method)) {
@@ -314,15 +313,14 @@
     method.getCode().registerArgumentReferences(method, collector);
     BitSet used = collector.getUsedArguments();
     if (used.cardinality() < argumentCount) {
-      RemovedArgumentInfoCollection.Builder argInfosBuilder =
-          RemovedArgumentInfoCollection.builder();
+      ArgumentInfoCollection.Builder argInfosBuilder = ArgumentInfoCollection.builder();
       for (int argumentIndex = 0; argumentIndex < argumentCount; argumentIndex++) {
         if (!used.get(argumentIndex)) {
           RemovedArgumentInfo removedArg =
               RemovedArgumentInfo.builder()
                   .setType(method.method.proto.parameters.values[argumentIndex - offset])
                   .build();
-          argInfosBuilder.addRemovedArgument(argumentIndex, removedArg);
+          argInfosBuilder.addArgumentInfo(argumentIndex, removedArg);
         }
       }
       return argInfosBuilder.build();
@@ -331,7 +329,7 @@
   }
 
   private DexProto createProtoWithRemovedArguments(
-      DexEncodedMethod encodedMethod, RemovedArgumentInfoCollection unused) {
+      DexEncodedMethod encodedMethod, ArgumentInfoCollection unused) {
     DexType[] parameters = unused.rewriteParameters(encodedMethod);
     return appView.dexItemFactory().createProto(encodedMethod.method.proto.returnType, parameters);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index b3a25d0..ee87965 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
@@ -5,17 +5,28 @@
 package com.android.tools.r8.ir.optimize.enums;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClass.FieldSetter;
+import com.android.tools.r8.graph.DexClass.MethodSetter;
 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.DexProto;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
 import com.android.tools.r8.ir.analysis.type.ArrayTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.If;
@@ -23,23 +34,40 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.CodeOptimization;
+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.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 
-public class EnumUnboxer {
+public class EnumUnboxer implements PostOptimization {
 
   private final AppView<AppInfoWithLiveness> appView;
-  private final Set<DexType> enumsToUnbox;
+  private final DexItemFactory factory;
+  // Map the enum candidates with their dependencies, i.e., the methods to reprocess for the given
+  // enum if the optimization eventually decides to unbox it.
+  private final Map<DexType, Set<DexEncodedMethod>> enumsUnboxingCandidates;
+
+  private EnumUnboxingRewriter enumUnboxerRewriter;
 
   private final boolean debugLogEnabled;
   private final Map<DexType, Reason> debugLogs;
-  private final DexItemFactory factory;
 
   public EnumUnboxer(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
@@ -51,19 +79,17 @@
       debugLogEnabled = false;
       debugLogs = null;
     }
-    enumsToUnbox = new EnumUnboxingCandidateAnalysis(appView, this).findCandidates();
-  }
-
-  public void unboxEnums(IRCode code) {
-    // TODO(b/147860220): To implement.
-    // Do not forget static get, which is implicitly valid (no inValue).
+    enumsUnboxingCandidates = new EnumUnboxingCandidateAnalysis(appView, this).findCandidates();
   }
 
   public void analyzeEnums(IRCode code) {
     // Enum <clinit> and <init> are analyzed in between the two processing phases using optimization
-    // feedback.
+    // feedback. Methods valueOf and values are generated by javac and are analyzed differently.
     DexClass dexClass = appView.definitionFor(code.method.method.holder);
-    if (dexClass.isEnum() && code.method.isInitializer()) {
+    if (dexClass.isEnum()
+        && (code.method.isInitializer()
+            || appView.dexItemFactory().enumMethods.isValueOfMethod(code.method.method, dexClass)
+            || appView.dexItemFactory().enumMethods.isValuesMethod(code.method.method, dexClass))) {
       return;
     }
     analyzeEnumsInMethod(code);
@@ -72,7 +98,7 @@
   private void markEnumAsUnboxable(Reason reason, DexProgramClass enumClass) {
     assert enumClass.isEnum();
     reportFailure(enumClass.type, reason);
-    enumsToUnbox.remove(enumClass.type);
+    enumsUnboxingCandidates.remove(enumClass.type);
   }
 
   private DexProgramClass getEnumUnboxingCandidateOrNull(TypeLatticeElement lattice) {
@@ -92,41 +118,105 @@
   }
 
   private DexProgramClass getEnumUnboxingCandidateOrNull(DexType anyType) {
-    if (!enumsToUnbox.contains(anyType)) {
+    if (!enumsUnboxingCandidates.containsKey(anyType)) {
       return null;
     }
     return appView.definitionForProgramType(anyType);
   }
 
   private void analyzeEnumsInMethod(IRCode code) {
+    Set<DexType> eligibleEnums = Sets.newIdentityHashSet();
     for (BasicBlock block : code.blocks) {
       for (Instruction instruction : block.getInstructions()) {
         Value outValue = instruction.outValue();
-        DexProgramClass enumClass =
-            outValue == null ? null : getEnumUnboxingCandidateOrNull(outValue.getTypeLattice());
-        if (enumClass != null) {
-          validateEnumUsages(code, outValue.uniqueUsers(), outValue.uniquePhiUsers(), enumClass);
+        if (outValue != null) {
+          DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(outValue.getTypeLattice());
+          if (enumClass != null) {
+            Reason reason = validateEnumUsages(code, outValue, enumClass);
+            if (reason == Reason.ELIGIBLE) {
+              if (instruction.isCheckCast()) {
+                // We are doing a type check, which typically means the in-value is of an upper
+                // type and cannot be dealt with.
+                markEnumAsUnboxable(Reason.DOWN_CAST, enumClass);
+              }
+              eligibleEnums.add(enumClass.type);
+            }
+          }
+          if (outValue.getTypeLattice().isNullType()) {
+            addNullDependencies(outValue.uniqueUsers(), eligibleEnums);
+          }
+        }
+        // If we have a ConstClass referencing directly an enum, it cannot be unboxed, except if
+        // the constClass is in an enum valueOf method (in this case the valueOf method will be
+        // removed or the enum will be marked as non unboxable).
+        if (instruction.isConstClass()) {
+          ConstClass constClass = instruction.asConstClass();
+          if (enumsUnboxingCandidates.containsKey(constClass.getValue())) {
+            markEnumAsUnboxable(
+                Reason.CONST_CLASS, appView.definitionForProgramType(constClass.getValue()));
+          }
         }
       }
       for (Phi phi : block.getPhis()) {
         DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(phi.getTypeLattice());
         if (enumClass != null) {
-          validateEnumUsages(code, phi.uniqueUsers(), phi.uniquePhiUsers(), enumClass);
+          Reason reason = validateEnumUsages(code, phi, enumClass);
+          if (reason == Reason.ELIGIBLE) {
+            eligibleEnums.add(enumClass.type);
+          }
+        }
+        if (phi.getTypeLattice().isNullType()) {
+          addNullDependencies(phi.uniqueUsers(), eligibleEnums);
+        }
+      }
+    }
+    if (!eligibleEnums.isEmpty()) {
+      for (DexType eligibleEnum : eligibleEnums) {
+        Set<DexEncodedMethod> dependencies = enumsUnboxingCandidates.get(eligibleEnum);
+        // If dependencies is null, it means the enum is not eligible (It has been marked as
+        // unboxable by this thread or another one), so we do not need to record dependencies.
+        if (dependencies != null) {
+          dependencies.add(code.method);
         }
       }
     }
   }
 
-  private Reason validateEnumUsages(
-      IRCode code, Set<Instruction> uses, Set<Phi> phiUses, DexProgramClass enumClass) {
-    for (Instruction user : uses) {
-      Reason reason = instructionAllowEnumUnboxing(user, code, enumClass);
+  private void addNullDependencies(Set<Instruction> uses, Set<DexType> eligibleEnums) {
+    for (Instruction use : uses) {
+      if (use.isInvokeMethod()) {
+        InvokeMethod invokeMethod = use.asInvokeMethod();
+        DexMethod invokedMethod = invokeMethod.getInvokedMethod();
+        for (DexType paramType : invokedMethod.proto.parameters.values) {
+          if (enumsUnboxingCandidates.containsKey(paramType)) {
+            eligibleEnums.add(paramType);
+          }
+        }
+        if (invokeMethod.isInvokeMethodWithReceiver()) {
+          DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(invokedMethod.holder);
+          if (enumClass != null) {
+            markEnumAsUnboxable(Reason.ENUM_METHOD_CALLED_WITH_NULL_RECEIVER, enumClass);
+          }
+        }
+      }
+      if (use.isFieldPut()) {
+        DexType type = use.asFieldInstruction().getField().type;
+        if (enumsUnboxingCandidates.containsKey(type)) {
+          eligibleEnums.add(type);
+        }
+      }
+    }
+  }
+
+  private Reason validateEnumUsages(IRCode code, Value value, DexProgramClass enumClass) {
+    for (Instruction user : value.uniqueUsers()) {
+      Reason reason = instructionAllowEnumUnboxing(user, code, enumClass, value);
       if (reason != Reason.ELIGIBLE) {
         markEnumAsUnboxable(reason, enumClass);
         return reason;
       }
     }
-    for (Phi phi : phiUses) {
+    for (Phi phi : value.uniquePhiUsers()) {
       for (Value operand : phi.getOperands()) {
         if (getEnumUnboxingCandidateOrNull(operand.getTypeLattice()) != enumClass) {
           markEnumAsUnboxable(Reason.INVALID_PHI, enumClass);
@@ -137,8 +227,28 @@
     return Reason.ELIGIBLE;
   }
 
-  public void finishEnumAnalysis() {
-    for (DexType toUnbox : enumsToUnbox) {
+  public void unboxEnums(PostMethodProcessor.Builder postBuilder) {
+    // 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);
+    if (enumUnboxingLens != null) {
+      appView.setGraphLense(enumUnboxingLens);
+      appView.setAppInfo(
+          appView
+              .appInfo()
+              .rewrittenWithLens(appView.appInfo().app().asDirect(), enumUnboxingLens));
+    }
+    postBuilder.put(this);
+    postBuilder.mapDexEncodedMethods(appView);
+  }
+
+  public void finishAnalysis() {
+    for (DexType toUnbox : enumsUnboxingCandidates.keySet()) {
       DexProgramClass enumClass = appView.definitionForProgramType(toUnbox);
       assert enumClass != null;
 
@@ -160,13 +270,13 @@
         continue;
       }
 
-      Map<DexField, EnumValueInfo> enumValueInfoMapFor =
-          appView.appInfo().withLiveness().getEnumValueInfoMapFor(enumClass.type);
-      if (enumValueInfoMapFor == null) {
+      EnumValueInfoMap enumValueInfoMap =
+          appView.appInfo().withLiveness().getEnumValueInfoMap(enumClass.type);
+      if (enumValueInfoMap == null) {
         markEnumAsUnboxable(Reason.MISSING_INFO_MAP, enumClass);
         continue;
       }
-      if (enumValueInfoMapFor.size() != enumClass.staticFields().size() - 1) {
+      if (enumValueInfoMap.size() != enumClass.staticFields().size() - 1) {
         markEnumAsUnboxable(Reason.UNEXPECTED_STATIC_FIELD, enumClass);
       }
     }
@@ -176,7 +286,7 @@
   }
 
   private Reason instructionAllowEnumUnboxing(
-      Instruction instruction, IRCode code, DexProgramClass enumClass) {
+      Instruction instruction, IRCode code, DexProgramClass enumClass, Value enumValue) {
 
     // All invokes in the library are invalid, besides a few cherry picked cases such as ordinal().
     if (instruction.isInvokeMethod()) {
@@ -188,25 +298,33 @@
         }
         return Reason.INVALID_INVOKE_ON_ARRAY;
       }
-      DexEncodedMethod invokedEncodedMethod =
+      DexEncodedMethod encodedSingleTarget =
           invokeMethod.lookupSingleTarget(appView, code.method.method.holder);
-      if (invokedEncodedMethod == null) {
+      if (encodedSingleTarget == null) {
         return Reason.INVALID_INVOKE;
       }
-      DexMethod invokedMethod = invokedEncodedMethod.method;
-      DexClass dexClass = appView.definitionFor(invokedMethod.holder);
+      DexMethod singleTarget = encodedSingleTarget.method;
+      DexClass dexClass = appView.definitionFor(singleTarget.holder);
       if (dexClass == null) {
         return Reason.INVALID_INVOKE;
       }
       if (dexClass.isProgramClass()) {
         // All invokes in the program are generally valid, but specific care is required
         // for values() and valueOf().
-        if (dexClass.isEnum() && factory.enumMethods.isValuesMethod(invokedMethod, dexClass)) {
+        if (dexClass.isEnum() && factory.enumMethods.isValuesMethod(singleTarget, dexClass)) {
           return Reason.VALUES_INVOKE;
         }
-        if (dexClass.isEnum() && factory.enumMethods.isValueOfMethod(invokedMethod, dexClass)) {
+        if (dexClass.isEnum() && factory.enumMethods.isValueOfMethod(singleTarget, dexClass)) {
           return Reason.VALUE_OF_INVOKE;
         }
+        int offset = BooleanUtils.intValue(!encodedSingleTarget.isStatic());
+        for (int i = 0; i < singleTarget.proto.parameters.size(); i++) {
+          if (invokeMethod.inValues().get(offset + i) == enumValue) {
+            if (singleTarget.proto.parameters.values[i] != enumClass.type) {
+              return Reason.GENERIC_INVOKE;
+            }
+          }
+        }
         return Reason.ELIGIBLE;
       }
       if (dexClass.isClasspathClass()) {
@@ -219,17 +337,17 @@
       // TODO(b/147860220): Methods toString(), name(), compareTo(), EnumSet and EnumMap may be
       // interesting to model. A the moment rewrite only Enum#ordinal().
       if (debugLogEnabled) {
-        if (invokedMethod == factory.enumMethods.compareTo) {
+        if (singleTarget == factory.enumMethods.compareTo) {
           return Reason.COMPARE_TO_INVOKE;
         }
-        if (invokedMethod == factory.enumMethods.name) {
+        if (singleTarget == factory.enumMethods.name) {
           return Reason.NAME_INVOKE;
         }
-        if (invokedMethod == factory.enumMethods.toString) {
+        if (singleTarget == factory.enumMethods.toString) {
           return Reason.TO_STRING_INVOKE;
         }
       }
-      if (invokedMethod != factory.enumMethods.ordinal) {
+      if (singleTarget != factory.enumMethods.ordinal) {
         return Reason.UNSUPPORTED_LIBRARY_CALL;
       }
       return Reason.ELIGIBLE;
@@ -280,7 +398,7 @@
 
     if (instruction.isAssume()) {
       Value outValue = instruction.outValue();
-      return validateEnumUsages(code, outValue.uniqueUsers(), outValue.uniquePhiUsers(), enumClass);
+      return validateEnumUsages(code, outValue, enumClass);
     }
 
     // Return is used for valueOf methods.
@@ -301,9 +419,9 @@
     reporter.info(
         new StringDiagnostic(
             "Unboxed enums (Unboxing succeeded "
-                + enumsToUnbox.size()
+                + enumsUnboxingCandidates.size()
                 + "): "
-                + Arrays.toString(enumsToUnbox.toArray())));
+                + Arrays.toString(enumsUnboxingCandidates.keySet().toArray())));
     StringBuilder sb = new StringBuilder();
     sb.append("Boxed enums (Unboxing failed ").append(debugLogs.size()).append("):\n");
     for (DexType enumType : debugLogs.keySet()) {
@@ -322,14 +440,48 @@
     }
   }
 
+  public void rewriteCode(IRCode code) {
+    if (enumUnboxerRewriter != null) {
+      enumUnboxerRewriter.rewriteCode(code);
+    }
+  }
+
+  public void synthesizeUtilityClass(
+      DexApplication.Builder<?> appBuilder, IRConverter converter, ExecutorService executorService)
+      throws ExecutionException {
+    if (enumUnboxerRewriter != null) {
+      enumUnboxerRewriter.synthesizeEnumUnboxingUtilityClass(
+          appBuilder, converter, executorService);
+    }
+  }
+
+  @Override
+  public Set<DexEncodedMethod> methodsToRevisit() {
+    Set<DexEncodedMethod> toReprocess = Sets.newIdentityHashSet();
+    for (Set<DexEncodedMethod> methods : enumsUnboxingCandidates.values()) {
+      toReprocess.addAll(methods);
+    }
+    return toReprocess;
+  }
+
+  @Override
+  public Collection<CodeOptimization> codeOptimizationsForPostProcessing() {
+    // Answers null so default optimization setup is performed.
+    return null;
+  }
+
   public enum Reason {
     ELIGIBLE,
+    PINNED,
+    DOWN_CAST,
     SUBTYPES,
     INTERFACE,
     INSTANCE_FIELD,
+    GENERIC_INVOKE,
     UNEXPECTED_STATIC_FIELD,
     VIRTUAL_METHOD,
     UNEXPECTED_DIRECT_METHOD,
+    CONST_CLASS,
     INVALID_PHI,
     NO_INIT,
     INVALID_INIT,
@@ -348,6 +500,196 @@
     FIELD_PUT_ON_ENUM,
     TYPE_MISSMATCH_FIELD_PUT,
     INVALID_IF_TYPES,
+    ENUM_METHOD_CALLED_WITH_NULL_RECEIVER,
     OTHER_UNSUPPORTED_INSTRUCTION;
   }
+
+  private class TreeFixer {
+
+    private final EnumUnboxingLens.Builder lensBuilder = EnumUnboxingLens.builder();
+    private final Set<DexType> enumsToUnbox;
+
+    private TreeFixer(Set<DexType> enumsToUnbox) {
+      this.enumsToUnbox = enumsToUnbox;
+    }
+
+    private NestedGraphLense fixupTypeReferences() {
+      // Fix all methods and fields using enums to unbox.
+      for (DexProgramClass clazz : appView.appInfo().classes()) {
+        if (enumsToUnbox.contains(clazz.type)) {
+          assert clazz.instanceFields().size() == 0;
+          clearEnumtoUnboxMethods(clazz);
+        } else {
+          fixupMethods(clazz.directMethods(), clazz::setDirectMethod);
+          fixupMethods(clazz.virtualMethods(), clazz::setVirtualMethod);
+          fixupFields(clazz.staticFields(), clazz::setStaticField);
+          fixupFields(clazz.instanceFields(), clazz::setInstanceField);
+        }
+      }
+      for (DexType toUnbox : enumsToUnbox) {
+        lensBuilder.map(toUnbox, factory.intType);
+      }
+      return lensBuilder.build(factory, appView.graphLense());
+    }
+
+    private void clearEnumtoUnboxMethods(DexProgramClass clazz) {
+      // The compiler may have references to the enum methods, but such methods will be removed
+      // and they cannot be reprocessed since their rewriting through the lensCodeRewriter/
+      // enumUnboxerRewriter will generate invalid code.
+      // To work around this problem we clear such methods, i.e., we replace the code object by
+      // an empty throwing code object, so reprocessing won't take time and will be valid.
+      for (DexEncodedMethod method : clazz.methods()) {
+        method.setCode(
+            appView.options().isGeneratingClassFiles()
+                ? method.buildEmptyThrowingCfCode()
+                : method.buildEmptyThrowingDexCode(),
+            appView);
+      }
+    }
+
+    private void fixupMethods(List<DexEncodedMethod> methods, MethodSetter setter) {
+      if (methods == null) {
+        return;
+      }
+      for (int i = 0; i < methods.size(); i++) {
+        DexEncodedMethod encodedMethod = methods.get(i);
+        DexMethod method = encodedMethod.method;
+        DexMethod newMethod = fixupMethod(method);
+        if (newMethod != method) {
+          lensBuilder.move(method, newMethod, encodedMethod.isStatic());
+          setter.setMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
+        }
+      }
+    }
+
+    private void fixupFields(List<DexEncodedField> fields, FieldSetter setter) {
+      if (fields == null) {
+        return;
+      }
+      for (int i = 0; i < fields.size(); i++) {
+        DexEncodedField encodedField = fields.get(i);
+        DexField field = encodedField.field;
+        DexType newType = fixupType(field.type);
+        if (newType != field.type) {
+          DexField newField = factory.createField(field.holder, newType, field.name);
+          lensBuilder.move(field, newField);
+          setter.setField(i, encodedField.toTypeSubstitutedField(newField));
+        }
+      }
+    }
+
+    private DexMethod fixupMethod(DexMethod method) {
+      return factory.createMethod(method.holder, fixupProto(method.proto), method.name);
+    }
+
+    private DexProto fixupProto(DexProto proto) {
+      DexType returnType = fixupType(proto.returnType);
+      DexType[] arguments = fixupTypes(proto.parameters.values);
+      return factory.createProto(returnType, arguments);
+    }
+
+    private DexType fixupType(DexType type) {
+      if (type.isArrayType()) {
+        DexType base = type.toBaseType(factory);
+        DexType fixed = fixupType(base);
+        if (base == fixed) {
+          return type;
+        }
+        return type.replaceBaseType(fixed, factory);
+      }
+      if (type.isClassType() && enumsToUnbox.contains(type)) {
+        DexType intType = factory.intType;
+        lensBuilder.map(type, intType);
+        return intType;
+      }
+      return type;
+    }
+
+    private DexType[] fixupTypes(DexType[] types) {
+      DexType[] result = new DexType[types.length];
+      for (int i = 0; i < result.length; i++) {
+        result[i] = fixupType(types[i]);
+      }
+      return result;
+    }
+  }
+
+  private static class EnumUnboxingLens extends NestedGraphLense {
+
+    private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges;
+
+    EnumUnboxingLens(
+        Map<DexType, DexType> typeMap,
+        Map<DexMethod, DexMethod> methodMap,
+        Map<DexField, DexField> fieldMap,
+        BiMap<DexField, DexField> originalFieldSignatures,
+        BiMap<DexMethod, DexMethod> originalMethodSignatures,
+        GraphLense previousLense,
+        DexItemFactory dexItemFactory,
+        Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges) {
+      super(
+          typeMap,
+          methodMap,
+          fieldMap,
+          originalFieldSignatures,
+          originalMethodSignatures,
+          previousLense,
+          dexItemFactory);
+      this.prototypeChanges = prototypeChanges;
+    }
+
+    @Override
+    public RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method) {
+      // During the second IR processing enum unboxing is the only optimization rewriting
+      // prototype description, if this does not hold, remove the assertion and merge
+      // the two prototype changes.
+      assert previousLense.lookupPrototypeChanges(method).isEmpty();
+      return prototypeChanges.getOrDefault(method, RewrittenPrototypeDescription.none());
+    }
+
+    public static Builder builder() {
+      return new Builder();
+    }
+
+    private static class Builder extends NestedGraphLense.Builder {
+
+      private Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges =
+          new IdentityHashMap<>();
+
+      public void move(DexMethod from, DexMethod to, boolean isStatic) {
+        super.move(from, to);
+        int offset = BooleanUtils.intValue(!isStatic);
+        ArgumentInfoCollection.Builder builder = ArgumentInfoCollection.builder();
+        for (int i = 0; i < from.proto.parameters.size(); i++) {
+          DexType fromType = from.proto.parameters.values[i];
+          DexType toType = to.proto.parameters.values[i];
+          if (fromType != toType) {
+            builder.addArgumentInfo(i + offset, new RewrittenTypeInfo(fromType, toType));
+          }
+        }
+        RewrittenTypeInfo returnInfo =
+            from.proto.returnType == to.proto.returnType
+                ? null
+                : new RewrittenTypeInfo(from.proto.returnType, to.proto.returnType);
+        prototypeChanges.put(
+            to, RewrittenPrototypeDescription.createForRewrittenTypes(returnInfo, builder.build()));
+      }
+
+      @Override
+      public EnumUnboxingLens build(DexItemFactory dexItemFactory, GraphLense previousLense) {
+        if (typeMap.isEmpty() && methodMap.isEmpty() && fieldMap.isEmpty()) {
+          return null;
+        }
+        return new EnumUnboxingLens(
+            typeMap,
+            methodMap,
+            fieldMap,
+            originalFieldSignatures,
+            originalMethodSignatures,
+            previousLense,
+            dexItemFactory,
+            ImmutableMap.copyOf(prototypeChanges));
+      }
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index 7b207c6..3d18f6a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -7,33 +7,42 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMember;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxer.Reason;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 class EnumUnboxingCandidateAnalysis {
 
-  private final AppView<?> appView;
+  private final AppView<AppInfoWithLiveness> appView;
   private final EnumUnboxer enumUnboxer;
   private final DexItemFactory factory;
+  private Map<DexType, Set<DexEncodedMethod>> enumToUnboxCandidates = new ConcurrentHashMap<>();
 
-  EnumUnboxingCandidateAnalysis(AppView<?> appView, EnumUnboxer enumUnboxer) {
+  EnumUnboxingCandidateAnalysis(AppView<AppInfoWithLiveness> appView, EnumUnboxer enumUnboxer) {
     this.appView = appView;
     this.enumUnboxer = enumUnboxer;
     factory = appView.dexItemFactory();
   }
 
-  Set<DexType> findCandidates() {
-    Set<DexType> enums = Sets.newConcurrentHashSet();
+  Map<DexType, Set<DexEncodedMethod>> findCandidates() {
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       if (isEnumUnboxingCandidate(clazz)) {
-        enums.add(clazz.type);
+        enumToUnboxCandidates.put(clazz.type, Sets.newConcurrentHashSet());
       }
     }
-    return enums;
+    removePinnedCandidates();
+    return enumToUnboxCandidates;
   }
 
   private boolean isEnumUnboxingCandidate(DexProgramClass clazz) {
@@ -111,4 +120,39 @@
     }
     return true;
   }
+
+  private void removePinnedCandidates() {
+    // A holder type, for field or method, should block enum unboxing only if the enum type is
+    // also kept. This is to allow the keep rule -keepclassmembers to be used on enums while
+    // enum unboxing can still be performed.
+    for (DexReference item : appView.appInfo().getPinnedItems()) {
+      if (item.isDexType()) {
+        removePinnedCandidate(item.asDexType());
+      } else if (item.isDexField()) {
+        DexField field = item.asDexField();
+        removePinnedIfNotHolder(field, field.type);
+      } else {
+        assert item.isDexMethod();
+        DexMethod method = item.asDexMethod();
+        DexProto proto = method.proto;
+        removePinnedIfNotHolder(method, proto.returnType);
+        for (DexType parameterType : proto.parameters.values) {
+          removePinnedIfNotHolder(method, parameterType);
+        }
+      }
+    }
+  }
+
+  private void removePinnedIfNotHolder(DexMember<?, ?> member, DexType type) {
+    if (type != member.holder) {
+      removePinnedCandidate(type);
+    }
+  }
+
+  private void removePinnedCandidate(DexType type) {
+    if (enumToUnboxCandidates.containsKey(type)) {
+      enumUnboxer.reportFailure(type, Reason.PINNED);
+      enumToUnboxCandidates.remove(type);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
new file mode 100644
index 0000000..ea26434
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -0,0 +1,220 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.enums;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexApplication;
+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.DexTypeList;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
+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.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class EnumUnboxingRewriter {
+
+  public static final String ENUM_UNBOXING_UTILITY_CLASS_NAME = "$r8$EnumUnboxingUtility";
+  private static final int REQUIRED_CLASS_FILE_VERSION = 52;
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final DexItemFactory factory;
+  private final EnumValueInfoMapCollection enumsToUnbox;
+
+  private final DexType utilityClassType;
+  private final DexMethod ordinalUtilityMethod;
+
+  private boolean requiresOrdinalUtilityMethod = false;
+
+  EnumUnboxingRewriter(AppView<AppInfoWithLiveness> appView, Set<DexType> enumsToUnbox) {
+    this.appView = appView;
+    this.factory = appView.dexItemFactory();
+    EnumValueInfoMapCollection.Builder builder = EnumValueInfoMapCollection.builder();
+    for (DexType toUnbox : enumsToUnbox) {
+      builder.put(toUnbox, appView.appInfo().withLiveness().getEnumValueInfoMap(toUnbox));
+    }
+    this.enumsToUnbox = builder.build();
+
+    this.utilityClassType = factory.createType("L" + ENUM_UNBOXING_UTILITY_CLASS_NAME + ";");
+    this.ordinalUtilityMethod =
+        factory.createMethod(
+            utilityClassType,
+            factory.createProto(factory.intType, factory.intType),
+            "$enumboxing$ordinal");
+  }
+
+  void rewriteCode(IRCode code) {
+    // We should not process the enum methods, they will be removed and they may contain invalid
+    // rewriting rules.
+    if (enumsToUnbox.isEmpty()) {
+      return;
+    }
+    Set<Phi> affectedPhis = Sets.newIdentityHashSet();
+    InstructionListIterator iterator = code.instructionListIterator();
+    while (iterator.hasNext()) {
+      Instruction instruction = iterator.next();
+      // Rewrites specific enum methods, such as ordinal, into their corresponding enum unboxed
+      // counterpart.
+      if (instruction.isInvokeMethodWithReceiver()) {
+        InvokeMethodWithReceiver invokeMethod = instruction.asInvokeMethodWithReceiver();
+        DexMethod invokedMethod = invokeMethod.getInvokedMethod();
+        if (invokedMethod == factory.enumMethods.ordinal
+            && invokeMethod.getReceiver().getTypeLattice().isInt()) {
+          instruction =
+              new InvokeStatic(
+                  ordinalUtilityMethod, invokeMethod.outValue(), invokeMethod.inValues());
+          iterator.replaceCurrentInstruction(instruction);
+          requiresOrdinalUtilityMethod = true;
+        }
+        // TODO(b/147860220): rewrite also other enum methods.
+      }
+      // Rewrites direct access to enum values into the corresponding int, $VALUES is not
+      // supported.
+      if (instruction.isStaticGet()) {
+        StaticGet staticGet = instruction.asStaticGet();
+        DexType holder = staticGet.getField().holder;
+        if (enumsToUnbox.containsEnum(holder)) {
+          if (staticGet.outValue() == null) {
+            iterator.removeInstructionIgnoreOutValue();
+            continue;
+          }
+          EnumValueInfoMap enumValueInfoMap = enumsToUnbox.getEnumValueInfoMap(holder);
+          assert enumValueInfoMap != null;
+          // Replace by ordinal + 1 for null check (null is 0).
+          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);
+          staticGet
+              .outValue()
+              .setTypeLattice(PrimitiveTypeLatticeElement.fromNumericType(NumericType.INT));
+          iterator.replaceCurrentInstruction(instruction);
+          affectedPhis.addAll(staticGet.outValue().uniquePhiUsers());
+        }
+      }
+      assert validateEnumToUnboxRemoved(instruction);
+    }
+    if (!affectedPhis.isEmpty()) {
+      new DestructivePhiTypeUpdater(appView).recomputeAndPropagateTypes(code, affectedPhis);
+    }
+    assert code.isConsistentSSABeforeTypesAreCorrect();
+  }
+
+  private boolean validateEnumToUnboxRemoved(Instruction instruction) {
+    if (instruction.outValue() == null) {
+      return true;
+    }
+    TypeLatticeElement typeLattice = instruction.outValue().getTypeLattice();
+    assert !typeLattice.isClassType()
+        || !enumsToUnbox.containsEnum(typeLattice.asClassTypeLatticeElement().getClassType());
+    if (typeLattice.isArrayType()) {
+      TypeLatticeElement arrayBaseTypeLattice =
+          typeLattice.asArrayTypeLatticeElement().getArrayBaseTypeLattice();
+      assert !arrayBaseTypeLattice.isClassType()
+          || !enumsToUnbox.containsEnum(
+              arrayBaseTypeLattice.asClassTypeLatticeElement().getClassType());
+    }
+    return true;
+  }
+
+  // TODO(b/150172351): Synthesize the utility class upfront in the enqueuer.
+  void synthesizeEnumUnboxingUtilityClass(
+      DexApplication.Builder<?> appBuilder, IRConverter converter, ExecutorService executorService)
+      throws ExecutionException {
+    // Synthesize a class which holds various utility methods that may be called from the IR
+    // rewriting. If any of these methods are not used, they will be removed by the Enqueuer.
+    List<DexEncodedMethod> requiredMethods = new ArrayList<>();
+    if (requiresOrdinalUtilityMethod) {
+      requiredMethods.add(synthesizeOrdinalMethod());
+    }
+    // TODO(b/147860220): synthesize also other enum methods.
+    if (requiredMethods.isEmpty()) {
+      return;
+    }
+    DexProgramClass utilityClass =
+        new DexProgramClass(
+            utilityClassType,
+            null,
+            new SynthesizedOrigin("EnumUnboxing ", getClass()),
+            ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
+            factory.objectType,
+            DexTypeList.empty(),
+            factory.createString("enumunboxing"),
+            null,
+            Collections.emptyList(),
+            null,
+            Collections.emptyList(),
+            DexAnnotationSet.empty(),
+            DexEncodedField.EMPTY_ARRAY,
+            DexEncodedField.EMPTY_ARRAY,
+            // All synthesized methods are static in this case.
+            requiredMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
+            DexEncodedMethod.EMPTY_ARRAY,
+            factory.getSkipNameValidationForTesting(),
+            DexProgramClass::checksumFromType);
+    appBuilder.addSynthesizedClass(utilityClass, utilityClassInMainDexList());
+    appView.appInfo().addSynthesizedClass(utilityClass);
+    converter.optimizeSynthesizedClass(utilityClass, executorService);
+  }
+
+  // TODO(b/150178516): Add a test for this case.
+  private boolean utilityClassInMainDexList() {
+    for (DexType toUnbox : enumsToUnbox.enumSet()) {
+      if (appView.appInfo().isInMainDexList(toUnbox)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private DexEncodedMethod synthesizeOrdinalMethod() {
+    CfCode cfCode =
+        EnumUnboxingCfMethods.EnumUnboxingMethods_ordinal(appView.options(), ordinalUtilityMethod);
+    return new DexEncodedMethod(
+        ordinalUtilityMethod,
+        synthesizedMethodAccessFlags(),
+        DexAnnotationSet.empty(),
+        ParameterAnnotationsList.empty(),
+        cfCode,
+        REQUIRED_CLASS_FILE_VERSION,
+        true);
+  }
+
+  private MethodAccessFlags synthesizedMethodAccessFlags() {
+    return MethodAccessFlags.fromSharedAccessFlags(
+        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC | Constants.ACC_STATIC, false);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInfoMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueInfoMapCollector.java
similarity index 72%
rename from src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInfoMapCollector.java
rename to src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueInfoMapCollector.java
index e0991e0..386a32d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInfoMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueInfoMapCollector.java
@@ -8,31 +8,34 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
 import java.util.IdentityHashMap;
 import java.util.Map;
 
 /**
  * Extracts the ordinal values and any anonymous subtypes for all Enum classes from their static
  * initializer.
- * <p>
- * An Enum class has a field for each value. In the class initializer, each field is initialized
- * to a singleton object that represents the value. This code matches on the corresponding call
- * to the constructor (instance initializer) and extracts the value of the second argument, which
- * is the ordinal and the holder which is the concrete type.
+ *
+ * <p>An Enum class has a field for each value. In the class initializer, each field is initialized
+ * to a singleton object that represents the value. This code matches on the corresponding call to
+ * the constructor (instance initializer) and extracts the value of the second argument, which is
+ * the ordinal and the holder which is the concrete type.
  */
-public class EnumInfoMapCollector {
+public class EnumValueInfoMapCollector {
 
   private final AppView<AppInfoWithLiveness> appView;
 
-  private final Map<DexType, Map<DexField, EnumValueInfo>> valueInfoMaps = new IdentityHashMap<>();
+  private final EnumValueInfoMapCollection.Builder valueInfoMapsBuilder =
+      EnumValueInfoMapCollection.builder();
 
-  public EnumInfoMapCollector(AppView<AppInfoWithLiveness> appView) {
+  public EnumValueInfoMapCollector(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
   }
 
@@ -40,8 +43,9 @@
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       processClasses(clazz);
     }
+    EnumValueInfoMapCollection valueInfoMaps = valueInfoMapsBuilder.build();
     if (!valueInfoMaps.isEmpty()) {
-      return appView.appInfo().addEnumValueInfoMaps(valueInfoMaps);
+      return appView.appInfo().withEnumValueInfoMaps(valueInfoMaps);
     }
     return appView.appInfo();
   }
@@ -53,7 +57,7 @@
     }
     DexEncodedMethod initializer = clazz.getClassInitializer();
     IRCode code = initializer.getCode().buildIR(initializer, appView, clazz.origin);
-    Map<DexField, EnumValueInfo> valueInfoMap = new IdentityHashMap<>();
+    Map<DexField, EnumValueInfo> enumValueInfoMap = new IdentityHashMap<>();
     for (Instruction insn : code.instructions()) {
       if (!insn.isStaticPut()) {
         continue;
@@ -86,10 +90,10 @@
       }
 
       EnumValueInfo info = new EnumValueInfo(type, ordinal.asConstNumber().getIntValue());
-      if (valueInfoMap.put(staticPut.getField(), info) != null) {
+      if (enumValueInfoMap.put(staticPut.getField(), info) != null) {
         return;
       }
     }
-    valueInfoMaps.put(clazz.type, valueInfoMap);
+    valueInfoMapsBuilder.put(clazz.type, new EnumValueInfoMap(enumValueInfoMap));
   }
 }
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 c8122470..ddf697d 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
@@ -11,6 +11,8 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
 import com.android.tools.r8.ir.code.ArrayGet;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
@@ -27,12 +29,10 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.SwitchMapCollector;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import java.util.Arrays;
-import java.util.Map;
 
 public class EnumValueOptimizer {
 
@@ -76,8 +76,8 @@
       }
       DexField enumField = definition.asStaticGet().getField();
 
-      Map<DexField, EnumValueInfo> valueInfoMap =
-          appView.appInfo().withLiveness().getEnumValueInfoMapFor(enumField.type);
+      EnumValueInfoMap valueInfoMap =
+          appView.appInfo().withLiveness().getEnumValueInfoMap(enumField.type);
       if (valueInfoMap == null) {
         continue;
       }
@@ -86,7 +86,7 @@
       // that it is a static-get to a field whose type is the same as the enclosing class (which
       // is known to be an enum type). An enum may still define a static field using the enum type
       // so ensure the field is present in the ordinal map for final validation.
-      EnumValueInfo valueInfo = valueInfoMap.get(enumField);
+      EnumValueInfo valueInfo = valueInfoMap.getEnumValueInfo(enumField);
       if (valueInfo == null) {
         continue;
       }
@@ -153,7 +153,8 @@
       Int2IntMap targetMap = new Int2IntArrayMap();
       for (int i = 0; i < switchInsn.numberOfKeys(); i++) {
         assert switchInsn.targetBlockIndices()[i] != switchInsn.getFallthroughBlockIndex();
-        EnumValueInfo valueInfo = info.valueInfoMap.get(info.indexMap.get(switchInsn.getKey(i)));
+        DexField field = info.indexMap.get(switchInsn.getKey(i));
+        EnumValueInfo valueInfo = info.valueInfoMap.getEnumValueInfo(field);
         targetMap.put(valueInfo.ordinal, switchInsn.targetBlockIndices()[i]);
       }
       int[] keys = targetMap.keySet().toIntArray();
@@ -190,7 +191,7 @@
     final Instruction arrayGet;
     public final Instruction staticGet;
     final Int2ReferenceMap<DexField> indexMap;
-    final Map<DexField, EnumValueInfo> valueInfoMap;
+    final EnumValueInfoMap valueInfoMap;
 
     private EnumSwitchInfo(
         DexType enumClass,
@@ -198,7 +199,7 @@
         Instruction arrayGet,
         Instruction staticGet,
         Int2ReferenceMap<DexField> indexMap,
-        Map<DexField, EnumValueInfo> valueInfoMap) {
+        EnumValueInfoMap valueInfoMap) {
       this.enumClass = enumClass;
       this.ordinalInvoke = ordinalInvoke;
       this.arrayGet = arrayGet;
@@ -221,11 +222,10 @@
    *
    * </blockquote>
    *
-   * and extracts the components and the index and ordinal maps. See {@link EnumInfoMapCollector}
-   * and {@link SwitchMapCollector} for details.
+   * and extracts the components and the index and ordinal maps. See {@link
+   * EnumValueInfoMapCollector} and {@link SwitchMapCollector} for details.
    */
-  private EnumSwitchInfo analyzeSwitchOverEnum(Instruction switchInsn) {
-    AppInfoWithLiveness appInfo = appView.appInfo();
+  private EnumSwitchInfo analyzeSwitchOverEnum(IntSwitch switchInsn) {
     Instruction input = switchInsn.inValues().get(0).definition;
     if (input == null || !input.isArrayGet()) {
       return null;
@@ -237,8 +237,8 @@
     }
     InvokeVirtual ordinalInvoke = index.asInvokeVirtual();
     DexMethod ordinalMethod = ordinalInvoke.getInvokedMethod();
-    DexClass enumClass = appInfo.definitionFor(ordinalMethod.holder);
-    DexItemFactory dexItemFactory = appInfo.dexItemFactory();
+    DexClass enumClass = appView.definitionFor(ordinalMethod.holder);
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
     // After member rebinding, enumClass will be the actual java.lang.Enum class.
     if (enumClass == null
         || (!enumClass.accessFlags.isEnum() && enumClass.type != dexItemFactory.enumType)
@@ -252,14 +252,18 @@
       return null;
     }
     StaticGet staticGet = array.asStaticGet();
-    Int2ReferenceMap<DexField> indexMap = appInfo.getSwitchMapFor(staticGet.getField());
+    Int2ReferenceMap<DexField> indexMap = appView.appInfo().getSwitchMap(staticGet.getField());
     if (indexMap == null || indexMap.isEmpty()) {
       return null;
     }
-    // Due to member rebinding, only the fields are certain to provide the actual enums
-    // class.
+    for (int key : switchInsn.getKeys()) {
+      if (!indexMap.containsKey(key)) {
+        return null;
+      }
+    }
+    // Due to member rebinding, only the fields are certain to provide the actual enums class.
     DexType enumType = indexMap.values().iterator().next().holder;
-    Map<DexField, EnumValueInfo> valueInfoMap = appInfo.getEnumValueInfoMapFor(enumType);
+    EnumValueInfoMap valueInfoMap = appView.appInfo().getEnumValueInfoMap(enumType);
     if (valueInfoMap == null) {
       return null;
     }
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 91aa098..33c7b2a 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
@@ -31,12 +31,20 @@
 
   public void fixupClassTypeReferences(
       Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
-    if (dynamicLowerBoundType != null) {
-      dynamicLowerBoundType = dynamicLowerBoundType.fixupClassTypeReferences(mapping, appView);
-    }
     if (dynamicUpperBoundType != null) {
       dynamicUpperBoundType = dynamicUpperBoundType.fixupClassTypeReferences(mapping, appView);
     }
+    if (dynamicLowerBoundType != null) {
+      TypeLatticeElement dynamicLowerBoundType =
+          this.dynamicLowerBoundType.fixupClassTypeReferences(mapping, appView);
+      if (dynamicLowerBoundType.isClassType()) {
+        this.dynamicLowerBoundType = dynamicLowerBoundType.asClassTypeLatticeElement();
+      } else {
+        assert dynamicLowerBoundType.isPrimitive();
+        this.dynamicLowerBoundType = null;
+        this.dynamicUpperBoundType = null;
+      }
+    }
   }
 
   @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 c241c0a..3c689a7 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
@@ -177,7 +177,13 @@
   public synchronized void methodReturnsAbstractValue(
       DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue value) {
     if (appView.appInfo().mayPropagateValueFor(method.method)) {
-      getMethodOptimizationInfoForUpdating(method).markReturnsAbstractValue(value);
+      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);
     }
   }
 
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 d2e78f4..3b2a85b 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
@@ -147,8 +147,16 @@
           returnsObjectWithUpperBoundType.fixupClassTypeReferences(mapping, appView);
     }
     if (returnsObjectWithLowerBoundType != null) {
-      returnsObjectWithLowerBoundType =
-          returnsObjectWithLowerBoundType.fixupClassTypeReferences(mapping, appView);
+      TypeLatticeElement returnsObjectWithLowerBoundType =
+          this.returnsObjectWithLowerBoundType.fixupClassTypeReferences(mapping, appView);
+      if (returnsObjectWithLowerBoundType.isClassType()) {
+        this.returnsObjectWithLowerBoundType =
+            returnsObjectWithLowerBoundType.asClassTypeLatticeElement();
+      } else {
+        assert returnsObjectWithLowerBoundType.isPrimitive();
+        this.returnsObjectWithUpperBoundType = DefaultMethodOptimizationInfo.UNKNOWN_TYPE;
+        this.returnsObjectWithLowerBoundType = DefaultMethodOptimizationInfo.UNKNOWN_CLASS_TYPE;
+      }
     }
   }
 
@@ -376,8 +384,6 @@
   }
 
   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 54a50fe..6b3cf4c 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,7 +4,9 @@
 
 package com.android.tools.r8.ir.optimize.info.field;
 
+import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
+import java.util.function.BiConsumer;
 
 /**
  * Represents that no information is known about the way a constructor initializes the instance
@@ -23,6 +25,13 @@
   }
 
   @Override
+  public void forEach(
+      DexDefinitionSupplier definitions,
+      BiConsumer<DexEncodedField, InstanceFieldInitializationInfo> consumer) {
+    // Intentionally empty.
+  }
+
+  @Override
   public InstanceFieldInitializationInfo get(DexEncodedField field) {
     return UnknownInstanceFieldInitializationInfo.getInstance();
   }
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 4912d46..ed1c4d3 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,10 +4,12 @@
 
 package com.android.tools.r8.ir.optimize.info.field;
 
+import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.function.BiConsumer;
 
 /**
  * A mapping from instance fields of a class to information about how a particular constructor
@@ -22,6 +24,10 @@
     return new Builder();
   }
 
+  public abstract void forEach(
+      DexDefinitionSupplier definitions,
+      BiConsumer<DexEncodedField, InstanceFieldInitializationInfo> consumer);
+
   public abstract InstanceFieldInitializationInfo get(DexEncodedField field);
 
   public abstract boolean isEmpty();
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 bdaaf78..5883be6 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,11 @@
 
 package com.android.tools.r8.ir.optimize.info.field;
 
+import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import java.util.Map;
+import java.util.function.BiConsumer;
 
 /** See {@link InstanceFieldArgumentInitializationInfo}. */
 public class NonTrivialInstanceFieldInitializationInfoCollection
@@ -22,6 +24,21 @@
   }
 
   @Override
+  public void forEach(
+      DexDefinitionSupplier definitions,
+      BiConsumer<DexEncodedField, InstanceFieldInitializationInfo> consumer) {
+    infos.forEach(
+        (field, info) -> {
+          DexEncodedField encodedField = definitions.definitionFor(field);
+          if (encodedField != null) {
+            consumer.accept(encodedField, info);
+          } else {
+            assert false;
+          }
+        });
+  }
+
+  @Override
   public InstanceFieldInitializationInfo get(DexEncodedField field) {
     return infos.getOrDefault(field.field, UnknownInstanceFieldInitializationInfo.getInstance());
   }
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 ab02906..cdec947 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
@@ -251,8 +251,7 @@
         .forEachOrdered(
             lambda -> {
               try {
-                LambdaGroupId id =
-                    KotlinLambdaGroupIdFactory.create(kotlin, lambda, appView.options());
+                LambdaGroupId id = KotlinLambdaGroupIdFactory.create(appView, kotlin, lambda);
                 LambdaGroup group = groups.computeIfAbsent(id, LambdaGroupId::createGroup);
                 group.add(lambda);
                 lambdas.put(lambda.type, group);
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 07c1b08..df6f812 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
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.code.ReturnVoid;
 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.DexEncodedMethod;
@@ -21,6 +22,7 @@
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
 import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
 import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.collect.Lists;
 import java.util.List;
@@ -123,10 +125,16 @@
 
   // Specialized group id.
   final static class GroupId extends KotlinLambdaGroupId {
-    GroupId(String capture, DexType iface,
-        String pkg, String signature, DexEncodedMethod mainMethod,
-        InnerClassAttribute inner, EnclosingMethodAttribute enclosing) {
-      super(capture, iface, pkg, signature, mainMethod, inner, enclosing);
+    GroupId(
+        AppView<AppInfoWithLiveness> appView,
+        String capture,
+        DexType iface,
+        String pkg,
+        String signature,
+        DexEncodedMethod mainMethod,
+        InnerClassAttribute inner,
+        EnclosingMethodAttribute enclosing) {
+      super(appView, capture, iface, pkg, signature, mainMethod, inner, enclosing);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
index 6957cb1..8f26df4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.lambda.kotlin;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
@@ -11,15 +12,17 @@
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaStructureError;
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroupId;
 import com.android.tools.r8.kotlin.Kotlin;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 final class JStyleLambdaGroupIdFactory extends KotlinLambdaGroupIdFactory {
   static final JStyleLambdaGroupIdFactory INSTANCE = new JStyleLambdaGroupIdFactory();
 
   @Override
-  LambdaGroupId validateAndCreate(Kotlin kotlin, DexClass lambda, InternalOptions options)
+  LambdaGroupId validateAndCreate(
+      AppView<AppInfoWithLiveness> appView, Kotlin kotlin, DexClass lambda)
       throws LambdaStructureError {
-    boolean accessRelaxed = options.getProguardConfiguration().isAccessModificationAllowed();
+    boolean accessRelaxed =
+        appView.options().getProguardConfiguration().isAccessModificationAllowed();
 
     assert lambda.hasKotlinInfo() && lambda.getKotlinInfo().isSyntheticClass();
     assert lambda.getKotlinInfo().asSyntheticClass().isJavaStyleLambda();
@@ -35,12 +38,18 @@
     String captureSignature = validateInstanceFields(lambda, accessRelaxed);
     validateDirectMethods(lambda);
     DexEncodedMethod mainMethod = validateVirtualMethods(lambda);
-    String genericSignature = validateAnnotations(kotlin, lambda);
+    String genericSignature = validateAnnotations(appView, kotlin, lambda);
     InnerClassAttribute innerClass = validateInnerClasses(lambda);
 
-    return new JStyleLambdaGroup.GroupId(captureSignature, iface,
+    return new JStyleLambdaGroup.GroupId(
+        appView,
+        captureSignature,
+        iface,
         accessRelaxed ? "" : lambda.type.getPackageDescriptor(),
-        genericSignature, mainMethod, innerClass, lambda.getEnclosingMethod());
+        genericSignature,
+        mainMethod,
+        innerClass,
+        lambda.getEnclosingMethod());
   }
 
   @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 49eaa46..b5f0eaf 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
@@ -8,6 +8,7 @@
 import com.android.tools.r8.code.Const4;
 import com.android.tools.r8.code.ReturnVoid;
 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.DexEncodedMethod;
@@ -24,6 +25,7 @@
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
 import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
 import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.collect.Lists;
 import java.util.List;
@@ -129,10 +131,16 @@
 
   // Specialized group id.
   final static class GroupId extends KotlinLambdaGroupId {
-    GroupId(String capture, DexType iface,
-        String pkg, String signature, DexEncodedMethod mainMethod,
-        InnerClassAttribute inner, EnclosingMethodAttribute enclosing) {
-      super(capture, iface, pkg, signature, mainMethod, inner, enclosing);
+    GroupId(
+        AppView<AppInfoWithLiveness> appView,
+        String capture,
+        DexType iface,
+        String pkg,
+        String signature,
+        DexEncodedMethod mainMethod,
+        InnerClassAttribute inner,
+        EnclosingMethodAttribute enclosing) {
+      super(appView, capture, iface, pkg, signature, mainMethod, inner, enclosing);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
index 3d60e0f..eaf154c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.lambda.kotlin;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
@@ -11,15 +12,17 @@
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaStructureError;
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroupId;
 import com.android.tools.r8.kotlin.Kotlin;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 final class KStyleLambdaGroupIdFactory extends KotlinLambdaGroupIdFactory {
   static final KotlinLambdaGroupIdFactory INSTANCE = new KStyleLambdaGroupIdFactory();
 
   @Override
-  LambdaGroupId validateAndCreate(Kotlin kotlin, DexClass lambda, InternalOptions options)
+  LambdaGroupId validateAndCreate(
+      AppView<AppInfoWithLiveness> appView, Kotlin kotlin, DexClass lambda)
       throws LambdaStructureError {
-    boolean accessRelaxed = options.getProguardConfiguration().isAccessModificationAllowed();
+    boolean accessRelaxed =
+        appView.options().getProguardConfiguration().isAccessModificationAllowed();
 
     assert lambda.hasKotlinInfo() && lambda.getKotlinInfo().isSyntheticClass();
     assert lambda.getKotlinInfo().asSyntheticClass().isKotlinStyleLambda();
@@ -35,12 +38,18 @@
     String captureSignature = validateInstanceFields(lambda, accessRelaxed);
     validateDirectMethods(lambda);
     DexEncodedMethod mainMethod = validateVirtualMethods(lambda);
-    String genericSignature = validateAnnotations(kotlin, lambda);
+    String genericSignature = validateAnnotations(appView, kotlin, lambda);
     InnerClassAttribute innerClass = validateInnerClasses(lambda);
 
-    return new KStyleLambdaGroup.GroupId(captureSignature, iface,
+    return new KStyleLambdaGroup.GroupId(
+        appView,
+        captureSignature,
+        iface,
         accessRelaxed ? "" : lambda.type.getPackageDescriptor(),
-        genericSignature, mainMethod, innerClass, lambda.getEnclosingMethod());
+        genericSignature,
+        mainMethod,
+        innerClass,
+        lambda.getEnclosingMethod());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java
index b1fabf6..7f24d79 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.lambda.kotlin;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProto;
@@ -14,6 +15,7 @@
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroupId;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 abstract class KotlinLambdaGroupId implements LambdaGroupId {
   private static final int MISSING_INNER_CLASS_ATTRIBUTE = -1;
@@ -50,8 +52,15 @@
   // access from InnerClassAttribute.
   final int innerClassAccess;
 
-  KotlinLambdaGroupId(String capture, DexType iface, String pkg, String signature,
-      DexEncodedMethod mainMethod, InnerClassAttribute inner, EnclosingMethodAttribute enclosing) {
+  KotlinLambdaGroupId(
+      AppView<AppInfoWithLiveness> appView,
+      String capture,
+      DexType iface,
+      String pkg,
+      String signature,
+      DexEncodedMethod mainMethod,
+      InnerClassAttribute inner,
+      EnclosingMethodAttribute enclosing) {
     assert capture != null && iface != null && pkg != null && mainMethod != null;
     assert inner == null || (inner.isAnonymous() && inner.getOuter() == null);
     this.capture = capture;
@@ -60,8 +69,8 @@
     this.signature = signature;
     this.mainMethodName = mainMethod.method.name;
     this.mainMethodProto = mainMethod.method.proto;
-    this.mainMethodAnnotations = mainMethod.annotations();
-    this.mainMethodParamAnnotations = mainMethod.parameterAnnotationsList;
+    this.mainMethodAnnotations = mainMethod.liveAnnotations(appView);
+    this.mainMethodParamAnnotations = mainMethod.liveParameterAnnotations(appView);
     this.innerClassAccess = inner != null ? inner.getAccess() : MISSING_INNER_CLASS_ATTRIBUTE;
     this.enclosing = enclosing;
     this.hash = computeHashCode();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
index 9344464..91ff482 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.lambda.kotlin;
 
 import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -15,7 +16,7 @@
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaStructureError;
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroupId;
 import com.android.tools.r8.kotlin.Kotlin;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.List;
 
 public abstract class KotlinLambdaGroupIdFactory implements KotlinLambdaConstants {
@@ -29,19 +30,21 @@
   // At this point we only perform high-level checks before qualifying the lambda as a candidate
   // for merging and assigning lambda group id. We can NOT perform checks on method bodies since
   // they may not be converted yet, we'll do that in KStyleLambdaClassValidator.
-  public static LambdaGroupId create(Kotlin kotlin, DexClass lambda, InternalOptions options)
+  public static LambdaGroupId create(
+      AppView<AppInfoWithLiveness> appView, Kotlin kotlin, DexClass lambda)
       throws LambdaStructureError {
 
     assert lambda.hasKotlinInfo() && lambda.getKotlinInfo().isSyntheticClass();
     if (lambda.getKotlinInfo().asSyntheticClass().isKotlinStyleLambda()) {
-      return KStyleLambdaGroupIdFactory.INSTANCE.validateAndCreate(kotlin, lambda, options);
+      return KStyleLambdaGroupIdFactory.INSTANCE.validateAndCreate(appView, kotlin, lambda);
     }
 
     assert lambda.getKotlinInfo().asSyntheticClass().isJavaStyleLambda();
-    return JStyleLambdaGroupIdFactory.INSTANCE.validateAndCreate(kotlin, lambda, options);
+    return JStyleLambdaGroupIdFactory.INSTANCE.validateAndCreate(appView, kotlin, lambda);
   }
 
-  abstract LambdaGroupId validateAndCreate(Kotlin kotlin, DexClass lambda, InternalOptions options)
+  abstract LambdaGroupId validateAndCreate(
+      AppView<AppInfoWithLiveness> appView, Kotlin kotlin, DexClass lambda)
       throws LambdaStructureError;
 
   abstract void validateSuperclass(Kotlin kotlin, DexClass lambda) throws LambdaStructureError;
@@ -99,9 +102,10 @@
     return true;
   }
 
-  String validateAnnotations(Kotlin kotlin, DexClass lambda) throws LambdaStructureError {
+  String validateAnnotations(AppView<AppInfoWithLiveness> appView, Kotlin kotlin, DexClass lambda)
+      throws LambdaStructureError {
     String signature = null;
-    for (DexAnnotation annotation : lambda.annotations().annotations) {
+    for (DexAnnotation annotation : lambda.liveAnnotations(appView).annotations) {
       if (DexAnnotation.isSignatureAnnotation(annotation, kotlin.factory)) {
         signature = DexAnnotation.getSignature(annotation);
         continue;
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index ed47e54..c7d94d4 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -156,7 +156,7 @@
 
   @Override
   public final void buildPrelude(IRBuilder builder) {
-    builder.buildArgumentsWithUnusedArgumentStubs(
+    builder.buildArgumentsWithRewrittenPrototypeChanges(
         0, builder.getMethod(), DexSourceCode::doNothingWriteConsumer);
   }
 
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureAction.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureAction.java
index 7cb3506..81c5413 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureAction.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureAction.java
@@ -7,6 +7,8 @@
 /**
  * Actions triggered by the generic signature parser.
  */
+// TODO(b/129925954): Deprecate this once ...graph.GenericSignature is ready and rewriter is
+//   reimplemented based on the internal encoding and transformation logic.
 public interface GenericSignatureAction<T> {
 
   enum ParserPosition {
@@ -15,15 +17,15 @@
     MEMBER_ANNOTATION
   }
 
-  public void parsedSymbol(char symbol);
+  void parsedSymbol(char symbol);
 
-  public void parsedIdentifier(String identifier);
+  void parsedIdentifier(String identifier);
 
-  public T parsedTypeName(String name, ParserPosition isTopLevel);
+  T parsedTypeName(String name, ParserPosition isTopLevel);
 
-  public T parsedInnerTypeName(T enclosingType, String name);
+  T parsedInnerTypeName(T enclosingType, String name);
 
-  public void start();
+  void start();
 
-  public void stop();
+  void stop();
 }
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
index 74b17d6..b953586 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
@@ -26,7 +26,7 @@
  *
  * FieldTypeSignature ::=
  *     ClassTypeSignature | ArrayTypeSignature | TypeVariableSignature.
- * ArrayTypeSignature ::= "[" TypSignature.
+ * ArrayTypeSignature ::= "[" TypeSignature.
  *
  * ClassTypeSignature ::=
  *     "L" {Ident "/"} Ident OptTypeArguments {"." Ident OptTypeArguments} ";".
@@ -49,6 +49,8 @@
  * VoidDescriptor ::= "V".
  * </pre>
  */
+// TODO(b/129925954): Deprecate this once ...graph.GenericSignature is ready and rewriter is
+//   reimplemented based on the internal encoding and transformation logic.
 public class GenericSignatureParser<T> {
 
   private GenericSignatureAction<T> actions;
@@ -136,7 +138,7 @@
   // Parser:
   //
 
-  void parseClassSignature() {
+  private void parseClassSignature() {
     // ClassSignature ::= OptFormalTypeParameters SuperclassSignature {SuperinterfaceSignature}.
 
     parseOptFormalTypeParameters();
@@ -150,7 +152,7 @@
     }
   }
 
-  void parseOptFormalTypeParameters() {
+  private void parseOptFormalTypeParameters() {
     // OptFormalTypeParameters ::= ["<" FormalTypeParameter {FormalTypeParameter} ">"].
 
     if (symbol == '<') {
@@ -168,7 +170,7 @@
     }
   }
 
-  void updateFormalTypeParameter() {
+  private void updateFormalTypeParameter() {
     // FormalTypeParameter ::= Ident ClassBound {InterfaceBound}.
     scanIdentifier();
     assert identifier != null;
@@ -279,7 +281,7 @@
     }
   }
 
- private  void updateTypeVariableSignature() {
+  private void updateTypeVariableSignature() {
     // TypeVariableSignature ::= "T" Ident ";".
     actions.parsedSymbol(symbol);
     expect('T');
@@ -384,7 +386,7 @@
     }
   }
 
- private  boolean isStopSymbol(char ch) {
+  private boolean isStopSymbol(char ch) {
     switch (ch) {
       case ':':
       case '/':
@@ -405,7 +407,6 @@
       if (!isStopSymbol(symbol)) {
         identBuf.append(symbol);
 
-        // FINDBUGS
         char[] bufferLocal = buffer;
         assert bufferLocal != null;
         do {
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index 67d2fa9..6e88574 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -10,13 +10,12 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -30,10 +29,12 @@
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 
+// TODO(b/129925954): Reimplement this by using the internal encoding and transformation logic.
 public class GenericSignatureRewriter {
 
   private final AppView<AppInfoWithLiveness> appView;
   private final Map<DexType, DexString> renaming;
+  private final InternalOptions options;
   private final Reporter reporter;
 
   public GenericSignatureRewriter(AppView<AppInfoWithLiveness> appView) {
@@ -44,7 +45,8 @@
       AppView<AppInfoWithLiveness> appView, Map<DexType, DexString> renaming) {
     this.appView = appView;
     this.renaming = renaming;
-    this.reporter = appView.options().reporter;
+    this.options = appView.options();
+    this.reporter = options.reporter;
   }
 
   public void run(Iterable<? extends DexProgramClass> classes, ExecutorService executorService)
@@ -65,7 +67,8 @@
                   clazz.annotations(),
                   genericSignatureParser::parseClassSignature,
                   genericSignatureCollector::getRenamedSignature,
-                  (signature, e) -> parseError(clazz, clazz.getOrigin(), signature, e)));
+                  (signature, e) ->
+                      options.warningInvalidSignature(clazz, clazz.getOrigin(), signature, e)));
           clazz.forEachField(
               field ->
                   field.setAnnotations(
@@ -73,7 +76,9 @@
                           field.annotations(),
                           genericSignatureParser::parseFieldSignature,
                           genericSignatureCollector::getRenamedSignature,
-                          (signature, e) -> parseError(field, clazz.getOrigin(), signature, e))));
+                          (signature, e) ->
+                              options.warningInvalidSignature(
+                                  field, clazz.getOrigin(), signature, e))));
           clazz.forEachMethod(
               method ->
                   method.setAnnotations(
@@ -81,7 +86,9 @@
                           method.annotations(),
                           genericSignatureParser::parseMethodSignature,
                           genericSignatureCollector::getRenamedSignature,
-                          (signature, e) -> parseError(method, clazz.getOrigin(), signature, e))));
+                          (signature, e) ->
+                              options.warningInvalidSignature(
+                                  method, clazz.getOrigin(), signature, e))));
         },
         executorService
     );
@@ -137,29 +144,6 @@
     return new DexAnnotationSet(prunedAnnotations);
   }
 
-  private void parseError(
-      DexDefinition item, Origin origin, String signature, GenericSignatureFormatError e) {
-    StringBuilder message = new StringBuilder("Invalid signature '");
-    message.append(signature);
-    message.append("' for ");
-    if (item.isDexClass()) {
-      message.append("class ");
-      message.append((item.asDexClass()).getType().toSourceString());
-    } else if (item.isDexEncodedField()) {
-      message.append("field ");
-      message.append(item.toSourceString());
-    } else {
-      assert item.isDexEncodedMethod();
-      message.append("method ");
-      message.append(item.toSourceString());
-    }
-    message.append(".\n");
-    message.append("Signature is ignored and will not be present in the output.\n");
-    message.append("Parser error: ");
-    message.append(e.getMessage());
-    reporter.warning(new StringDiagnostic(message.toString(), origin));
-  }
-
   private class GenericSignatureCollector implements GenericSignatureAction<DexType> {
     private StringBuilder renamedSignature;
     private final DexProgramClass currentClassContext;
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 703deea..4d20f6b 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -53,7 +53,9 @@
     } else {
       newHolder = firstLibraryClass(target.holder, original.holder);
     }
-    return appView.dexItemFactory().createMethod(newHolder, original.proto, original.name);
+    return newHolder == null
+        ? original
+        : appView.dexItemFactory().createMethod(newHolder, original.proto, original.name);
   }
 
   private DexField validTargetFor(DexField target, DexField original,
@@ -69,12 +71,17 @@
     } else {
       newHolder = firstLibraryClass(target.holder, original.holder);
     }
-    return appView.dexItemFactory().createField(newHolder, original.type, original.name);
+    return newHolder == null
+        ? original
+        : appView.dexItemFactory().createField(newHolder, original.type, original.name);
   }
 
   private <T> DexType firstLibraryClassForInterfaceTarget(T target, DexType current,
       BiFunction<DexClass, T, ?> lookup) {
     DexClass clazz = appView.definitionFor(current);
+    if (clazz == null) {
+      return null;
+    }
     Object potential = lookup.apply(clazz, target);
     if (potential != null) {
       // Found, return type.
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationMatchResult.java b/src/main/java/com/android/tools/r8/shaking/AnnotationMatchResult.java
new file mode 100644
index 0000000..f5a3bbf
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationMatchResult.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.graph.DexAnnotation;
+
+public abstract class AnnotationMatchResult {
+
+  public boolean isConcreteAnnotationMatchResult() {
+    return false;
+  }
+
+  public ConcreteAnnotationMatchResult asConcreteAnnotationMatchResult() {
+    return null;
+  }
+
+  static class AnnotationsIgnoredMatchResult extends AnnotationMatchResult {
+
+    private static final AnnotationsIgnoredMatchResult INSTANCE =
+        new AnnotationsIgnoredMatchResult();
+
+    private AnnotationsIgnoredMatchResult() {}
+
+    public static AnnotationsIgnoredMatchResult getInstance() {
+      return INSTANCE;
+    }
+  }
+
+  static class ConcreteAnnotationMatchResult extends AnnotationMatchResult {
+
+    private final DexAnnotation matchedAnnotation;
+
+    public ConcreteAnnotationMatchResult(DexAnnotation matchedAnnotation) {
+      this.matchedAnnotation = matchedAnnotation;
+    }
+
+    public DexAnnotation getMatchedAnnotation() {
+      return matchedAnnotation;
+    }
+
+    @Override
+    public boolean isConcreteAnnotationMatchResult() {
+      return true;
+    }
+
+    @Override
+    public ConcreteAnnotationMatchResult asConcreteAnnotationMatchResult() {
+      return this;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 6cfa6d8..6bf54dd 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -19,8 +19,8 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
-import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
@@ -28,26 +28,50 @@
 public class AnnotationRemover {
 
   private final AppView<AppInfoWithLiveness> appView;
-  private final ProguardKeepAttributes keep;
+  private final Set<DexAnnotation> annotationsToRetain;
   private final Set<DexType> classesToRetainInnerClassAttributeFor;
+  private final ProguardKeepAttributes keep;
 
   public AnnotationRemover(
       AppView<AppInfoWithLiveness> appView, Set<DexType> classesToRetainInnerClassAttributeFor) {
+    this(appView, classesToRetainInnerClassAttributeFor, ImmutableSet.of());
+  }
+
+  private AnnotationRemover(
+      AppView<AppInfoWithLiveness> appView,
+      Set<DexType> classesToRetainInnerClassAttributeFor,
+      Set<DexAnnotation> annotationsToRetain) {
     this.appView = appView;
-    this.keep = appView.options().getProguardConfiguration().getKeepAttributes();
+    this.annotationsToRetain = annotationsToRetain;
     this.classesToRetainInnerClassAttributeFor = classesToRetainInnerClassAttributeFor;
+    this.keep = appView.options().getProguardConfiguration().getKeepAttributes();
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public Set<DexType> getClassesToRetainInnerClassAttributeFor() {
+    return classesToRetainInnerClassAttributeFor;
   }
 
   /** Used to filter annotations on classes, methods and fields. */
   private boolean filterAnnotations(DexDefinition holder, DexAnnotation annotation) {
-    return shouldKeepAnnotation(holder, annotation, isAnnotationTypeLive(annotation), appView);
+    return annotationsToRetain.contains(annotation)
+        || shouldKeepAnnotation(appView, holder, annotation, isAnnotationTypeLive(annotation));
   }
 
-  static boolean shouldKeepAnnotation(
+  public static boolean shouldKeepAnnotation(
+      AppView<AppInfoWithLiveness> appView, DexDefinition holder, DexAnnotation annotation) {
+    return shouldKeepAnnotation(
+        appView, holder, annotation, isAnnotationTypeLive(annotation, appView));
+  }
+
+  public static boolean shouldKeepAnnotation(
+      AppView<?> appView,
       DexDefinition holder,
       DexAnnotation annotation,
-      boolean isAnnotationTypeLive,
-      AppView<?> appView) {
+      boolean isAnnotationTypeLive) {
     ProguardKeepAttributes config =
         appView.options().getProguardConfiguration() != null
             ? appView.options().getProguardConfiguration().getKeepAttributes()
@@ -107,6 +131,11 @@
   }
 
   private boolean isAnnotationTypeLive(DexAnnotation annotation) {
+    return isAnnotationTypeLive(annotation, appView);
+  }
+
+  private static boolean isAnnotationTypeLive(
+      DexAnnotation annotation, AppView<AppInfoWithLiveness> appView) {
     DexType annotationType = annotation.annotation.type.toBaseType(appView.dexItemFactory());
     return appView.appInfo().isNonProgramTypeOrLiveProgramType(annotationType);
   }
@@ -115,6 +144,9 @@
    * Used to filter annotations on parameters.
    */
   private boolean filterParameterAnnotations(DexAnnotation annotation) {
+    if (annotationsToRetain.contains(annotation)) {
+      return true;
+    }
     switch (annotation.visibility) {
       case DexAnnotation.VISIBILITY_SYSTEM:
         return false;
@@ -164,68 +196,6 @@
     return false;
   }
 
-  public static Set<DexType> computeClassesToRetainInnerClassAttributeFor(
-      AppView<? extends AppInfoWithLiveness> appView) {
-    // In case of minification for certain inner classes we need to retain their InnerClass
-    // attributes because their minified name still needs to be in hierarchical format
-    // (enclosing$inner) otherwise the GenericSignatureRewriter can't produce the correct,
-    // renamed signature.
-
-    // More precisely:
-    // - we're going to retain the InnerClass attribute that refers to the same class as 'inner'
-    // - for live, inner, nonstatic classes
-    // - that are enclosed by a class with a generic signature.
-
-    // In compat mode we always keep all InnerClass attributes (if requested).
-    // If not requested we never keep any. In these cases don't compute eligible classes.
-    if (appView.options().forceProguardCompatibility
-        || !appView.options().getProguardConfiguration().getKeepAttributes().innerClasses) {
-      return Collections.emptySet();
-    }
-
-    // Build lookup table and set of the interesting classes.
-    // enclosingClasses.get(clazz) gives the enclosing class of 'clazz'
-    Map<DexType, DexProgramClass> enclosingClasses = new IdentityHashMap<>();
-    Set<DexProgramClass> genericClasses = Sets.newIdentityHashSet();
-
-    Iterable<DexProgramClass> programClasses = appView.appInfo().classes();
-    for (DexProgramClass clazz : programClasses) {
-      if (hasSignatureAnnotation(clazz, appView.dexItemFactory())) {
-        genericClasses.add(clazz);
-      }
-      for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
-        if ((innerClassAttribute.getAccess() & Constants.ACC_STATIC) == 0
-            && innerClassAttribute.getOuter() == clazz.type) {
-          enclosingClasses.put(innerClassAttribute.getInner(), clazz);
-        }
-      }
-    }
-
-    Set<DexType> result = Sets.newIdentityHashSet();
-    for (DexProgramClass clazz : programClasses) {
-      // If [clazz] is mentioned by a keep rule, it could be used for reflection, and we therefore
-      // need to keep the enclosing method and inner classes attributes, if requested.
-      if (appView.appInfo().isPinned(clazz.type)) {
-        for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
-          DexType inner = innerClassAttribute.getInner();
-          if (appView.appInfo().isNonProgramTypeOrLiveProgramType(inner)) {
-            result.add(inner);
-          }
-          DexType context = innerClassAttribute.getLiveContext(appView.appInfo());
-          if (context != null && appView.appInfo().isNonProgramTypeOrLiveProgramType(context)) {
-            result.add(context);
-          }
-        }
-      }
-      if (clazz.getInnerClassAttributeForThisClass() != null
-          && appView.appInfo().isNonProgramTypeOrLiveProgramType(clazz.type)
-          && hasGenericEnclosingClass(clazz, enclosingClasses, genericClasses)) {
-        result.add(clazz.type);
-      }
-    }
-    return result;
-  }
-
   public void run() {
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       stripAttributes(clazz);
@@ -250,11 +220,11 @@
 
   private DexAnnotation rewriteAnnotation(DexDefinition holder, DexAnnotation original) {
     // Check if we should keep this annotation first.
-    if (!filterAnnotations(holder, original)) {
-      return null;
+    if (filterAnnotations(holder, original)) {
+      // Then, filter out values that refer to dead definitions.
+      return original.rewrite(this::rewriteEncodedAnnotation);
     }
-    // Then, filter out values that refer to dead definitions.
-    return original.rewrite(this::rewriteEncodedAnnotation);
+    return null;
   }
 
   private DexEncodedAnnotation rewriteEncodedAnnotation(DexEncodedAnnotation original) {
@@ -362,4 +332,91 @@
       clazz.members().forEach(DexDefinition::clearAnnotations);
     }
   }
+
+  public static class Builder {
+
+    /**
+     * The set of annotations that were matched by a conditional if rule. These are needed for the
+     * interpretation of if rules in the second round of tree shaking.
+     */
+    private final Set<DexAnnotation> annotationsToRetain = Sets.newIdentityHashSet();
+
+    private Set<DexType> classesToRetainInnerClassAttributeFor;
+
+    public Builder computeClassesToRetainInnerClassAttributeFor(
+        AppView<AppInfoWithLiveness> appView) {
+      assert classesToRetainInnerClassAttributeFor == null;
+      // In case of minification for certain inner classes we need to retain their InnerClass
+      // attributes because their minified name still needs to be in hierarchical format
+      // (enclosing$inner) otherwise the GenericSignatureRewriter can't produce the correct,
+      // renamed signature.
+
+      // More precisely:
+      // - we're going to retain the InnerClass attribute that refers to the same class as 'inner'
+      // - for live, inner, nonstatic classes
+      // - that are enclosed by a class with a generic signature.
+
+      // In compat mode we always keep all InnerClass attributes (if requested).
+      // If not requested we never keep any. In these cases don't compute eligible classes.
+      Set<DexType> result = Sets.newIdentityHashSet();
+      if (!appView.options().forceProguardCompatibility
+          && appView.options().getProguardConfiguration().getKeepAttributes().innerClasses) {
+        // Build lookup table and set of the interesting classes.
+        // enclosingClasses.get(clazz) gives the enclosing class of 'clazz'
+        Map<DexType, DexProgramClass> enclosingClasses = new IdentityHashMap<>();
+        Set<DexProgramClass> genericClasses = Sets.newIdentityHashSet();
+        for (DexProgramClass clazz : appView.appInfo().classes()) {
+          if (hasSignatureAnnotation(clazz, appView.dexItemFactory())) {
+            genericClasses.add(clazz);
+          }
+          for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
+            if ((innerClassAttribute.getAccess() & Constants.ACC_STATIC) == 0
+                && innerClassAttribute.getOuter() == clazz.type) {
+              enclosingClasses.put(innerClassAttribute.getInner(), clazz);
+            }
+          }
+        }
+        for (DexProgramClass clazz : appView.appInfo().classes()) {
+          // If [clazz] is mentioned by a keep rule, it could be used for reflection, and we
+          // therefore
+          // need to keep the enclosing method and inner classes attributes, if requested.
+          if (appView.appInfo().isPinned(clazz.type)) {
+            for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
+              DexType inner = innerClassAttribute.getInner();
+              if (appView.appInfo().isNonProgramTypeOrLiveProgramType(inner)) {
+                result.add(inner);
+              }
+              DexType context = innerClassAttribute.getLiveContext(appView.appInfo());
+              if (context != null && appView.appInfo().isNonProgramTypeOrLiveProgramType(context)) {
+                result.add(context);
+              }
+            }
+          }
+          if (clazz.getInnerClassAttributeForThisClass() != null
+              && appView.appInfo().isNonProgramTypeOrLiveProgramType(clazz.type)
+              && hasGenericEnclosingClass(clazz, enclosingClasses, genericClasses)) {
+            result.add(clazz.type);
+          }
+        }
+      }
+      classesToRetainInnerClassAttributeFor = result;
+      return this;
+    }
+
+    public Builder setClassesToRetainInnerClassAttributeFor(
+        Set<DexType> classesToRetainInnerClassAttributeFor) {
+      this.classesToRetainInnerClassAttributeFor = classesToRetainInnerClassAttributeFor;
+      return this;
+    }
+
+    public void retainAnnotation(DexAnnotation annotation) {
+      annotationsToRetain.add(annotation);
+    }
+
+    public AnnotationRemover build(AppView<AppInfoWithLiveness> appView) {
+      assert classesToRetainInnerClassAttributeFor != null;
+      return new AnnotationRemover(
+          appView, classesToRetainInnerClassAttributeFor, annotationsToRetain);
+    }
+  }
 }
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 fd8742d..a1277ef 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -20,6 +20,8 @@
 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;
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
@@ -174,18 +176,7 @@
   /** A map from switchmap class types to their corresponding switchmaps. */
   final Map<DexField, Int2ReferenceMap<DexField>> switchMaps;
   /** A map from enum types to their value types and ordinals. */
-  final Map<DexType, Map<DexField, EnumValueInfo>> enumValueInfoMaps;
-
-  public static final class EnumValueInfo {
-    /** The anonymous subtype of this specific value or the enum type. */
-    public final DexType type;
-    public final int ordinal;
-
-    public EnumValueInfo(DexType type, int ordinal) {
-      this.type = type;
-      this.ordinal = ordinal;
-    }
-  }
+  final EnumValueInfoMapCollection enumValueInfoMaps;
 
   final Set<DexType> instantiatedLambdas;
 
@@ -228,7 +219,7 @@
       Object2BooleanMap<DexReference> identifierNameStrings,
       Set<DexType> prunedTypes,
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
-      Map<DexType, Map<DexField, EnumValueInfo>> enumValueInfoMaps,
+      EnumValueInfoMapCollection enumValueInfoMaps,
       Set<DexType> instantiatedLambdas,
       Set<DexType> constClassReferences) {
     super(application);
@@ -311,7 +302,7 @@
       Object2BooleanMap<DexReference> identifierNameStrings,
       Set<DexType> prunedTypes,
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
-      Map<DexType, Map<DexField, EnumValueInfo>> enumValueInfoMaps,
+      EnumValueInfoMapCollection enumValueInfoMaps,
       Set<DexType> instantiatedLambdas,
       Set<DexType> constClassReferences) {
     super(appInfoWithSubtyping);
@@ -458,7 +449,7 @@
   public AppInfoWithLiveness(
       AppInfoWithLiveness previous,
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
-      Map<DexType, Map<DexField, EnumValueInfo>> enumValueInfoMaps) {
+      EnumValueInfoMapCollection enumValueInfoMaps) {
     super(previous);
     this.missingTypes = previous.missingTypes;
     this.liveTypes = previous.liveTypes;
@@ -704,12 +695,12 @@
     return result;
   }
 
-  public Map<DexField, EnumValueInfo> getEnumValueInfoMapFor(DexType enumClass) {
+  public EnumValueInfoMap getEnumValueInfoMap(DexType enumType) {
     assert checkIfObsolete();
-    return enumValueInfoMaps.get(enumClass);
+    return enumValueInfoMaps.getEnumValueInfoMap(enumType);
   }
 
-  public Int2ReferenceMap<DexField> getSwitchMapFor(DexField field) {
+  public Int2ReferenceMap<DexField> getSwitchMap(DexField field) {
     assert checkIfObsolete();
     return switchMaps.get(field);
   }
@@ -1065,7 +1056,7 @@
         // Don't rewrite pruned types - the removed types are identified by their original name.
         prunedTypes,
         rewriteReferenceKeys(switchMaps, lens::lookupField),
-        rewriteReferenceKeys(enumValueInfoMaps, lens::lookupType),
+        enumValueInfoMaps.rewrittenWithLens(lens),
         rewriteItems(instantiatedLambdas, lens::lookupType),
         constClassReferences);
   }
@@ -1447,14 +1438,13 @@
     return result == null || !result.isVirtualMethod() ? null : result;
   }
 
-  public AppInfoWithLiveness addSwitchMaps(Map<DexField, Int2ReferenceMap<DexField>> switchMaps) {
+  public AppInfoWithLiveness withSwitchMaps(Map<DexField, Int2ReferenceMap<DexField>> switchMaps) {
     assert checkIfObsolete();
     assert this.switchMaps.isEmpty();
     return new AppInfoWithLiveness(this, switchMaps, enumValueInfoMaps);
   }
 
-  public AppInfoWithLiveness addEnumValueInfoMaps(
-      Map<DexType, Map<DexField, EnumValueInfo>> enumValueInfoMaps) {
+  public AppInfoWithLiveness withEnumValueInfoMaps(EnumValueInfoMapCollection enumValueInfoMaps) {
     assert checkIfObsolete();
     assert this.enumValueInfoMaps.isEmpty();
     return new AppInfoWithLiveness(this, switchMaps, enumValueInfoMaps);
diff --git a/src/main/java/com/android/tools/r8/shaking/ConsequentRootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/ConsequentRootSetBuilder.java
new file mode 100644
index 0000000..2e5edba
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ConsequentRootSetBuilder.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
+
+class ConsequentRootSetBuilder extends RootSetBuilder {
+
+  private final Enqueuer enqueuer;
+
+  ConsequentRootSetBuilder(AppView<? extends AppInfoWithSubtyping> appView, Enqueuer enqueuer) {
+    super(appView, appView.appInfo().app(), null);
+    this.enqueuer = enqueuer;
+  }
+
+  @Override
+  void handleMatchedAnnotation(AnnotationMatchResult annotationMatchResult) {
+    if (enqueuer.getMode().isInitialTreeShaking()
+        && annotationMatchResult.isConcreteAnnotationMatchResult()) {
+      enqueuer.retainAnnotationForFinalTreeShaking(
+          annotationMatchResult.asConcreteAnnotationMatchResult().getMatchedAnnotation());
+    }
+  }
+}
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 4dd45b3..36afff6 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -51,6 +51,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.DirectMappedDexApplication.Builder;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection;
 import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
 import com.android.tools.r8.graph.FieldAccessInfoImpl;
 import com.android.tools.r8.graph.InnerClassAttribute;
@@ -174,6 +175,7 @@
   private RootSet rootSet;
   private ProguardClassFilter dontWarnPatterns;
   private final EnqueuerUseRegistryFactory useRegistryFactory;
+  private AnnotationRemover.Builder annotationRemoverBuilder;
 
   private final Map<DexProgramClass, Set<DexProgramClass>> immediateSubtypesOfLiveTypes =
       new IdentityHashMap<>();
@@ -191,14 +193,6 @@
   private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
 
   /**
-   * This map keeps a view of all virtual methods that are reachable from virtual invokes. A method
-   * is reachable even if no live subtypes exist, so this is not sufficient for inclusion in the
-   * live set.
-   */
-  private final Map<DexProgramClass, ReachableVirtualMethodsSet> reachableVirtualMethods =
-      Maps.newIdentityHashMap();
-
-  /**
    * Tracks the dependency between a method and the super-method it calls, if any. Used to make
    * super methods become live when they become reachable from a live sub-method.
    */
@@ -417,6 +411,10 @@
     return this;
   }
 
+  public void setAnnotationRemoverBuilder(AnnotationRemover.Builder annotationRemoverBuilder) {
+    this.annotationRemoverBuilder = annotationRemoverBuilder;
+  }
+
   private boolean isProgramClass(DexType type) {
     return getProgramClassOrNull(type) != null;
   }
@@ -529,6 +527,7 @@
     }
     populateInstantiatedTypesCache(clazz);
     markTypeAsLive(clazz, witness);
+    transitionDependentItemsForInstantiatedInterface(clazz);
   }
 
   private void enqueueFirstNonSerializableClassInitializer(
@@ -720,27 +719,7 @@
         throw new Unreachable();
     }
 
-    // In similar way as what transitionMethodsForInstantiatedClass does for existing
-    // classes we need to process classes dynamically created by runtime for lambdas.
-    // We make an assumption that such classes are inherited directly from java.lang.Object
-    // and implement all lambda interfaces.
-
-    // The set now contains all virtual methods on the type and its supertype that are reachable.
-    // In a second step, we now look at interfaces. We have to do this in this order due to JVM
-    // semantics for default methods. A default method is only reachable if it is not overridden
-    // in any superclass. Also, it is not defined which default method is chosen if multiple
-    // interfaces define the same default method. Hence, for every interface (direct or indirect),
-    // we have to look at the interface chain and mark default methods as reachable, not taking
-    // the shadowing of other interface chains into account.
-    // See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3
-    // TODO(b/148271337): Support lookupVirtualDispatchTarget(CallSite) and replace this.
-    ScopedDexMethodSet seen = new ScopedDexMethodSet();
-    for (DexType iface : descriptor.interfaces) {
-      DexProgramClass ifaceClazz = getProgramClassOrNull(iface);
-      if (ifaceClazz != null) {
-        transitionDefaultMethodsForInstantiatedClass(iface, seen);
-      }
-    }
+    transitionMethodsForInstantiatedLambda(descriptor);
   }
 
   boolean traceCheckCast(DexType type, DexEncodedMethod currentMethod) {
@@ -1177,13 +1156,6 @@
     return clazz -> graphReporter.reportClassReferencedFrom(clazz, currentMethod);
   }
 
-  private void transitionReachableVirtualMethods(DexProgramClass clazz, ScopedDexMethodSet seen) {
-    ReachableVirtualMethodsSet reachableMethods = reachableVirtualMethods.get(clazz);
-    if (reachableMethods != null) {
-      transitionNonAbstractMethodsToLiveAndShadow(clazz, reachableMethods, seen);
-    }
-  }
-
   private DexMethod getInvokeSuperTarget(DexMethod method, DexEncodedMethod currentMethod) {
     DexClass methodHolderClass = appView.definitionFor(method.holder);
     if (methodHolderClass != null && methodHolderClass.isInterface()) {
@@ -1342,8 +1314,8 @@
       }
     }
 
-    // TODO(b/149729626): Consider marking types with a dependent instance constructor as being
-    //  instantiated.
+    rootSet.forEachDependentInstanceConstructor(
+        holder, appView, this::enqueueHolderWithDependentInstanceConstructor);
     rootSet.forEachDependentStaticMember(holder, appView, this::enqueueDependentItem);
     compatEnqueueHolderIfDependentNonStaticMember(
         holder, rootSet.getDependentKeepClassCompatRule(holder.getType()));
@@ -1388,9 +1360,24 @@
         // The interface is needed if it has a live default interface method or field, though.
         // Therefore, we record that this implemented-by edge has not been reported, such that we
         // can report it in the future if one its members becomes live.
-        unusedInterfaceTypes
-            .computeIfAbsent(clazz, ignore -> Sets.newIdentityHashSet())
-            .add(implementer);
+        WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList();
+        worklist.addIfNotSeen(clazz);
+        while (worklist.hasNext()) {
+          DexProgramClass current = worklist.next();
+          if (liveTypes.contains(current)) {
+            continue;
+          }
+          Set<DexProgramClass> implementors =
+              unusedInterfaceTypes.computeIfAbsent(current, ignore -> Sets.newIdentityHashSet());
+          if (implementors.add(implementer)) {
+            for (DexType iface : current.interfaces.values) {
+              DexProgramClass definition = getProgramClassOrNull(iface);
+              if (definition != null) {
+                worklist.addIfNotSeen(definition);
+              }
+            }
+          }
+        }
       }
     }
   }
@@ -1400,6 +1387,13 @@
     internalEnqueueRootItem(consequent, reasons, precondition);
   }
 
+  private void enqueueHolderWithDependentInstanceConstructor(
+      DexProgramClass clazz,
+      DexEncodedMethod instanceInitializer,
+      Set<ProguardKeepRuleBase> reasons) {
+    enqueueRootItem(clazz, reasons);
+  }
+
   private void processAnnotations(DexProgramClass holder, DexDefinition annotatedItem) {
     processAnnotations(holder, annotatedItem, annotatedItem.annotations());
   }
@@ -1426,7 +1420,7 @@
     DexClass clazz = appView.definitionFor(type);
     boolean annotationTypeIsLibraryClass = clazz == null || clazz.isNotProgramClass();
     boolean isLive = annotationTypeIsLibraryClass || liveTypes.contains(clazz.asProgramClass());
-    if (!shouldKeepAnnotation(annotatedItem, annotation, isLive, appView)) {
+    if (!shouldKeepAnnotation(appView, annotatedItem, annotation, isLive)) {
       // Remember this annotation for later.
       if (!annotationTypeIsLibraryClass) {
         deferredAnnotations.computeIfAbsent(type, ignore -> new HashSet<>()).add(annotation);
@@ -1670,7 +1664,7 @@
     markDirectAndIndirectClassInitializersAsLive(clazz);
     // For all methods of the class, if we have seen a call, mark the method live.
     // We only do this for virtual calls, as the other ones will be done directly.
-    transitionMethodsForInstantiatedClass(clazz, keepReason);
+    transitionMethodsForInstantiatedClass(clazz);
     // For all instance fields visible from the class, mark them live if we have seen a read.
     transitionFieldsForInstantiatedClass(clazz);
     // Add all dependent instance members to the workqueue.
@@ -1695,33 +1689,42 @@
     }
   }
 
+  private void transitionMethodsForInstantiatedLambda(LambdaDescriptor lambda) {
+    transitionMethodsForInstantiatedObject(
+        InstantiatedObject.of(lambda),
+        definitionFor(appInfo.dexItemFactory().objectType),
+        lambda.interfaces);
+  }
+
+  private void transitionMethodsForInstantiatedClass(DexProgramClass clazz) {
+    assert !clazz.isAnnotation();
+    assert !clazz.isInterface();
+    transitionMethodsForInstantiatedObject(
+        InstantiatedObject.of(clazz), clazz, Collections.emptyList());
+  }
+
   /**
-   * Marks all methods live that are overrides of reachable methods for a given class.
+   * Marks all methods live that are overrides of reachable methods for a given instantiation.
    *
-   * <p>Only reachable methods in the hierarchy of the given class and above are considered, and
-   * only the lowest such reachable target (ie, mirroring resolution). All library and classpath
+   * <p>Only reachable methods in the hierarchy of the given instantiation and above are considered,
+   * and only the lowest such reachable target (ie, mirroring resolution). All library and classpath
    * methods are considered reachable.
    */
-  private void transitionMethodsForInstantiatedClass(
-      DexProgramClass instantiatedClass, KeepReason instantiationReason) {
-    assert !instantiatedClass.isAnnotation();
-    assert !instantiatedClass.isInterface();
+  private void transitionMethodsForInstantiatedObject(
+      InstantiatedObject instantiation, DexClass clazz, List<DexType> interfaces) {
     ScopedDexMethodSet seen = new ScopedDexMethodSet();
     WorkList<DexType> worklist = WorkList.newIdentityWorkList();
+    worklist.addIfNotSeen(interfaces);
     // First we lookup and mark all targets on the instantiated class for each reachable method in
     // the super chain (inclusive).
-    {
-      DexClass clazz = instantiatedClass;
-      while (clazz != null) {
-        if (clazz.isProgramClass()) {
-          markProgramMethodOverridesAsLive(
-              instantiatedClass, clazz.asProgramClass(), seen, instantiationReason);
-        } else {
-          markLibraryAndClasspathMethodOverridesAsLive(instantiatedClass, clazz);
-        }
-        worklist.addIfNotSeen(Arrays.asList(clazz.interfaces.values));
-        clazz = clazz.superType != null ? definitionFor(clazz.superType) : null;
+    while (clazz != null) {
+      if (clazz.isProgramClass()) {
+        markProgramMethodOverridesAsLive(instantiation, clazz.asProgramClass(), seen);
+      } else {
+        markLibraryAndClasspathMethodOverridesAsLive(instantiation, clazz);
       }
+      worklist.addIfNotSeen(Arrays.asList(clazz.interfaces.values));
+      clazz = clazz.superType != null ? definitionFor(clazz.superType) : null;
     }
     // The targets for methods on the type and its supertype that are reachable are now marked.
     // In a second step, we look at interfaces. We order the search this way such that a
@@ -1735,10 +1738,9 @@
       }
       assert iface.superType == appInfo.dexItemFactory().objectType;
       if (iface.isNotProgramClass()) {
-        markLibraryAndClasspathMethodOverridesAsLive(instantiatedClass, iface);
+        markLibraryAndClasspathMethodOverridesAsLive(instantiation, iface);
       } else {
-        markProgramMethodOverridesAsLive(
-            instantiatedClass, iface.asProgramClass(), seen, instantiationReason);
+        markProgramMethodOverridesAsLive(instantiation, iface.asProgramClass(), seen);
       }
       worklist.addIfNotSeen(Arrays.asList(iface.interfaces.values));
     }
@@ -1749,22 +1751,20 @@
   }
 
   private void markProgramMethodOverridesAsLive(
-      DexProgramClass instantiatedClass,
+      InstantiatedObject instantiation,
       DexProgramClass superClass,
-      ScopedDexMethodSet seenMethods,
-      KeepReason instantiationReason) {
+      ScopedDexMethodSet seenMethods) {
     for (DexEncodedMethod resolution : getReachableVirtualResolutions(superClass)) {
       if (seenMethods.addMethod(resolution)) {
-        markLiveOverrides(instantiatedClass, superClass, resolution, instantiationReason);
+        markLiveOverrides(instantiation, superClass, resolution);
       }
     }
   }
 
   private void markLiveOverrides(
-      DexProgramClass instantiatedClass,
+      InstantiatedObject instantiation,
       DexProgramClass reachableHolder,
-      DexEncodedMethod reachableMethod,
-      KeepReason instantiationReason) {
+      DexEncodedMethod reachableMethod) {
     assert reachableHolder.type == reachableMethod.method.holder;
     // The validity of the reachable method is checked at the point it becomes "reachable" and is
     // resolved. If the method is private, then the dispatch is not "virtual" and the method is
@@ -1781,12 +1781,11 @@
     // ensures that access will be generally valid.
     SingleResolutionResult result =
         new SingleResolutionResult(reachableHolder, reachableHolder, reachableMethod);
-    DexClassAndMethod lookup = result.lookupVirtualDispatchTarget(instantiatedClass, appView);
+    DexClassAndMethod lookup = result.lookupVirtualDispatchTarget(instantiation, appView);
     if (lookup == null || !lookup.isProgramMethod() || lookup.getMethod().isAbstract()) {
       return;
     }
     ProgramMethod method = lookup.asProgramMethod();
-    markTypeAsLive(method.getHolder().type, instantiationReason);
     markVirtualMethodAsLive(
         method.getHolder(),
         method.getMethod(),
@@ -1794,9 +1793,8 @@
   }
 
   private void markLibraryAndClasspathMethodOverridesAsLive(
-      DexProgramClass instantiatedClass, DexClass libraryClass) {
+      InstantiatedObject instantiation, DexClass libraryClass) {
     assert libraryClass.isNotProgramClass();
-    assert !instantiatedClass.isInterface() || instantiatedClass.isAnnotation();
     if (mode.isTracingMainDex()) {
       // Library roots must be specified for tracing of library methods. For classpath the expected
       // use case is that the classes will be classloaded, thus they should have no bearing on the
@@ -1808,41 +1806,52 @@
       // Note: It would be reasonable to not process methods already seen during the marking of
       // program usages, but that would cause the methods to not be marked as library overrides.
       markLibraryOrClasspathOverrideLive(
-          instantiatedClass, libraryClass, appInfo.resolveMethod(libraryClass, method.method));
+          instantiation, libraryClass, appInfo.resolveMethod(libraryClass, method.method));
 
       // Due to API conversion, some overrides can be hidden since they will be rewritten. See
       // class comment of DesugaredLibraryAPIConverter and vivifiedType logic.
       // In the first enqueuer phase, the signature has not been desugared, so firstResolution
       // maintains the library override. In the second enqueuer phase, the signature has been
       // desugared, and the second resolution maintains the the library override.
-      if (appView.rewritePrefix.hasRewrittenTypeInSignature(method.method.proto, appView)) {
+      if (instantiation.isClass()
+          && appView.rewritePrefix.hasRewrittenTypeInSignature(method.method.proto, appView)) {
         DexMethod methodToResolve =
             DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
                 method.method, method.method.holder, appView);
         assert methodToResolve != method.method;
         markLibraryOrClasspathOverrideLive(
-            instantiatedClass,
+            instantiation,
             libraryClass,
-            appInfo.resolveMethod(instantiatedClass, methodToResolve));
+            appInfo.resolveMethod(instantiation.asClass(), methodToResolve));
       }
     }
   }
 
   private void markLibraryOrClasspathOverrideLive(
-      DexProgramClass instantiatedClass,
+      InstantiatedObject instantiation,
       DexClass libraryOrClasspathClass,
       ResolutionResult resolution) {
-    DexClassAndMethod lookup = resolution.lookupVirtualDispatchTarget(instantiatedClass, appView);
+    DexClassAndMethod lookup = resolution.lookupVirtualDispatchTarget(instantiation, appView);
     if (lookup == null || !lookup.isProgramMethod() || lookup.getMethod().isAbstract()) {
       return;
     }
     DexProgramClass clazz = lookup.asProgramMethod().getHolder();
     DexEncodedMethod target = lookup.getMethod();
-    if (shouldMarkLibraryMethodOverrideAsReachable(clazz, target)) {
+    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));
     }
-    markOverridesAsLibraryMethodOverrides(instantiatedClass, target.method);
+    if (instantiation.isClass()) {
+      // TODO(b/149976493): We need to mark these for lambdas too!
+      markOverridesAsLibraryMethodOverrides(instantiation.asClass(), target.method);
+    }
   }
 
   private void markOverridesAsLibraryMethodOverrides(
@@ -1866,35 +1875,6 @@
     }
   }
 
-  private void transitionDefaultMethodsForInstantiatedClass(
-      DexType iface, ScopedDexMethodSet seen) {
-    DexProgramClass clazz = getProgramClassOrNull(iface);
-    if (clazz == null) {
-      return;
-    }
-    assert clazz.accessFlags.isInterface();
-    transitionReachableVirtualMethods(clazz, seen.newNestedScope());
-    for (DexType superInterface : clazz.interfaces.values) {
-      transitionDefaultMethodsForInstantiatedClass(superInterface, seen);
-    }
-  }
-
-  private void transitionNonAbstractMethodsToLiveAndShadow(
-      DexProgramClass clazz, ReachableVirtualMethodsSet reachable, ScopedDexMethodSet seen) {
-    for (DexEncodedMethod encodedMethod : reachable.getMethods()) {
-      if (seen.addMethod(encodedMethod)) {
-        // Abstract methods do shadow implementations but they cannot be live, as they have no code.
-        if (!encodedMethod.accessFlags.isAbstract()) {
-          markVirtualMethodAsLive(
-              clazz,
-              encodedMethod,
-              graphReporter.reportReachableMethodAsLive(
-                  encodedMethod, reachable.getReasons(encodedMethod)));
-        }
-      }
-    }
-  }
-
   /**
    * Marks all fields live that can be reached by a read assuming that the given type or one of its
    * subtypes is instantiated.
@@ -1915,6 +1895,15 @@
   private void transitionDependentItemsForInstantiatedClass(DexProgramClass clazz) {
     assert !clazz.isAnnotation();
     assert !clazz.isInterface();
+    transitionDependentItemsForInstantiatedItem(clazz);
+  }
+
+  private void transitionDependentItemsForInstantiatedInterface(DexProgramClass clazz) {
+    assert clazz.isInterface();
+    transitionDependentItemsForInstantiatedItem(clazz);
+  }
+
+  private void transitionDependentItemsForInstantiatedItem(DexProgramClass clazz) {
     do {
       // Handle keep rules that are dependent on the class being instantiated.
       rootSet.forEachDependentNonStaticMember(clazz, appView, this::enqueueDependentItem);
@@ -2067,6 +2056,11 @@
     }
   }
 
+  public boolean isFieldReferenced(DexEncodedField field) {
+    FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field.field);
+    return info != null;
+  }
+
   public boolean isFieldLive(DexEncodedField field) {
     return liveFields.contains(field);
   }
@@ -2098,6 +2092,18 @@
     return directAndIndirectlyInstantiatedTypes.contains(clazz);
   }
 
+  public boolean isMethodLive(DexEncodedMethod method) {
+    return liveMethods.contains(method);
+  }
+
+  public boolean isMethodTargeted(DexEncodedMethod method) {
+    return targetedMethods.contains(method);
+  }
+
+  public boolean isTypeLive(DexProgramClass clazz) {
+    return liveTypes.contains(clazz);
+  }
+
   // Package protected due to entry point from worklist.
   void markInstanceFieldAsReachable(DexEncodedField encodedField, KeepReason reason) {
     DexField field = encodedField.field;
@@ -2217,7 +2223,7 @@
     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);
+            .lookupVirtualDispatchTargets(context, appView, appInfo, pinnedItems::contains);
     if (!lookupResult.isLookupResultSuccess()) {
       return;
     }
@@ -2238,13 +2244,6 @@
     assert !encodedPossibleTarget.isAbstract();
     DexMethod possibleTarget = encodedPossibleTarget.method;
     DexProgramClass clazz = getProgramClassOrNull(possibleTarget.holder);
-    // Add the method to the reachable set to ensure targets to lambdas are still identified.
-    ReachableVirtualMethodsSet reachable =
-        reachableVirtualMethods.computeIfAbsent(clazz, ignore -> new ReachableVirtualMethodsSet());
-    if (!reachable.add(encodedPossibleTarget, reason)) {
-      return;
-    }
-
     // If the holder type is uninstantiated (directly or indirectly) the method is not live yet.
     if (clazz == null || !isInstantiatedOrHasInstantiatedSubtype(clazz)) {
       return;
@@ -2558,7 +2557,7 @@
             joinIdentifierNameStrings(rootSet.identifierNameStrings, identifierNameStrings),
             Collections.emptySet(),
             Collections.emptyMap(),
-            Collections.emptyMap(),
+            EnumValueInfoMapCollection.empty(),
             SetUtils.mapIdentityHashSet(
                 unknownInstantiatedInterfaceTypes.getItems(), DexProgramClass::getType),
             constClassReferences);
@@ -2799,18 +2798,15 @@
               activeIfRules.computeIfAbsent(wrap, ignore -> new LinkedHashSet<>()).add(ifRule);
             }
           }
-          RootSetBuilder consequentSetBuilder = new RootSetBuilder(appView);
+          ConsequentRootSetBuilder consequentSetBuilder =
+              new ConsequentRootSetBuilder(appView, this);
           IfRuleEvaluator ifRuleEvaluator =
               new IfRuleEvaluator(
                   appView,
+                  this,
                   executorService,
                   activeIfRules,
-                  liveFields.getItems(),
-                  liveMethods.getItems(),
-                  liveTypes.getItems(),
-                  mode,
-                  consequentSetBuilder,
-                  targetedMethods.getItems());
+                  consequentSetBuilder);
           addConsequentRootSet(ifRuleEvaluator.run(), false);
           assert getNumberOfLiveItems() == numberOfLiveItemsAfterProcessing;
           if (!workList.isEmpty()) {
@@ -2848,9 +2844,6 @@
 
       if (Log.ENABLED) {
         Set<DexEncodedMethod> allLive = Sets.newIdentityHashSet();
-        for (ReachableVirtualMethodsSet reachable : reachableVirtualMethods.values()) {
-          allLive.addAll(reachable.getMethods());
-        }
         Set<DexEncodedMethod> reachableNotLive = Sets.difference(allLive, liveMethods.getItems());
         Log.debug(getClass(), "%s methods are reachable but not live", reachableNotLive.size());
         Log.info(getClass(), "Only reachable: %s", reachableNotLive);
@@ -2943,6 +2936,13 @@
     desugaredLambdaImplementationMethods.clear();
   }
 
+  void retainAnnotationForFinalTreeShaking(DexAnnotation annotation) {
+    assert mode.isInitialTreeShaking();
+    if (annotationRemoverBuilder != null) {
+      annotationRemoverBuilder.retainAnnotation(annotation);
+    }
+  }
+
   // Package protected due to entry point from worklist.
   void markMethodAsKept(DexProgramClass holder, DexEncodedMethod target, KeepReason reason) {
     DexMethod method = target.method;
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index 9c6889e..fe81bb7 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -13,8 +15,8 @@
 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.shaking.Enqueuer.Mode;
 import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
+import com.android.tools.r8.utils.InternalOptions.TestingOptions.ProguardIfRuleEvaluationData;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableSet;
@@ -34,35 +36,23 @@
 public class IfRuleEvaluator {
 
   private final AppView<? extends AppInfoWithSubtyping> appView;
+  private final Enqueuer enqueuer;
   private final ExecutorService executorService;
   private final List<Future<?>> futures = new ArrayList<>();
   private final Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> ifRules;
-  private final Set<DexEncodedField> liveFields;
-  private final Set<DexEncodedMethod> liveMethods;
-  private final Set<DexProgramClass> liveTypes;
-  private final Mode mode;
-  private final RootSetBuilder rootSetBuilder;
-  private final Set<DexEncodedMethod> targetedMethods;
+  private final ConsequentRootSetBuilder rootSetBuilder;
 
   IfRuleEvaluator(
       AppView<? extends AppInfoWithSubtyping> appView,
+      Enqueuer enqueuer,
       ExecutorService executorService,
       Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> ifRules,
-      Set<DexEncodedField> liveFields,
-      Set<DexEncodedMethod> liveMethods,
-      Set<DexProgramClass> liveTypes,
-      Mode mode,
-      RootSetBuilder rootSetBuilder,
-      Set<DexEncodedMethod> targetedMethods) {
+      ConsequentRootSetBuilder rootSetBuilder) {
     this.appView = appView;
+    this.enqueuer = enqueuer;
     this.executorService = executorService;
     this.ifRules = ifRules;
-    this.liveFields = liveFields;
-    this.liveMethods = liveMethods;
-    this.liveTypes = liveTypes;
-    this.mode = mode;
     this.rootSetBuilder = rootSetBuilder;
-    this.targetedMethods = targetedMethods;
   }
 
   public ConsequentRootSet run() throws ExecutionException {
@@ -74,6 +64,8 @@
         while (it.hasNext()) {
           Map.Entry<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> ifRuleEntry = it.next();
           ProguardIfRule ifRule = ifRuleEntry.getKey().get();
+          ProguardIfRuleEvaluationData ifRuleEvaluationData =
+              appView.options().testing.proguardIfRuleEvaluationData;
 
           // Depending on which types that trigger the -if rule, the application of the subsequent
           // -keep rule may vary (due to back references). So, we need to try all pairs of -if
@@ -86,12 +78,9 @@
 
             // Check if the class matches the if-rule.
             if (appView.options().testing.measureProguardIfRuleEvaluations) {
-              appView.options()
-                  .testing
-                  .proguardIfRuleEvaluationData
-                  .numberOfProguardIfRuleClassEvaluations++;
+              ifRuleEvaluationData.numberOfProguardIfRuleClassEvaluations++;
             }
-            if (evaluateClassForIfRule(ifRule, clazz, clazz)) {
+            if (evaluateClassForIfRule(ifRule, clazz)) {
               // When matching an if rule against a type, the if-rule are filled with the current
               // capture of wildcards. Propagate this down to member rules with same class part
               // equivalence.
@@ -101,10 +90,7 @@
                       memberRule -> {
                         registerClassCapture(memberRule, clazz, clazz);
                         if (appView.options().testing.measureProguardIfRuleEvaluations) {
-                          appView.options()
-                              .testing
-                              .proguardIfRuleEvaluationData
-                              .numberOfProguardIfRuleMemberEvaluations++;
+                          ifRuleEvaluationData.numberOfProguardIfRuleMemberEvaluations++;
                         }
                         return evaluateIfRuleMembersAndMaterialize(memberRule, clazz, clazz)
                             && canRemoveSubsequentKeepRule(memberRule);
@@ -112,33 +98,30 @@
             }
 
             // Check if one of the types that have been merged into `clazz` satisfies the if-rule.
-            if (appView.options().enableVerticalClassMerging
-                && appView.verticallyMergedClasses() != null) {
+            if (appView.verticallyMergedClasses() != null) {
               Iterable<DexType> sources =
                   appView.verticallyMergedClasses().getSourcesFor(clazz.type);
               for (DexType sourceType : sources) {
                 // Note that, although `sourceType` has been merged into `type`, the dex class for
                 // `sourceType` is still available until the second round of tree shaking. This
                 // way we can still retrieve the access flags of `sourceType`.
-                DexClass sourceClass = appView.definitionFor(sourceType);
-                assert sourceClass != null;
-                if (appView.options().testing.measureProguardIfRuleEvaluations) {
-                  appView.options()
-                      .testing
-                      .proguardIfRuleEvaluationData
-                      .numberOfProguardIfRuleClassEvaluations++;
+                DexProgramClass sourceClass =
+                    asProgramClassOrNull(appView.definitionFor(sourceType));
+                if (sourceClass == null) {
+                  assert false;
+                  continue;
                 }
-                if (evaluateClassForIfRule(ifRule, sourceClass, clazz)) {
+                if (appView.options().testing.measureProguardIfRuleEvaluations) {
+                  ifRuleEvaluationData.numberOfProguardIfRuleClassEvaluations++;
+                }
+                if (evaluateClassForIfRule(ifRule, sourceClass)) {
                   ifRuleEntry
                       .getValue()
                       .removeIf(
                           memberRule -> {
                             registerClassCapture(memberRule, sourceClass, clazz);
                             if (appView.options().testing.measureProguardIfRuleEvaluations) {
-                              appView.options()
-                                  .testing
-                                  .proguardIfRuleEvaluationData
-                                  .numberOfProguardIfRuleMemberEvaluations++;
+                              ifRuleEvaluationData.numberOfProguardIfRuleMemberEvaluations++;
                             }
                             return evaluateIfRuleMembersAndMaterialize(
                                     memberRule, sourceClass, clazz)
@@ -188,7 +171,7 @@
     // A type is effectively live if (1) it is truly live, (2) the value of one of its fields has
     // been inlined by the member value propagation, or (3) the return value of one of its methods
     // has been forwarded by the member value propagation.
-    if (liveTypes.contains(clazz)) {
+    if (enqueuer.isTypeLive(clazz)) {
       return true;
     }
     for (DexEncodedField field : clazz.fields()) {
@@ -204,28 +187,25 @@
     return false;
   }
 
-  /**
-   * Determines if `sourceClass` satisfies the given if-rule class specification. If `sourceClass`
-   * has not been merged into another class, then `targetClass` is the same as `sourceClass`.
-   * Otherwise, `targetClass` denotes the class that `sourceClass` has been merged into.
-   */
-  private boolean evaluateClassForIfRule(
-      ProguardIfRule rule, DexClass sourceClass, DexClass targetClass) {
-    if (!RootSetBuilder.satisfyClassType(rule, sourceClass)) {
+  /** Determines if {@param clazz} satisfies the given if-rule class specification. */
+  private boolean evaluateClassForIfRule(ProguardIfRule rule, DexProgramClass clazz) {
+    if (!RootSetBuilder.satisfyClassType(rule, clazz)) {
       return false;
     }
-    if (!RootSetBuilder.satisfyAccessFlag(rule, sourceClass)) {
+    if (!RootSetBuilder.satisfyAccessFlag(rule, clazz)) {
       return false;
     }
-    if (!RootSetBuilder.satisfyAnnotation(rule, sourceClass)) {
+    AnnotationMatchResult annotationMatchResult = RootSetBuilder.satisfyAnnotation(rule, clazz);
+    if (annotationMatchResult == null) {
       return false;
     }
-    if (!rule.getClassNames().matches(sourceClass.type)) {
+    rootSetBuilder.handleMatchedAnnotation(annotationMatchResult);
+    if (!rule.getClassNames().matches(clazz.type)) {
       return false;
     }
     if (rule.hasInheritanceClassName()) {
       // Try another live type since the current one doesn't satisfy the inheritance rule.
-      return rootSetBuilder.satisfyInheritanceRule(sourceClass, rule);
+      return rootSetBuilder.satisfyInheritanceRule(clazz, rule);
     }
     return true;
   }
@@ -243,15 +223,15 @@
         filteredMembers,
         targetClass.fields(
             f ->
-                (liveFields.contains(f) || f.getOptimizationInfo().valueHasBeenPropagated())
+                (enqueuer.isFieldReferenced(f) || f.getOptimizationInfo().valueHasBeenPropagated())
                     && appView.graphLense().getOriginalFieldSignature(f.field).holder
                         == sourceClass.type));
     Iterables.addAll(
         filteredMembers,
         targetClass.methods(
             m ->
-                (liveMethods.contains(m)
-                        || targetedMethods.contains(m)
+                (enqueuer.isMethodLive(m)
+                        || enqueuer.isMethodTargeted(m)
                         || m.getOptimizationInfo().returnValueHasBeenPropagated())
                     && appView.graphLense().getOriginalMethodSignature(m.method).holder
                         == sourceClass.type));
@@ -294,7 +274,7 @@
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     ProguardIfRule materializedRule = rule.materialize(dexItemFactory, preconditions);
 
-    if (mode.isInitialTreeShaking() && !rule.isUsed()) {
+    if (enqueuer.getMode().isInitialTreeShaking() && !rule.isUsed()) {
       // We need to abort class inlining of classes that could be matched by the condition of this
       // -if rule.
       ClassInlineRule neverClassInlineRuleForCondition =
diff --git a/src/main/java/com/android/tools/r8/shaking/InstantiatedObject.java b/src/main/java/com/android/tools/r8/shaking/InstantiatedObject.java
new file mode 100644
index 0000000..d4fe555
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/InstantiatedObject.java
@@ -0,0 +1,71 @@
+// 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.DexProgramClass;
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
+
+public abstract class InstantiatedObject {
+
+  public static InstantiatedObject of(DexProgramClass clazz) {
+    return new InstantiatedClass(clazz);
+  }
+
+  public static InstantiatedObject of(LambdaDescriptor lambda) {
+    return new InstantiatedLambda(lambda);
+  }
+
+  public boolean isClass() {
+    return false;
+  }
+
+  public DexProgramClass asClass() {
+    return null;
+  }
+
+  public boolean isLambda() {
+    return false;
+  }
+
+  public LambdaDescriptor asLambda() {
+    return null;
+  }
+
+  private static class InstantiatedClass extends InstantiatedObject {
+    final DexProgramClass clazz;
+
+    InstantiatedClass(DexProgramClass clazz) {
+      assert !clazz.isInterface() || clazz.isAnnotation();
+      this.clazz = clazz;
+    }
+
+    @Override
+    public boolean isClass() {
+      return true;
+    }
+
+    @Override
+    public DexProgramClass asClass() {
+      return clazz;
+    }
+  }
+
+  private static class InstantiatedLambda extends InstantiatedObject {
+    final LambdaDescriptor lambdaDescriptor;
+
+    public InstantiatedLambda(LambdaDescriptor lambdaDescriptor) {
+      this.lambdaDescriptor = lambdaDescriptor;
+    }
+
+    @Override
+    public boolean isLambda() {
+      return true;
+    }
+
+    @Override
+    public LambdaDescriptor asLambda() {
+      return lambdaDescriptor;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index 4be540a..1a894d3 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -16,6 +16,7 @@
 import com.google.common.collect.Iterables;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import java.util.stream.StreamSupport;
 
@@ -170,38 +171,45 @@
     return type;
   }
 
-  public boolean matches(DexEncodedField field, AppView<?> appView, DexStringCache stringCache) {
+  public boolean matches(
+      DexEncodedField field,
+      AppView<?> appView,
+      Consumer<AnnotationMatchResult> matchedAnnotationsConsumer,
+      DexStringCache stringCache) {
     DexField originalSignature = appView.graphLense().getOriginalFieldSignature(field.field);
     switch (getRuleType()) {
       case ALL:
       case ALL_FIELDS:
-        // Access flags check.
-        if (!getAccessFlags().containsAll(field.accessFlags)
-            || !getNegatedAccessFlags().containsNone(field.accessFlags)) {
-          break;
+        {
+          // Access flags check.
+          if (!getAccessFlags().containsAll(field.accessFlags)
+              || !getNegatedAccessFlags().containsNone(field.accessFlags)) {
+            break;
+          }
+          // Annotations check.
+          return RootSetBuilder.containsAnnotation(annotation, field, matchedAnnotationsConsumer);
         }
-        // Annotations check.
-        return RootSetBuilder.containsAnnotation(annotation, field);
+
       case FIELD:
-        // Name check.
-        String name = stringCache.lookupString(originalSignature.name);
-        if (!getName().matches(name)) {
-          break;
+        {
+          // Name check.
+          String name = stringCache.lookupString(originalSignature.name);
+          if (!getName().matches(name)) {
+            break;
+          }
+          // Access flags check.
+          if (!getAccessFlags().containsAll(field.accessFlags)
+              || !getNegatedAccessFlags().containsNone(field.accessFlags)) {
+            break;
+          }
+          // Type check.
+          if (!getType().matches(originalSignature.type, appView)) {
+            break;
+          }
+          // Annotations check
+          return RootSetBuilder.containsAnnotation(annotation, field, matchedAnnotationsConsumer);
         }
-        // Access flags check.
-        if (!getAccessFlags().containsAll(field.accessFlags)
-            || !getNegatedAccessFlags().containsNone(field.accessFlags)) {
-          break;
-        }
-        // Type check.
-        if (!getType().matches(originalSignature.type, appView)) {
-          break;
-        }
-        // Annotations check
-        if (!RootSetBuilder.containsAnnotation(annotation, field)) {
-          break;
-        }
-        return true;
+
       case ALL_METHODS:
       case CLINIT:
       case INIT:
@@ -212,7 +220,11 @@
     return false;
   }
 
-  public boolean matches(DexEncodedMethod method, AppView<?> appView, DexStringCache stringCache) {
+  public boolean matches(
+      DexEncodedMethod method,
+      AppView<?> appView,
+      Consumer<AnnotationMatchResult> matchedAnnotationsConsumer,
+      DexStringCache stringCache) {
     DexMethod originalSignature = appView.graphLense().getOriginalMethodSignature(method.method);
     switch (getRuleType()) {
       case ALL_METHODS:
@@ -220,53 +232,61 @@
           break;
         }
         // Fall through for all other methods.
+
       case ALL:
-        // Access flags check.
-        if (!getAccessFlags().containsAll(method.accessFlags)
-            || !getNegatedAccessFlags().containsNone(method.accessFlags)) {
-          break;
+        {
+          // Access flags check.
+          if (!getAccessFlags().containsAll(method.accessFlags)
+              || !getNegatedAccessFlags().containsNone(method.accessFlags)) {
+            break;
+          }
+          // Annotations check.
+          return RootSetBuilder.containsAnnotation(annotation, method, matchedAnnotationsConsumer);
         }
-        // Annotations check.
-        return RootSetBuilder.containsAnnotation(annotation, method);
+
       case METHOD:
         // Check return type.
         if (!type.matches(originalSignature.proto.returnType, appView)) {
           break;
         }
         // Fall through for access flags, name and arguments.
+
       case CONSTRUCTOR:
       case INIT:
       case CLINIT:
-        // Name check.
-        String name = stringCache.lookupString(originalSignature.name);
-        if (!getName().matches(name)) {
-          break;
-        }
-        // Access flags check.
-        if (!getAccessFlags().containsAll(method.accessFlags)
-            || !getNegatedAccessFlags().containsNone(method.accessFlags)) {
-          break;
-        }
-        // Annotations check.
-        if (!RootSetBuilder.containsAnnotation(annotation, method)) {
-          break;
-        }
-        // Parameter types check.
-        List<ProguardTypeMatcher> arguments = getArguments();
-        if (arguments.size() == 1 && arguments.get(0).isTripleDotPattern()) {
-          return true;
-        }
-        DexType[] parameters = originalSignature.proto.parameters.values;
-        if (parameters.length != arguments.size()) {
-          break;
-        }
-        for (int i = 0; i < parameters.length; i++) {
-          if (!arguments.get(i).matches(parameters[i], appView)) {
+        {
+          // Name check.
+          String name = stringCache.lookupString(originalSignature.name);
+          if (!getName().matches(name)) {
+            break;
+          }
+          // Access flags check.
+          if (!getAccessFlags().containsAll(method.accessFlags)
+              || !getNegatedAccessFlags().containsNone(method.accessFlags)) {
+            break;
+          }
+          // Annotations check.
+          if (!RootSetBuilder.containsAnnotation(annotation, method, matchedAnnotationsConsumer)) {
             return false;
           }
+          // Parameter types check.
+          List<ProguardTypeMatcher> arguments = getArguments();
+          if (arguments.size() == 1 && arguments.get(0).isTripleDotPattern()) {
+            return true;
+          }
+          DexType[] parameters = originalSignature.proto.parameters.values;
+          if (parameters.length != arguments.size()) {
+            break;
+          }
+          for (int i = 0; i < parameters.length; i++) {
+            if (!arguments.get(i).matches(parameters[i], appView)) {
+              return false;
+            }
+          }
+          // All parameters matched.
+          return true;
         }
-        // All parameters matched.
-        return true;
+
       case ALL_FIELDS:
       case FIELD:
         break;
@@ -409,5 +429,4 @@
     ruleBuilder.setRuleType(ProguardMemberType.ALL);
     return ruleBuilder.build();
   }
-
 }
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 7d93686..2189be7 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -15,9 +15,11 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexLibraryClass;
+import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
@@ -27,6 +29,8 @@
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
 import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.shaking.AnnotationMatchResult.AnnotationsIgnoredMatchResult;
+import com.android.tools.r8.shaking.AnnotationMatchResult.ConcreteAnnotationMatchResult;
 import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.Consumer3;
@@ -63,6 +67,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
@@ -123,6 +128,10 @@
     this(appView, appView.appInfo().app(), null);
   }
 
+  void handleMatchedAnnotation(AnnotationMatchResult annotation) {
+    // Intentionally empty.
+  }
+
   // Process a class with the keep rule.
   private void process(
       DexClass clazz,
@@ -134,9 +143,11 @@
     if (!satisfyAccessFlag(rule, clazz)) {
       return;
     }
-    if (!satisfyAnnotation(rule, clazz)) {
+    AnnotationMatchResult annotationMatchResult = satisfyAnnotation(rule, clazz);
+    if (annotationMatchResult == null) {
       return;
     }
+    handleMatchedAnnotation(annotationMatchResult);
     // In principle it should make a difference whether the user specified in a class
     // spec that a class either extends or implements another type. However, proguard
     // seems not to care, so users have started to use this inconsistently. We are thus
@@ -146,95 +157,96 @@
       return;
     }
 
-    if (rule.getClassNames().matches(clazz.type)) {
-      Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
-      Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier;
-      if (rule instanceof ProguardKeepRule) {
-        if (clazz.isNotProgramClass()) {
-          return;
-        }
-        switch (((ProguardKeepRule) rule).getType()) {
-          case KEEP_CLASS_MEMBERS:
-            // Members mentioned at -keepclassmembers always depend on their holder.
-            preconditionSupplier = ImmutableMap.of(definition -> true, clazz);
-            markMatchingVisibleMethods(
-                clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
-            markMatchingVisibleFields(
-                clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
-            break;
-          case KEEP_CLASSES_WITH_MEMBERS:
-            if (!allRulesSatisfied(memberKeepRules, clazz)) {
-              break;
-            }
-            // fallthrough;
-          case KEEP:
-            markClass(clazz, rule, ifRule);
-            preconditionSupplier = new HashMap<>();
-            if (ifRule != null) {
-              // Static members in -keep are pinned no matter what.
-              preconditionSupplier.put(DexDefinition::isStaticMember, null);
-              // Instance members may need to be kept even though the holder is not instantiated.
-              preconditionSupplier.put(definition -> !definition.isStaticMember(), clazz);
-            } else {
-              // Members mentioned at -keep should always be pinned as long as that -keep rule is
-              // not triggered conditionally.
-              preconditionSupplier.put((definition -> true), null);
-            }
-            markMatchingVisibleMethods(
-                clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
-            markMatchingVisibleFields(
-                clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
-            break;
-          case CONDITIONAL:
-            throw new Unreachable("-if rule will be evaluated separately, not here.");
-        }
+    if (!rule.getClassNames().matches(clazz.type)) {
+      return;
+    }
+
+    Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
+    Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier;
+    if (rule instanceof ProguardKeepRule) {
+      if (clazz.isNotProgramClass()) {
         return;
       }
-      // Only the ordinary keep rules are supported in a conditional rule.
-      assert ifRule == null;
-      if (rule instanceof ProguardIfRule) {
-        throw new Unreachable("-if rule will be evaluated separately, not here.");
-      } else if (rule instanceof ProguardCheckDiscardRule) {
-        if (memberKeepRules.isEmpty()) {
-          markClass(clazz, rule, ifRule);
-        } else {
-          preconditionSupplier = ImmutableMap.of((definition -> true), clazz);
+      switch (((ProguardKeepRule) rule).getType()) {
+        case KEEP_CLASS_MEMBERS:
+          // Members mentioned at -keepclassmembers always depend on their holder.
+          preconditionSupplier = ImmutableMap.of(definition -> true, clazz);
           markMatchingVisibleMethods(
-              clazz, memberKeepRules, rule, preconditionSupplier, true, ifRule);
+              clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
           markMatchingVisibleFields(
-              clazz, memberKeepRules, rule, preconditionSupplier, true, ifRule);
-        }
-      } else if (rule instanceof ProguardWhyAreYouKeepingRule) {
-        markClass(clazz, rule, ifRule);
-        markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
-        markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
-      } else if (rule instanceof ProguardAssumeMayHaveSideEffectsRule
-          || rule instanceof ProguardAssumeNoSideEffectRule
-          || rule instanceof ProguardAssumeValuesRule) {
-        markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
-        markMatchingOverriddenMethods(
-            appView.appInfo(), clazz, memberKeepRules, rule, null, true, ifRule);
-        markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
-      } else if (rule instanceof InlineRule
-          || rule instanceof ConstantArgumentRule
-          || rule instanceof UnusedArgumentRule
-          || rule instanceof ReprocessMethodRule
-          || rule instanceof WhyAreYouNotInliningRule) {
-        markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
-      } else if (rule instanceof ClassInlineRule
-          || rule instanceof ClassMergingRule
-          || rule instanceof ReprocessClassInitializerRule) {
-        if (allRulesSatisfied(memberKeepRules, clazz)) {
+              clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
+          break;
+        case KEEP_CLASSES_WITH_MEMBERS:
+          if (!allRulesSatisfied(memberKeepRules, clazz)) {
+            break;
+          }
+          // fallthrough;
+        case KEEP:
           markClass(clazz, rule, ifRule);
-        }
-      } else if (rule instanceof MemberValuePropagationRule) {
-        markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
-        markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
-      } else {
-        assert rule instanceof ProguardIdentifierNameStringRule;
-        markMatchingFields(clazz, memberKeepRules, rule, null, ifRule);
-        markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
+          preconditionSupplier = new HashMap<>();
+          if (ifRule != null) {
+            // Static members in -keep are pinned no matter what.
+            preconditionSupplier.put(DexDefinition::isStaticMember, null);
+            // Instance members may need to be kept even though the holder is not instantiated.
+            preconditionSupplier.put(definition -> !definition.isStaticMember(), clazz);
+          } else {
+            // Members mentioned at -keep should always be pinned as long as that -keep rule is
+            // not triggered conditionally.
+            preconditionSupplier.put((definition -> true), null);
+          }
+          markMatchingVisibleMethods(
+              clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
+          markMatchingVisibleFields(
+              clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
+          break;
+        case CONDITIONAL:
+          throw new Unreachable("-if rule will be evaluated separately, not here.");
       }
+      return;
+    }
+    // Only the ordinary keep rules are supported in a conditional rule.
+    assert ifRule == null;
+    if (rule instanceof ProguardIfRule) {
+      throw new Unreachable("-if rule will be evaluated separately, not here.");
+    } else if (rule instanceof ProguardCheckDiscardRule) {
+      if (memberKeepRules.isEmpty()) {
+        markClass(clazz, rule, ifRule);
+      } else {
+        preconditionSupplier = ImmutableMap.of((definition -> true), clazz);
+        markMatchingVisibleMethods(
+            clazz, memberKeepRules, rule, preconditionSupplier, true, ifRule);
+        markMatchingVisibleFields(clazz, memberKeepRules, rule, preconditionSupplier, true, ifRule);
+      }
+    } else if (rule instanceof ProguardWhyAreYouKeepingRule) {
+      markClass(clazz, rule, ifRule);
+      markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
+      markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
+    } else if (rule instanceof ProguardAssumeMayHaveSideEffectsRule
+        || rule instanceof ProguardAssumeNoSideEffectRule
+        || rule instanceof ProguardAssumeValuesRule) {
+      markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
+      markMatchingOverriddenMethods(
+          appView.appInfo(), clazz, memberKeepRules, rule, null, true, ifRule);
+      markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
+    } else if (rule instanceof InlineRule
+        || rule instanceof ConstantArgumentRule
+        || rule instanceof UnusedArgumentRule
+        || rule instanceof ReprocessMethodRule
+        || rule instanceof WhyAreYouNotInliningRule) {
+      markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
+    } else if (rule instanceof ClassInlineRule
+        || rule instanceof ClassMergingRule
+        || rule instanceof ReprocessClassInitializerRule) {
+      if (allRulesSatisfied(memberKeepRules, clazz)) {
+        markClass(clazz, rule, ifRule);
+      }
+    } else if (rule instanceof MemberValuePropagationRule) {
+      markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
+      markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
+    } else {
+      assert rule instanceof ProguardIdentifierNameStringRule;
+      markMatchingFields(clazz, memberKeepRules, rule, null, ifRule);
+      markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
     }
   }
 
@@ -505,6 +517,10 @@
       this.ifRule = ifRule;
     }
 
+    void handleMatchedAnnotation(AnnotationMatchResult annotationMatchResult) {
+      // Intentionally empty.
+    }
+
     void run() {
       visitAllSuperInterfaces(originalClazz.type);
     }
@@ -531,7 +547,7 @@
           continue;
         }
         for (ProguardMemberRule rule : memberKeepRules) {
-          if (rule.matches(method, appView, dexStringCache)) {
+          if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
             tryAndKeepMethodOnClass(method, rule);
           }
         }
@@ -733,7 +749,7 @@
         && rule.getNegatedClassAccessFlags().containsNone(clazz.accessFlags);
   }
 
-  static boolean satisfyAnnotation(ProguardConfigurationRule rule, DexClass clazz) {
+  static AnnotationMatchResult satisfyAnnotation(ProguardConfigurationRule rule, DexClass clazz) {
     return containsAnnotation(rule.getClassAnnotation(), clazz);
   }
 
@@ -764,9 +780,13 @@
       // TODO(b/110141157): Should the vertical class merger move annotations from the source to
       // the target class? If so, it is sufficient only to apply the annotation-matcher to the
       // annotations of `class`.
-      if (rule.getInheritanceClassName().matches(clazz.type, appView)
-          && containsAnnotation(rule.getInheritanceAnnotation(), clazz)) {
-        return true;
+      if (rule.getInheritanceClassName().matches(clazz.type, appView)) {
+        AnnotationMatchResult annotationMatchResult =
+            containsAnnotation(rule.getInheritanceAnnotation(), clazz);
+        if (annotationMatchResult != null) {
+          handleMatchedAnnotation(annotationMatchResult);
+          return true;
+        }
       }
       type = clazz.superType;
     }
@@ -797,9 +817,13 @@
       // TODO(b/110141157): Should the vertical class merger move annotations from the source to
       // the target class? If so, it is sufficient only to apply the annotation-matcher to the
       // annotations of `ifaceClass`.
-      if (rule.getInheritanceClassName().matches(iface, appView)
-          && containsAnnotation(rule.getInheritanceAnnotation(), ifaceClass)) {
-        return true;
+      if (rule.getInheritanceClassName().matches(iface, appView)) {
+        AnnotationMatchResult annotationMatchResult =
+            containsAnnotation(rule.getInheritanceAnnotation(), ifaceClass);
+        if (annotationMatchResult != null) {
+          handleMatchedAnnotation(annotationMatchResult);
+          return true;
+        }
       }
       if (anyImplementedInterfaceMatchesImplementsRule(ifaceClass, rule)) {
         return true;
@@ -852,7 +876,7 @@
   boolean ruleSatisfiedByMethods(ProguardMemberRule rule, Iterable<DexEncodedMethod> methods) {
     if (rule.getRuleType().includesMethods()) {
       for (DexEncodedMethod method : methods) {
-        if (rule.matches(method, appView, dexStringCache)) {
+        if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
           return true;
         }
       }
@@ -863,7 +887,7 @@
   boolean ruleSatisfiedByFields(ProguardMemberRule rule, Iterable<DexEncodedField> fields) {
     if (rule.getRuleType().includesFields()) {
       for (DexEncodedField field : fields) {
-        if (rule.matches(field, appView, dexStringCache)) {
+        if (rule.matches(field, appView, this::handleMatchedAnnotation, dexStringCache)) {
           return true;
         }
       }
@@ -871,40 +895,46 @@
     return false;
   }
 
-  static boolean containsAnnotation(ProguardTypeMatcher classAnnotation, DexClass clazz) {
+  static AnnotationMatchResult containsAnnotation(
+      ProguardTypeMatcher classAnnotation, DexClass clazz) {
     return containsAnnotation(classAnnotation, clazz.annotations());
   }
 
-  static boolean containsAnnotation(ProguardTypeMatcher classAnnotation, DexEncodedMethod method) {
-    if (containsAnnotation(classAnnotation, method.annotations())) {
+  static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> boolean containsAnnotation(
+      ProguardTypeMatcher classAnnotation,
+      DexEncodedMember<D, R> member,
+      Consumer<AnnotationMatchResult> matchedAnnotationsConsumer) {
+    AnnotationMatchResult annotationMatchResult =
+        containsAnnotation(classAnnotation, member.annotations());
+    if (annotationMatchResult != null) {
+      matchedAnnotationsConsumer.accept(annotationMatchResult);
       return true;
     }
-    for (int i = 0; i < method.parameterAnnotationsList.size(); i++) {
-      if (containsAnnotation(classAnnotation, method.parameterAnnotationsList.get(i))) {
-        return true;
+    if (member.isDexEncodedMethod()) {
+      DexEncodedMethod method = member.asDexEncodedMethod();
+      for (int i = 0; i < method.parameterAnnotationsList.size(); i++) {
+        annotationMatchResult =
+            containsAnnotation(classAnnotation, method.parameterAnnotationsList.get(i));
+        if (annotationMatchResult != null) {
+          matchedAnnotationsConsumer.accept(annotationMatchResult);
+          return true;
+        }
       }
     }
     return false;
   }
 
-  static boolean containsAnnotation(ProguardTypeMatcher classAnnotation, DexEncodedField field) {
-    return containsAnnotation(classAnnotation, field.annotations());
-  }
-
-  private static boolean containsAnnotation(
+  private static AnnotationMatchResult containsAnnotation(
       ProguardTypeMatcher classAnnotation, DexAnnotationSet annotations) {
     if (classAnnotation == null) {
-      return true;
-    }
-    if (annotations.isEmpty()) {
-      return false;
+      return AnnotationsIgnoredMatchResult.getInstance();
     }
     for (DexAnnotation annotation : annotations.annotations) {
       if (classAnnotation.matches(annotation.annotation.type)) {
-        return true;
+        return new ConcreteAnnotationMatchResult(annotation);
       }
     }
-    return false;
+    return null;
   }
 
   private void markMethod(
@@ -920,7 +950,7 @@
       return;
     }
     for (ProguardMemberRule rule : rules) {
-      if (rule.matches(method, appView, dexStringCache)) {
+      if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
         if (Log.ENABLED) {
           Log.verbose(getClass(), "Marking method `%s` due to `%s { %s }`.", method, context,
               rule);
@@ -940,7 +970,7 @@
       DexDefinition precondition,
       ProguardIfRule ifRule) {
     for (ProguardMemberRule rule : rules) {
-      if (rule.matches(field, appView, dexStringCache)) {
+      if (rule.matches(field, appView, this::handleMatchedAnnotation, dexStringCache)) {
         if (Log.ENABLED) {
           Log.verbose(getClass(), "Marking field `%s` due to `%s { %s }`.", field, context,
               rule);
@@ -1386,6 +1416,22 @@
               });
     }
 
+    public void forEachDependentInstanceConstructor(
+        DexProgramClass clazz,
+        AppView<?> appView,
+        Consumer3<DexProgramClass, DexEncodedMethod, Set<ProguardKeepRuleBase>> fn) {
+      getDependentItems(clazz)
+          .forEach(
+              (reference, reasons) -> {
+                DexDefinition definition = appView.definitionFor(reference);
+                if (definition != null
+                    && definition.isDexEncodedMethod()
+                    && definition.asDexEncodedMethod().isInstanceInitializer()) {
+                  fn.accept(clazz, definition.asDexEncodedMethod(), reasons);
+                }
+              });
+    }
+
     public void copy(DexReference original, DexReference rewritten) {
       if (noShrinking.containsKey(original)) {
         noShrinking.put(rewritten, noShrinking.get(original));
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index cccaa07..8dd1b92 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -210,10 +210,15 @@
 
   public int applicationSize() throws IOException, ResourceException {
     int bytes = 0;
+    assert getDexProgramResourcesForTesting().size() == 0
+        || getClassProgramResourcesForTesting().size() == 0;
     try (Closer closer = Closer.create()) {
       for (ProgramResource dex : getDexProgramResourcesForTesting()) {
         bytes += ByteStreams.toByteArray(closer.register(dex.getByteStream())).length;
       }
+      for (ProgramResource cf : getClassProgramResourcesForTesting()) {
+        bytes += ByteStreams.toByteArray(closer.register(cf.getByteStream())).length;
+      }
     }
     return bytes;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
new file mode 100644
index 0000000..271bd70
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -0,0 +1,128 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.CompatProguardCommandBuilder;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Wrapper to make it easy to call R8 in compat mode when compiling a dump file.
+ *
+ * <p>This wrapper will be added to the classpath so it *must* only refer to the public API. See
+ * {@code tools/compiledump.py}.
+ *
+ * <p>It is tempting to have this class share the R8 parser code, but such refactoring would not be
+ * valid on past version of the R8 API. Thus there is little else to do than reimplement the parts
+ * we want to support for reading dumps.
+ */
+public class CompileDumpCompatR8 {
+
+  private static final List<String> VALID_OPTIONS =
+      Arrays.asList("--classfile", "--compat", "--debug", "--release");
+
+  private static final List<String> VALID_OPTIONS_WITH_OPERAND =
+      Arrays.asList(
+          "--output",
+          "--lib",
+          "--classpath",
+          "--min-api",
+          "--main-dex-rules",
+          "--main-dex-list",
+          "--main-dex-list-output",
+          "--pg-conf",
+          "--pg-map-output",
+          "--desugared-lib");
+
+  public static void main(String[] args) throws CompilationFailedException {
+    boolean isCompatMode = false;
+    OutputMode outputMode = OutputMode.DexIndexed;
+    Path outputPath = null;
+    CompilationMode compilationMode = CompilationMode.RELEASE;
+    List<Path> program = new ArrayList<>();
+    List<Path> library = new ArrayList<>();
+    List<Path> classpath = new ArrayList<>();
+    List<Path> config = new ArrayList<>();
+    int minApi = 1;
+    for (int i = 0; i < args.length; i++) {
+      String option = args[i];
+      if (VALID_OPTIONS.contains(option)) {
+        switch (option) {
+          case "--classfile":
+            {
+              outputMode = OutputMode.ClassFile;
+              break;
+            }
+          case "--compat":
+            {
+              isCompatMode = true;
+              break;
+            }
+          case "--debug":
+            {
+              compilationMode = CompilationMode.DEBUG;
+              break;
+            }
+          case "--release":
+            {
+              compilationMode = CompilationMode.RELEASE;
+              break;
+            }
+          default:
+            throw new IllegalArgumentException("Unimplemented option: " + option);
+        }
+      } else if (VALID_OPTIONS_WITH_OPERAND.contains(option)) {
+        String operand = args[++i];
+        switch (option) {
+          case "--output":
+            {
+              outputPath = Paths.get(operand);
+              break;
+            }
+          case "--lib":
+            {
+              library.add(Paths.get(operand));
+              break;
+            }
+          case "--classpath":
+            {
+              classpath.add(Paths.get(operand));
+              break;
+            }
+          case "--min-api":
+            {
+              minApi = Integer.parseInt(operand);
+              break;
+            }
+          case "--pg-conf":
+            {
+              config.add(Paths.get(operand));
+              break;
+            }
+          default:
+            throw new IllegalArgumentException("Unimplemented option: " + option);
+        }
+      } else {
+        program.add(Paths.get(option));
+      }
+    }
+    R8.run(
+        new CompatProguardCommandBuilder(isCompatMode)
+            .addProgramFiles(program)
+            .addLibraryFiles(library)
+            .addClasspathFiles(classpath)
+            .addProguardConfigurationFiles(config)
+            .setOutput(outputPath, outputMode)
+            .setMode(compilationMode)
+            .setMinApiLevel(minApi)
+            .build());
+  }
+}
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 232b54a..8d04c47 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.features.FeatureSplitConfiguration;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -50,6 +51,7 @@
 import com.google.common.collect.Sets;
 import java.io.IOException;
 import java.io.PrintStream;
+import java.lang.reflect.GenericSignatureFormatError;
 import java.nio.file.Files;
 import java.nio.file.InvalidPathException;
 import java.nio.file.Path;
@@ -841,6 +843,31 @@
     }
   }
 
+  public void warningInvalidSignature(
+      DexDefinition item, Origin origin, String signature, GenericSignatureFormatError e) {
+    StringBuilder message = new StringBuilder("Invalid signature '");
+    message.append(signature);
+    message.append("' for ");
+    if (item.isDexClass()) {
+      message.append("class ");
+      message.append((item.asDexClass()).getType().toSourceString());
+    } else if (item.isDexEncodedField()) {
+      message.append("field ");
+      message.append(item.toSourceString());
+    } else {
+      assert item.isDexEncodedMethod();
+      message.append("method ");
+      message.append(item.toSourceString());
+    }
+    message.append(".");
+    message.append(System.lineSeparator());
+    message.append("Signature is ignored and will not be present in the output.");
+    message.append(System.lineSeparator());
+    message.append("Parser error: ");
+    message.append(e.getMessage());
+    reporter.warning(new StringDiagnostic(message.toString(), origin));
+  }
+
   public boolean printWarnings() {
     boolean printed = false;
     boolean printOutdatedToolchain = false;
@@ -1019,6 +1046,7 @@
      */
     public boolean addCallEdgesForLibraryInvokes = false;
 
+    public boolean allowCheckDiscardedErrors = false;
     public boolean allowTypeErrors =
         !Version.isDevelopmentVersion()
             || System.getProperty("com.android.tools.r8.allowTypeErrors") != null;
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 835c528..d389826 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -19,8 +19,10 @@
     return new WorkList<T>(EqualityTest.IDENTITY);
   }
 
-  public WorkList() {
-    this(EqualityTest.HASH);
+  public static <T> WorkList<T> newIdentityWorkList(Iterable<T> items) {
+    WorkList<T> workList = new WorkList<>(EqualityTest.IDENTITY);
+    workList.addIfNotSeen(items);
+    return workList;
   }
 
   private WorkList(EqualityTest equalityTest) {
@@ -35,6 +37,12 @@
     items.forEach(this::addIfNotSeen);
   }
 
+  public void addIfNotSeen(T[] items) {
+    for (T item : items) {
+      addIfNotSeen(item);
+    }
+  }
+
   public void addIfNotSeen(T item) {
     if (seen.add(item)) {
       workingList.addLast(item);
diff --git a/src/test/examplesJava11/nesthostexample/BasicNestHostTreePruning.java b/src/test/examplesJava11/nesthostexample/BasicNestHostTreePruning.java
index 07ec806..eb2b3bf 100644
--- a/src/test/examplesJava11/nesthostexample/BasicNestHostTreePruning.java
+++ b/src/test/examplesJava11/nesthostexample/BasicNestHostTreePruning.java
@@ -6,7 +6,7 @@
 
 public class BasicNestHostTreePruning {
 
-  private String field = "NotPruned";
+  private String field = System.currentTimeMillis() >= 0 ? "NotPruned" : "Dead";
 
   public static class NotPruned extends BasicNestHostTreePruning {
 
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index db80105..a642002 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -102,7 +102,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 118, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 115, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -142,7 +142,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 118, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 115, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index ae6fc69..3a16807 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -8,9 +8,11 @@
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.graphinspector.GraphInspector;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
@@ -100,4 +102,9 @@
   public String getProguardMap() {
     return proguardMap;
   }
+
+  public R8TestCompileResult writeProguardMap(Path path) throws IOException {
+    FileUtils.writeTextFile(path, getProguardMap());
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 5cc8194..ac15d64 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -598,8 +598,23 @@
     return appView;
   }
 
+  protected static AppView<AppInfoWithLiveness> computeAppViewWithLiveness(AndroidApp app)
+      throws Exception {
+    return computeAppViewWithLiveness(
+        app, factory -> ImmutableList.of(ProguardKeepRule.defaultKeepAllRule(unused -> {})));
+  }
+
   protected static AppView<AppInfoWithLiveness> computeAppViewWithLiveness(
       AndroidApp app, Class<?> mainClass) throws Exception {
+    return computeAppViewWithLiveness(
+        app, factory -> buildKeepRuleForClassAndMethods(mainClass, factory));
+  }
+
+  protected static AppView<AppInfoWithLiveness> computeAppViewWithLiveness(
+      AndroidApp app,
+      Function<DexItemFactory, Collection<ProguardConfigurationRule>>
+          proguardConfigurationRulesGenerator)
+      throws Exception {
     AppView<AppInfoWithSubtyping> appView = computeAppViewWithSubtyping(app);
     // Run the tree shaker to compute an instance of AppInfoWithLiveness.
     ExecutorService executor = Executors.newSingleThreadExecutor();
@@ -608,7 +623,7 @@
         new RootSetBuilder(
                 appView,
                 application,
-                buildKeepRuleForClassAndMethods(mainClass, application.dexItemFactory))
+                proguardConfigurationRulesGenerator.apply(appView.appInfo().dexItemFactory()))
             .run(executor);
     AppInfoWithLiveness appInfoWithLiveness =
         EnqueuerFactory.createForInitialTreeShaking(appView)
@@ -662,7 +677,7 @@
         ListUtils.map(formalTypes, type -> buildType(type, factory)));
   }
 
-  private static List<ProguardConfigurationRule> buildKeepRuleForClass(
+  protected static List<ProguardConfigurationRule> buildKeepRuleForClass(
       Class<?> clazz, DexItemFactory factory) {
     Builder keepRuleBuilder = ProguardKeepRule.builder();
     keepRuleBuilder.setSource("buildKeepRuleForClass " + clazz.getTypeName());
@@ -674,10 +689,10 @@
     return Collections.singletonList(keepRuleBuilder.build());
   }
 
-  private static List<ProguardConfigurationRule> buildKeepRuleForClassAndMethods(
+  protected static List<ProguardConfigurationRule> buildKeepRuleForClassAndMethods(
       Class<?> clazz, DexItemFactory factory) {
     Builder keepRuleBuilder = ProguardKeepRule.builder();
-    keepRuleBuilder.setSource("buildKeepRuleForClass " + clazz.getTypeName());
+    keepRuleBuilder.setSource("buildKeepRuleForClassAndMethods " + clazz.getTypeName());
     keepRuleBuilder.setType(ProguardKeepRuleType.KEEP);
     keepRuleBuilder.setClassNames(
         ProguardClassNameList.singletonList(
@@ -1415,7 +1430,7 @@
     return JarBuilder.builder(temp);
   }
 
-  public Collection<Path> buildOnDexRuntime(TestParameters parameters, Collection<Path> paths)
+  public List<Path> buildOnDexRuntime(TestParameters parameters, List<Path> paths)
       throws CompilationFailedException, IOException {
     if (parameters.isCfRuntime()) {
       return paths;
@@ -1428,7 +1443,7 @@
             .writeToZip());
   }
 
-  public Collection<Path> buildOnDexRuntime(TestParameters parameters, Path... paths)
+  public List<Path> buildOnDexRuntime(TestParameters parameters, Path... paths)
       throws IOException, CompilationFailedException {
     return buildOnDexRuntime(parameters, Arrays.asList(paths));
   }
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 5f0843f..4f64c5b 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -84,6 +84,10 @@
     return self();
   }
 
+  public T allowCheckDiscardedErrors() {
+    return addOptionsModification(options -> options.testing.allowCheckDiscardedErrors = true);
+  }
+
   public CR compile() throws CompilationFailedException {
     AndroidAppConsumers sink = new AndroidAppConsumers();
     builder.setProgramConsumer(sink.wrapProgramConsumer(programConsumer));
@@ -346,7 +350,7 @@
   }
 
   public T allowStderrMessages() {
-    allowStdoutMessages = true;
+    allowStderrMessages = true;
     return self();
   }
 
diff --git a/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java b/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java
index c7ffc01..412311e 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.compatproguard;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.AllOf.allOf;
 import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertFalse;
@@ -175,8 +177,8 @@
               assertTrue(inspector.clazz(MAIN_CLASS).isPresent());
               assertTrue(inspector.clazz(BAR_CLASS).isPresent());
               assertBarGetInstanceIsNotInlined(inspector);
-              assertFalse(inspector.clazz(BAR_CLASS).uniqueFieldWithName("i").isPresent());
-              assertFalse(inspector.clazz(BAR_CLASS).uniqueMethodWithName("<init>").isPresent());
+              assertThat(inspector.clazz(BAR_CLASS).init(), isPresent());
+              assertThat(inspector.clazz(BAR_CLASS).uniqueFieldWithName("i"), isPresent());
             });
   }
 
@@ -286,8 +288,8 @@
               assertTrue(inspector.clazz(MAIN_CLASS).isPresent());
               assertTrue(inspector.clazz(BAR_CLASS).isPresent());
               assertBarGetInstanceIsNotInlined(inspector);
-              assertFalse(inspector.clazz(BAR_CLASS).uniqueFieldWithName("i").isPresent());
-              assertFalse(inspector.clazz(BAR_CLASS).uniqueMethodWithName("<init>").isPresent());
+              assertThat(inspector.clazz(BAR_CLASS).uniqueMethodWithName("<init>"), isPresent());
+              assertThat(inspector.clazz(BAR_CLASS).uniqueFieldWithName("i"), isPresent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
index 159aa8d..1b46018 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
@@ -66,6 +66,7 @@
         L8Command.builder(diagnosticsHandler)
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
             .addProgramFiles(ToolHelper.getDesugarJDKLibs())
+            .addProgramFiles(ToolHelper.DESUGAR_LIB_CONVERSIONS)
             .addLibraryFiles(ToolHelper.getCoreLambdaStubs())
             .addDesugaredLibraryConfiguration(
                 StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
@@ -74,6 +75,12 @@
     ToolHelper.runL8(l8Builder.build(), options -> {});
     CodeInspector codeInspector = new CodeInspector(desugaredLib);
     assertCorrect(codeInspector);
+    assertNoWarningsErrors(diagnosticsHandler);
+  }
+
+  private void assertNoWarningsErrors(TestDiagnosticMessagesImpl diagnosticsHandler) {
+    assertTrue(diagnosticsHandler.getWarnings().isEmpty());
+    assertTrue(diagnosticsHandler.getErrors().isEmpty());
   }
 
   private void assertCorrect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ArgumentInfoCollectionTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ArgumentInfoCollectionTest.java
new file mode 100644
index 0000000..ddef0b4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ArgumentInfoCollectionTest.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
+import org.junit.Test;
+
+public class ArgumentInfoCollectionTest extends TestBase {
+
+  @Test
+  public void testCombineRewritten() {
+    DexItemFactory factory = new DexItemFactory();
+    ArgumentInfoCollection.Builder builder1 = ArgumentInfoCollection.builder();
+    builder1.addArgumentInfo(1, new RewrittenTypeInfo(factory.intType, factory.longType));
+    builder1.addArgumentInfo(3, new RewrittenTypeInfo(factory.intType, factory.longType));
+    ArgumentInfoCollection arguments1 = builder1.build();
+
+    ArgumentInfoCollection.Builder builder2 = ArgumentInfoCollection.builder();
+    builder2.addArgumentInfo(2, new RewrittenTypeInfo(factory.floatType, factory.doubleType));
+    builder2.addArgumentInfo(4, new RewrittenTypeInfo(factory.floatType, factory.doubleType));
+    ArgumentInfoCollection arguments2 = builder2.build();
+
+    ArgumentInfoCollection combine = arguments1.combine(arguments2);
+
+    RewrittenTypeInfo arg1 = combine.getArgumentInfo(1).asRewrittenTypeInfo();
+    assertEquals(arg1.getOldType(), factory.intType);
+    assertEquals(arg1.getNewType(), factory.longType);
+    RewrittenTypeInfo arg2 = combine.getArgumentInfo(2).asRewrittenTypeInfo();
+    assertEquals(arg2.getOldType(), factory.floatType);
+    assertEquals(arg2.getNewType(), factory.doubleType);
+    RewrittenTypeInfo arg3 = combine.getArgumentInfo(3).asRewrittenTypeInfo();
+    assertEquals(arg3.getOldType(), factory.intType);
+    assertEquals(arg3.getNewType(), factory.longType);
+    RewrittenTypeInfo arg4 = combine.getArgumentInfo(4).asRewrittenTypeInfo();
+    assertEquals(arg4.getOldType(), factory.floatType);
+    assertEquals(arg4.getNewType(), factory.doubleType);
+  }
+
+  @Test
+  public void testCombineRemoved() {
+    DexItemFactory factory = new DexItemFactory();
+
+    // Arguments removed: 0 1 2 3 4 -> 0 2 4.
+    ArgumentInfoCollection.Builder builder1 = ArgumentInfoCollection.builder();
+    builder1.addArgumentInfo(
+        1, RemovedArgumentInfo.builder().setType(factory.intType).setIsAlwaysNull().build());
+    builder1.addArgumentInfo(
+        3, RemovedArgumentInfo.builder().setType(factory.intType).setIsAlwaysNull().build());
+    ArgumentInfoCollection arguments1 = builder1.build();
+
+    // Arguments removed: 0 2 4 -> 0. Arguments 2 and 4 are at position 1 and 2 after first removal.
+    ArgumentInfoCollection.Builder builder2 = ArgumentInfoCollection.builder();
+    builder2.addArgumentInfo(1, RemovedArgumentInfo.builder().setType(factory.doubleType).build());
+    builder2.addArgumentInfo(2, RemovedArgumentInfo.builder().setType(factory.doubleType).build());
+    ArgumentInfoCollection arguments2 = builder2.build();
+
+    // Arguments removed: 0 1 2 3 4 -> 0.
+    ArgumentInfoCollection combine = arguments1.combine(arguments2);
+
+    RemovedArgumentInfo arg1 = combine.getArgumentInfo(1).asRemovedArgumentInfo();
+    assertEquals(arg1.getType(), factory.intType);
+    assertTrue(arg1.isAlwaysNull());
+    RemovedArgumentInfo arg2 = combine.getArgumentInfo(2).asRemovedArgumentInfo();
+    assertEquals(arg2.getType(), factory.doubleType);
+    assertFalse(arg2.isAlwaysNull());
+    RemovedArgumentInfo arg3 = combine.getArgumentInfo(3).asRemovedArgumentInfo();
+    assertEquals(arg3.getType(), factory.intType);
+    assertTrue(arg3.isAlwaysNull());
+    RemovedArgumentInfo arg4 = combine.getArgumentInfo(4).asRemovedArgumentInfo();
+    assertEquals(arg4.getType(), factory.doubleType);
+    assertFalse(arg4.isAlwaysNull());
+  }
+
+  @Test
+  public void testCombineRemoveRewritten() {
+    DexItemFactory factory = new DexItemFactory();
+
+    ArgumentInfoCollection.Builder builder1 = ArgumentInfoCollection.builder();
+    builder1.addArgumentInfo(
+        1, RemovedArgumentInfo.builder().setType(factory.intType).setIsAlwaysNull().build());
+    builder1.addArgumentInfo(
+        3, RemovedArgumentInfo.builder().setType(factory.intType).setIsAlwaysNull().build());
+    ArgumentInfoCollection arguments1 = builder1.build();
+
+    ArgumentInfoCollection.Builder builder2 = ArgumentInfoCollection.builder();
+    builder2.addArgumentInfo(1, new RewrittenTypeInfo(factory.floatType, factory.doubleType));
+    builder2.addArgumentInfo(2, new RewrittenTypeInfo(factory.floatType, factory.doubleType));
+    ArgumentInfoCollection arguments2 = builder2.build();
+
+    ArgumentInfoCollection combine = arguments1.combine(arguments2);
+
+    RemovedArgumentInfo arg1 = combine.getArgumentInfo(1).asRemovedArgumentInfo();
+    assertEquals(arg1.getType(), factory.intType);
+    assertTrue(arg1.isAlwaysNull());
+    RewrittenTypeInfo arg2 = combine.getArgumentInfo(2).asRewrittenTypeInfo();
+    assertEquals(arg2.getOldType(), factory.floatType);
+    assertEquals(arg2.getNewType(), factory.doubleType);
+    RemovedArgumentInfo arg3 = combine.getArgumentInfo(3).asRemovedArgumentInfo();
+    assertEquals(arg3.getType(), factory.intType);
+    assertTrue(arg3.isAlwaysNull());
+    RewrittenTypeInfo arg4 = combine.getArgumentInfo(4).asRewrittenTypeInfo();
+    assertEquals(arg4.getOldType(), factory.floatType);
+    assertEquals(arg4.getNewType(), factory.doubleType);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingTest.java
index b13c250..19c069d 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingTest.java
@@ -23,7 +23,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final boolean enumKeepRules;
+  private final KeepRule enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -31,7 +31,7 @@
   }
 
   public ComparisonEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -45,7 +45,7 @@
             .addKeepMainRules(INPUTS)
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
-            .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
+            .addKeepRules(enumKeepRules.getKeepRule())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
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 b7ebc83..399b6b0 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
@@ -10,18 +10,49 @@
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessages;
-import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
 import java.util.List;
 
 public class EnumUnboxingTestBase extends TestBase {
 
-  static final String KEEP_ENUM =
-      "-keepclassmembers enum * { public static **[] values(); public static **"
-          + " valueOf(java.lang.String); }";
+  private static final String KEEP_ENUM_STUDIO =
+      "-keepclassmembers enum * {\n"
+          + " public static **[] values();\n"
+          + " public static ** valueOf(java.lang.String);\n"
+          + "}";
+  private static final String KEEP_ENUM_SNAP =
+      "-keepclassmembers enum * {\n"
+          + "<fields>;\n"
+          + " public static **[] values();\n"
+          + " public static ** valueOf(java.lang.String);\n"
+          + "}";
+  private static final List<KeepRule> KEEP_ENUM =
+      ImmutableList.of(
+          new KeepRule("none", ""),
+          new KeepRule("studio", KEEP_ENUM_STUDIO),
+          new KeepRule("snap", KEEP_ENUM_SNAP));
+
+  public static class KeepRule {
+    private final String name;
+    private final String keepRule;
+
+    private KeepRule(String name, String keepRule) {
+      this.name = name;
+      this.keepRule = keepRule;
+    }
+
+    public String getKeepRule() {
+      return keepRule;
+    }
+
+    @Override
+    public String toString() {
+      return name;
+    }
+  }
 
   public void assertLines2By2Correct(String string) {
     List<String> lines = StringUtils.splitLines(string);
@@ -58,13 +89,8 @@
 
   static List<Object[]> enumUnboxingTestParameters() {
     return buildParameters(
-        getTestParameters()
-            .withCfRuntime(CfVm.JDK9)
-            .withDexRuntime(DexVm.Version.first())
-            .withDexRuntime(DexVm.Version.last())
-            .withAllApiLevels()
-            .build(),
+        getTestParameters().withAllRuntimesAndApiLevels().withAllApiLevels().build(),
         BooleanUtils.values(),
-        BooleanUtils.values());
+        KEEP_ENUM);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
index 4f13c05..ad16ee1 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
@@ -38,7 +38,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final boolean enumKeepRules;
+  private final KeepRule enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -46,7 +46,7 @@
   }
 
   public FailingEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -64,7 +64,7 @@
             .noTreeShaking() // Disabled to avoid merging Itf into EnumInterface.
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
-            .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
+            .addKeepRules(enumKeepRules.getKeepRule())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
index 0391875..8e7be57 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
@@ -28,12 +28,14 @@
     StaticFieldPutObject.class,
     ToString.class,
     EnumSetTest.class,
-    FailingPhi.class
+    FailingPhi.class,
+    FailingReturnType.class,
+    FailingParameterType.class
   };
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final boolean enumKeepRules;
+  private final KeepRule enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -41,7 +43,7 @@
   }
 
   public FailingMethodEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -53,7 +55,7 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(FailingMethodEnumUnboxingTest.class)
             .addKeepMainRules(FAILURES)
-            .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
+            .addKeepRules(enumKeepRules.getKeepRule())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .enableInliningAnnotations()
@@ -69,7 +71,7 @@
                       assertEnumIsBoxed(
                           failure.getDeclaredClasses()[0], failure.getSimpleName(), m))
               .run(parameters.getRuntime(), failure);
-      if (failure == EnumSetTest.class && !enumKeepRules) {
+      if (failure == EnumSetTest.class && enumKeepRules.getKeepRule().isEmpty()) {
         // EnumSet and EnumMap cannot be used without the enumKeepRules.
         run.assertFailure();
       } else {
@@ -200,4 +202,48 @@
       }
     }
   }
+
+  static class FailingReturnType {
+
+    @NeverClassInline
+    enum MyEnum {
+      A,
+      B,
+      C
+    }
+
+    public static void main(String[] args) {
+      System.out.println(returnObject(MyEnum.A) == MyEnum.A);
+      System.out.println("true");
+      System.out.println(returnObject(MyEnum.B) == MyEnum.B);
+      System.out.println("true");
+    }
+
+    @NeverInline
+    static Object returnObject(MyEnum e) {
+      return System.currentTimeMillis() >= 0 ? e : new Object();
+    }
+  }
+
+  static class FailingParameterType {
+
+    @NeverClassInline
+    enum MyEnum {
+      A,
+      B,
+      C
+    }
+
+    public static void main(String[] args) {
+      System.out.println(objectToInt(MyEnum.A));
+      System.out.println("0");
+      System.out.println(objectToInt(MyEnum.B));
+      System.out.println("1");
+    }
+
+    @NeverInline
+    static int objectToInt(Object e) {
+      return e instanceof Enum ? ((Enum) e).ordinal() : e.hashCode();
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
index 129230a..f2ed08b 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
@@ -25,7 +25,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final boolean enumKeepRules;
+  private final KeepRule enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -33,7 +33,7 @@
   }
 
   public FieldPutEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -45,7 +45,7 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(FieldPutEnumUnboxingTest.class)
             .addKeepMainRules(INPUTS)
-            .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
+            .addKeepRules(enumKeepRules.getKeepRule())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingTest.java
index 91cd718..b388ed7 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingTest.java
@@ -20,7 +20,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final boolean enumKeepRules;
+  private final KeepRule enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -28,7 +28,7 @@
   }
 
   public OrdinalEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -41,7 +41,7 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(classToTest, ENUM_CLASS)
             .addKeepMainRule(classToTest)
-            .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
+            .addKeepRules(enumKeepRules.getKeepRule())
             .enableNeverClassInliningAnnotations()
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java
index 62ccaf8..ba217f6 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java
@@ -21,7 +21,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final boolean enumKeepRules;
+  private final KeepRule enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -29,7 +29,7 @@
   }
 
   public PhiEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -42,7 +42,7 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(classToTest, ENUM_CLASS)
             .addKeepMainRule(classToTest)
-            .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
+            .addKeepRules(enumKeepRules.getKeepRule())
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/PinnedEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/PinnedEnumUnboxingTest.java
new file mode 100644
index 0000000..355c6a1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/PinnedEnumUnboxingTest.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PinnedEnumUnboxingTest extends EnumUnboxingTestBase {
+
+  private static final Class<?> ENUM_CLASS = MyEnum.class;
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final KeepRule enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public PinnedEnumUnboxingTest(
+      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    Class<MainWithKeptEnum> classToTest = MainWithKeptEnum.class;
+    testForR8(parameters.getBackend())
+        .addProgramClasses(classToTest, ENUM_CLASS)
+        .addKeepMainRule(classToTest)
+        .addKeepClassRules(MyEnum.class)
+        .addKeepRules(enumKeepRules.getKeepRule())
+        .enableNeverClassInliningAnnotations()
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .allowDiagnosticInfoMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspectDiagnosticMessages(
+            m -> assertEnumIsBoxed(ENUM_CLASS, classToTest.getSimpleName(), m))
+        .run(parameters.getRuntime(), classToTest)
+        .assertSuccessWithOutputLines("0");
+  }
+
+  @NeverClassInline
+  enum MyEnum {
+    A,
+    B,
+    C
+  }
+
+  static class MainWithKeptEnum {
+
+    public static void main(String[] args) {
+      System.out.println(MyEnum.A.ordinal());
+    }
+  }
+}
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 88dd987..4bfa568 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
@@ -21,7 +21,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final boolean enumKeepRules;
+  private final KeepRule enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -29,7 +29,7 @@
   }
 
   public SwitchEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, boolean enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -42,7 +42,7 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(SwitchEnumUnboxingTest.class)
             .addKeepMainRule(classToTest)
-            .addKeepRules(enumKeepRules ? KEEP_ENUM : "")
+            .addKeepRules(enumKeepRules.getKeepRule())
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
diff --git a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
new file mode 100644
index 0000000..710e8da
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
@@ -0,0 +1,253 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.graph;
+
+import static 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.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+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.Parser;
+import com.android.tools.r8.graph.GenericSignature.TypeSignature;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import org.junit.Test;
+
+public class GenericSignatureTest extends TestBase {
+
+  @Test
+  public void test() throws Exception {
+    AndroidApp app =
+        testForD8()
+            .debug()
+            .addProgramClassesAndInnerClasses(A.class, B.class, CY.class, CYY.class)
+            .compile()
+            .app;
+    AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(app);
+    DexItemFactory factory = appView.dexItemFactory();
+    CodeInspector inspector = new CodeInspector(appView.appInfo().app());
+
+    ClassSubject a = inspector.clazz(A.class);
+    assertThat(a, isPresent());
+    ClassSubject y = inspector.clazz(A.Y.class);
+    assertThat(y, isPresent());
+    ClassSubject yy = inspector.clazz(A.Y.YY.class);
+    assertThat(yy, isPresent());
+    ClassSubject zz = inspector.clazz(A.Y.ZZ.class);
+    assertThat(zz, isPresent());
+    ClassSubject cy = inspector.clazz(CY.class);
+    assertThat(cy, isPresent());
+    ClassSubject cyy = inspector.clazz(CYY.class);
+    assertThat(cyy, isPresent());
+
+    ClassSignature classSignature;
+    ClassTypeSignature classTypeSignature;
+    FieldTypeSignature fieldTypeSignature;
+    MethodTypeSignature methodTypeSignature;
+    List<FieldTypeSignature> typeArguments;
+    FieldTypeSignature typeArgument;
+
+    //
+    // Testing ClassSignature
+    //
+
+    // class CYY<T extends A<T>.Y> extends CY<T>
+    DexClass clazz = cyy.getDexClass();
+    assertNotNull(clazz);
+    classSignature = Parser.toClassSignature(clazz, appView);
+    assertNotNull(classSignature);
+
+    // TODO(b/129925954): test formal type parameter of CYY
+
+    assertTrue(classSignature.superInterfaceSignatures.isEmpty());
+    classTypeSignature = classSignature.superClassSignature;
+    assertEquals(cy.getDexClass().type, classTypeSignature.type);
+    typeArguments = classTypeSignature.typeArguments;
+    assertEquals(1, typeArguments.size());
+    typeArgument = typeArguments.get(0);
+    assertTrue(typeArgument.isTypeVariableSignature());
+    assertEquals("T", typeArgument.asTypeVariableSignature().typeVariable);
+
+    //
+    // Testing FieldTypeSignature
+    //
+
+    FieldSubject yyInZZ = zz.uniqueFieldWithName("yy");
+    assertThat(yyInZZ, isPresent());
+    DexEncodedField field = yyInZZ.getField();
+    assertNotNull(field);
+
+    fieldTypeSignature = Parser.toFieldTypeSignature(field, appView);
+    assertNotNull(fieldTypeSignature);
+
+    // field type: A$Y$YY
+    assertTrue(fieldTypeSignature.isClassTypeSignature());
+    check_A_Y_YY(a, y, yy, fieldTypeSignature.asClassTypeSignature());
+
+    //
+    // Testing MethodTypeSignature
+    //
+
+    // A$Y$YY newYY()
+    MethodSubject newYY = zz.uniqueMethodWithName("newYY");
+    assertThat(newYY, isPresent());
+    DexEncodedMethod 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());
+
+    // Function<A$Y$ZZ<TT>, A$Y$YY> convertToYY(Supplier<A$Y$ZZ<TT>>
+    MethodSubject convertToYY = zz.uniqueMethodWithName("convertToYY");
+    assertThat(convertToYY, isPresent());
+    method = convertToYY.getMethod();
+    assertNotNull(method);
+
+    methodTypeSignature = GenericSignature.Parser.toMethodTypeSignature(method, appView);
+    assertNotNull(methodTypeSignature);
+
+    // return type: Function<A$Y$ZZ<TT>, A$Y$YY>
+    returnType = methodTypeSignature.returnType();
+    assertTrue(returnType.isFieldTypeSignature());
+    assertTrue(returnType.asFieldTypeSignature().isClassTypeSignature());
+    classTypeSignature = returnType.asFieldTypeSignature().asClassTypeSignature();
+    DexType functionType =
+        factory.createType(DescriptorUtils.javaTypeToDescriptor(Function.class.getTypeName()));
+    assertEquals(functionType, classTypeSignature.type);
+
+    typeArguments = classTypeSignature.typeArguments;
+    assertEquals(2, typeArguments.size());
+
+    typeArgument = typeArguments.get(0);
+    assertTrue(typeArgument.isClassTypeSignature());
+    check_A_Y_ZZ(a, y, zz, typeArgument.asClassTypeSignature());
+
+    typeArgument = typeArguments.get(1);
+    assertTrue(typeArgument.isClassTypeSignature());
+    check_A_Y_YY(a, y, yy, typeArgument.asClassTypeSignature());
+
+    // 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());
+  }
+
+  private void check_A_Y(ClassSubject a, ClassSubject y, ClassTypeSignature signature) {
+    assertEquals(a.getDexClass().type, signature.type);
+    List<FieldTypeSignature> typeArguments = signature.typeArguments;
+    assertEquals(1, typeArguments.size());
+    FieldTypeSignature typeArgument = typeArguments.get(0);
+    assertTrue(typeArgument.isTypeVariableSignature());
+    assertEquals("T", typeArgument.asTypeVariableSignature().typeVariable);
+    assertEquals(y.getDexClass().type, signature.innerTypeSignature.type);
+  }
+
+  private void check_A_Y_YY(
+      ClassSubject a, ClassSubject y, ClassSubject yy, ClassTypeSignature signature) {
+    check_A_Y(a, y, signature);
+    assertEquals(yy.getDexClass().type, signature.innerTypeSignature.innerTypeSignature.type);
+  }
+
+  private void check_A_Y_ZZ(
+      ClassSubject a, ClassSubject y, ClassSubject zz, ClassTypeSignature signature) {
+    check_A_Y(a, y, signature);
+    ClassTypeSignature innerMost = signature.innerTypeSignature.innerTypeSignature;
+    assertEquals(zz.getDexClass().type, innerMost.type);
+    List<FieldTypeSignature> typeArguments = innerMost.typeArguments;
+    assertEquals(1, typeArguments.size());
+    FieldTypeSignature typeArgument = typeArguments.get(0);
+    assertTrue(typeArgument.isTypeVariableSignature());
+    assertEquals("TT", typeArgument.asTypeVariableSignature().typeVariable);
+  }
+}
+
+//
+// TODO(b/129925954): Once unified, these would be stale comments.
+// Borrowed from ...naming.signature.GenericSignatureRenamingTest
+// and then extended a bit to explore more details, e.g., MethodTypeSignature.
+//
+
+class A<T> {
+  class Y {
+
+    class YY {}
+
+    class ZZ<TT> extends YY {
+      public YY yy;
+
+      YY newYY() {
+        return new YY();
+      }
+
+      Function<ZZ<TT>, YY> convertToYY(Supplier<ZZ<TT>> zzSupplier) {
+        return zz -> {
+          if (System.currentTimeMillis() > 0) {
+            return zzSupplier.get().newYY();
+          } else {
+            return zz.newYY();
+          }
+        };
+      }
+    }
+
+    ZZ<T> zz() {
+      return new ZZ<T>();
+    }
+  }
+
+  class Z extends Y {}
+
+  static class S {}
+
+  Y newY() {
+    return new Y();
+  }
+
+  Z newZ() {
+    return new Z();
+  }
+
+  Y.ZZ<T> newZZ() {
+    return new Y().zz();
+  }
+}
+
+class B<T extends A<T>> {}
+
+class CY<T extends A<T>.Y> {}
+
+class CYY<T extends A<T>.Y> extends CY<T> {}
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 774209e..88251be 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -69,7 +69,8 @@
     // Check lookup targets with include method.
     ResolutionResult resolutionResult = appInfo().resolveMethodOnClass(clazz, method.method);
     LookupResult lookupResult =
-        resolutionResult.lookupVirtualDispatchTargets(clazz, appView, appInfo());
+        resolutionResult.lookupVirtualDispatchTargets(
+            clazz, appView, appInfo(), dexReference -> false);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<DexEncodedMethod> targets = lookupResult.asLookupResultSuccess().getMethodTargets();
     assertTrue(targets.contains(method));
@@ -79,7 +80,7 @@
     LookupResult lookupResult =
         appInfo()
             .resolveMethodOnInterface(clazz, method.method)
-            .lookupVirtualDispatchTargets(clazz, appView, appInfo());
+            .lookupVirtualDispatchTargets(clazz, appView, appInfo(), dexReference -> false);
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<DexEncodedMethod> targets = lookupResult.asLookupResultSuccess().getMethodTargets();
     if (appInfo().subtypes(method.method.holder).stream()
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 df8bcb0..57f4359 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
@@ -25,7 +25,12 @@
   final String base;
 
   public YouTubeCompilationBase(int majorVersion, int minorVersion) {
-    this.base = "third_party/youtube/youtube.android_" + majorVersion + "." + minorVersion + "/";
+    this.base =
+        "third_party/youtube/youtube.android_"
+            + majorVersion
+            + "."
+            + String.format("%02d", minorVersion)
+            + "/";
   }
 
   protected List<Path> getKeepRuleFiles() {
@@ -38,6 +43,13 @@
     return ImmutableList.of(Paths.get(base, "legacy_YouTubeRelease_combined_library_jars.jar"));
   }
 
+  protected List<Path> getMainDexRuleFiles() {
+    return ImmutableList.of(
+        Paths.get(base).resolve("mainDexClasses.rules"),
+        Paths.get(base).resolve("main-dex-classes-release-optimized.pgcfg"),
+        Paths.get(base).resolve("main_dex_YouTubeRelease_proguard.cfg"));
+  }
+
   protected List<Path> getProgramFiles() throws IOException {
     List<Path> result = new ArrayList<>();
     for (Path keepRuleFile : getKeepRuleFiles()) {
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 73da9f2..ab63ba7 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.analysis.ProtoApplicationStats;
+import java.nio.file.Paths;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -24,13 +25,14 @@
 @RunWith(Parameterized.class)
 public class YouTubeV1444TreeShakeJarVerificationTest extends YouTubeCompilationBase {
 
+  private static final boolean DUMP = false;
   private static final int MAX_SIZE = 27500000;
 
   private final TestParameters parameters;
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().build();
+    return getTestParameters().withNoneRuntime().build();
   }
 
   public YouTubeV1444TreeShakeJarVerificationTest(TestParameters parameters) {
@@ -52,12 +54,16 @@
             .assertSanitizedProguardConfigurationIsEmpty();
 
     R8TestCompileResult compileResult =
-        testForR8(parameters.getBackend())
+        testForR8(Backend.DEX)
             .addProgramFiles(getProgramFiles())
             .addLibraryFiles(librarySanitizer.getSanitizedLibrary())
             .addKeepRuleFiles(getKeepRuleFiles())
+            .addMainDexRuleFiles(getMainDexRuleFiles())
             .addOptionsModification(
                 options -> {
+                  assert !options.applyInliningToInlinee;
+                  options.applyInliningToInlinee = true;
+
                   assert !options.enableFieldBitAccessAnalysis;
                   options.enableFieldBitAccessAnalysis = true;
 
@@ -74,10 +80,17 @@
                   options.enableStringSwitchConversion = true;
                 })
             .setMinApi(AndroidApiLevel.H_MR2)
+            .allowDiagnosticMessages()
             .allowUnusedProguardConfigurationRules()
             .compile();
 
     if (ToolHelper.isLocalDevelopment()) {
+      if (DUMP) {
+        long time = System.currentTimeMillis();
+        compileResult.writeToZip(Paths.get("YouTubeV1444-" + time + ".zip"));
+        compileResult.writeProguardMap(Paths.get("YouTubeV1444-" + time + ".map"));
+      }
+
       DexItemFactory dexItemFactory = new DexItemFactory();
       ProtoApplicationStats original =
           new ProtoApplicationStats(dexItemFactory, new CodeInspector(getProgramFiles()));
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java
new file mode 100644
index 0000000..01120c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.internal;
+
+import static com.android.tools.r8.ToolHelper.isLocalDevelopment;
+import static com.android.tools.r8.ToolHelper.shouldRunSlowTests;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.analysis.ProtoApplicationStats;
+import java.nio.file.Paths;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class YouTubeV1508TreeShakeJarVerificationTest extends YouTubeCompilationBase {
+
+  private static final boolean DUMP = false;
+  private static final int MAX_SIZE = 27500000;
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public YouTubeV1508TreeShakeJarVerificationTest(TestParameters parameters) {
+    super(15, 8);
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    // TODO(b/141603168): Enable this on the bots.
+    assumeTrue(isLocalDevelopment());
+    assumeTrue(shouldRunSlowTests());
+
+    LibrarySanitizer librarySanitizer =
+        new LibrarySanitizer(temp)
+            .addProgramFiles(getProgramFiles())
+            .addLibraryFiles(getLibraryFiles())
+            .sanitize()
+            .assertSanitizedProguardConfigurationIsEmpty();
+
+    R8TestCompileResult compileResult =
+        testForR8(Backend.DEX)
+            .addProgramFiles(getProgramFiles())
+            .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()
+            .setMinApi(AndroidApiLevel.H_MR2)
+            .compile();
+
+    if (ToolHelper.isLocalDevelopment()) {
+      if (DUMP) {
+        long time = System.currentTimeMillis();
+        compileResult.writeToZip(Paths.get("YouTubeV1508-" + time + ".zip"));
+        compileResult.writeProguardMap(Paths.get("YouTubeV1508-" + time + ".map"));
+      }
+
+      DexItemFactory dexItemFactory = new DexItemFactory();
+      ProtoApplicationStats original =
+          new ProtoApplicationStats(dexItemFactory, new CodeInspector(getProgramFiles()));
+      ProtoApplicationStats actual =
+          new ProtoApplicationStats(dexItemFactory, compileResult.inspector(), original);
+      ProtoApplicationStats baseline =
+          new ProtoApplicationStats(
+              dexItemFactory,
+              new CodeInspector(getReleaseApk(), getReleaseProguardMap().toString()));
+      System.out.println(actual.getStats(baseline));
+    }
+
+    int applicationSize = compileResult.app.applicationSize();
+    System.out.println(applicationSize);
+
+    assertTrue(
+        "Expected max size of " + MAX_SIZE + ", got " + applicationSize,
+        applicationSize < MAX_SIZE);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentForwardingConstructorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentForwardingConstructorTest.java
index 5d0acf1..fba6f58 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentForwardingConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentForwardingConstructorTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.membervaluepropagation.fields;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -50,8 +51,7 @@
     ClassSubject testClassSubject = inspector.clazz(TestClass.class);
     assertThat(testClassSubject, isPresent());
     assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
-    // TODO(b/147652121): Should be absent.
-    assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+    assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantInOneConstructorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantInOneConstructorTest.java
index 71ef038..8dcd7bc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantInOneConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantInOneConstructorTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.membervaluepropagation.fields;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -50,8 +51,11 @@
     ClassSubject testClassSubject = inspector.clazz(TestClass.class);
     assertThat(testClassSubject, isPresent());
     assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
-    // TODO(b/147652121): Should be absent.
-    assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+    if (parameters.isCfRuntime()) {
+      assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+    } else {
+      assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
+    }
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java
index cf7650f..9bedfb1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.membervaluepropagation.fields;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -50,8 +51,11 @@
     ClassSubject testClassSubject = inspector.clazz(TestClass.class);
     assertThat(testClassSubject, isPresent());
     assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
-    // TODO(b/147652121): Should be absent.
-    assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+    if (parameters.isCfRuntime()) {
+      assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+    } else {
+      assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
+    }
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentInForwardingConstructorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentInForwardingConstructorTest.java
new file mode 100644
index 0000000..c89cfa9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentInForwardingConstructorTest.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.membervaluepropagation.fields;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.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.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class FieldInitializedByNonConstantArgumentInForwardingConstructorTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public FieldInitializedByNonConstantArgumentInForwardingConstructorTest(
+      TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(FieldInitializedByNonConstantArgumentInForwardingConstructorTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Live!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+    assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
+    assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      if (new A(42).x == 42) {
+        live();
+      } else {
+        dead();
+      }
+
+      if (System.currentTimeMillis() < 0) {
+        // So that we can't conclude a constant value for A.x.
+        System.out.println(new A(args.length));
+      }
+    }
+
+    @NeverInline
+    static void live() {
+      System.out.println("Live!");
+    }
+
+    @NeverInline
+    static void dead() {
+      System.out.println("Dead!");
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    int x;
+
+    A(int x) {
+      this(x, null);
+    }
+
+    @NeverInline
+    A(int x, Object unused) {
+      this.x = x;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentInSuperConstructorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentInSuperConstructorTest.java
new file mode 100644
index 0000000..a59e9a9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentInSuperConstructorTest.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.ir.optimize.membervaluepropagation.fields;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+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.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 FieldInitializedByNonConstantArgumentInSuperConstructorTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public FieldInitializedByNonConstantArgumentInSuperConstructorTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(FieldInitializedByNonConstantArgumentInSuperConstructorTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Live!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+    assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
+    assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      if (new B(42).x == 42) {
+        live();
+      } else {
+        dead();
+      }
+
+      if (System.currentTimeMillis() < 0) {
+        // So that we can't conclude a constant value for A.x.
+        System.out.println(new B(args.length));
+      }
+    }
+
+    @NeverInline
+    static void live() {
+      System.out.println("Live!");
+    }
+
+    @NeverInline
+    static void dead() {
+      System.out.println("Dead!");
+    }
+  }
+
+  @NeverMerge
+  static class A {
+
+    int x;
+
+    A(int x) {
+      this.x = x;
+    }
+  }
+
+  @NeverClassInline
+  static class B extends A {
+
+    B(int x) {
+      super(x);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentTest.java
index c0c12e2..c515e70 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.membervaluepropagation.fields;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -50,8 +51,7 @@
     ClassSubject testClassSubject = inspector.clazz(TestClass.class);
     assertThat(testClassSubject, isPresent());
     assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
-    // TODO(b/147652121): Should be absent.
-    assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+    assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java
new file mode 100644
index 0000000..d55906a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java
@@ -0,0 +1,207 @@
+// 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.outliner.b149971007;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dexsplitter.SplitterTestBase;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class B149971007 extends SplitterTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public B149971007(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private boolean invokesOutline(MethodSubject method, String outlineClassName) {
+    assertThat(method, isPresent());
+    for (InstructionSubject instruction : method.asFoundMethodSubject().instructions()) {
+      if (instruction.isInvoke()
+          && instruction.getMethod().holder.toSourceString().equals(outlineClassName)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private boolean referenceFeatureClass(FoundMethodSubject method) {
+    for (InstructionSubject instruction : method.instructions()) {
+      if (instruction.isInvoke()
+          && instruction.getMethod().holder.toSourceString().endsWith("FeatureClass")) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private void checkOutlineFromFeature(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(OutlineOptions.CLASS_NAME);
+    assertThat(clazz, isPresent());
+    assertEquals(2, clazz.allMethods().size());
+    assertTrue(clazz.allMethods().stream().anyMatch(this::referenceFeatureClass));
+  }
+
+  @Test
+  public void testWithoutSplit() throws Exception {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(TestClass.class, FeatureAPI.class, FeatureClass.class)
+            .addKeepClassAndMembersRules(TestClass.class)
+            .addKeepClassAndMembersRules(FeatureClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(options -> options.outline.threshold = 2)
+            .compile()
+            .inspect(this::checkOutlineFromFeature);
+
+    // Check that parts of method1 and method2 in FeatureClass was outlined.
+    ClassSubject featureClass = compileResult.inspector().clazz(FeatureClass.class);
+    assertThat(featureClass, isPresent());
+    String outlineClassName =
+        ClassNameMapper.mapperFromString(compileResult.getProguardMap())
+            .getObfuscatedToOriginalMapping()
+            .inverse
+            .get(OutlineOptions.CLASS_NAME);
+    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method1"), outlineClassName));
+    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method2"), outlineClassName));
+
+    compileResult.run(parameters.getRuntime(), TestClass.class).assertSuccessWithOutput("1234");
+  }
+
+  private void checkNoOutlineFromFeature(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(OutlineOptions.CLASS_NAME);
+    assertThat(clazz, isPresent());
+    assertEquals(1, clazz.allMethods().size());
+    assertTrue(clazz.allMethods().stream().noneMatch(this::referenceFeatureClass));
+  }
+
+  @Test
+  public void testWithSplit() throws Exception {
+    Path featureCode = temp.newFile("feature.zip").toPath();
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(TestClass.class, FeatureAPI.class)
+            .addKeepClassAndMembersRules(TestClass.class)
+            .addKeepClassAndMembersRules(FeatureClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .addFeatureSplit(
+                builder -> simpleSplitProvider(builder, featureCode, temp, FeatureClass.class))
+            .addOptionsModification(options -> options.outline.threshold = 2)
+            .compile()
+            .inspect(this::checkNoOutlineFromFeature);
+
+    // Check that parts of method1 and method2 in FeatureClass was not outlined.
+    CodeInspector featureInspector = new CodeInspector(featureCode);
+    ClassSubject featureClass = featureInspector.clazz(FeatureClass.class);
+    assertThat(featureClass, isPresent());
+    String outlineClassName =
+        ClassNameMapper.mapperFromString(compileResult.getProguardMap())
+            .getObfuscatedToOriginalMapping()
+            .inverse
+            .get(OutlineOptions.CLASS_NAME);
+    assertFalse(invokesOutline(featureClass.uniqueMethodWithName("method1"), outlineClassName));
+    assertFalse(invokesOutline(featureClass.uniqueMethodWithName("method2"), outlineClassName));
+
+    // Run the code without the feature code present.
+    compileResult.run(parameters.getRuntime(), TestClass.class).assertSuccessWithOutput("12");
+
+    // Run the code with the feature code present.
+    compileResult
+        .addRunClasspathFiles(featureCode)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput("1234");
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      method1(1, 2);
+      if (FeatureAPI.hasFeature()) {
+        FeatureAPI.feature(3);
+      }
+    }
+
+    public static void method1(int i1, int i2) {
+      System.out.print(i1 + "" + i2);
+    }
+
+    public static void method2(int i1, int i2) {
+      System.out.print(i1 + "" + i2);
+    }
+  }
+
+  public static class FeatureAPI {
+    private static String featureClassName() {
+      return FeatureAPI.class.getPackage().getName() + ".B149971007$FeatureClass";
+    }
+
+    public static boolean hasFeature() {
+      try {
+        Class.forName(featureClassName());
+      } catch (ClassNotFoundException e) {
+        return false;
+      }
+      return true;
+    }
+
+    public static void feature(int i)
+        throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
+            IllegalAccessException {
+      Class<?> featureClass = Class.forName(featureClassName());
+      Method featureMethod = featureClass.getMethod("feature", int.class);
+      featureMethod.invoke(null, i);
+    }
+  }
+
+  public static class FeatureClass {
+    private int i;
+
+    FeatureClass(int i) {
+      this.i = i;
+    }
+
+    public int getI() {
+      return i;
+    }
+
+    public static void feature(int i) {
+      FeatureClass.method1(new FeatureClass(i), new FeatureClass(i + 1));
+    }
+
+    public static void method1(FeatureClass fc1, FeatureClass fc2) {
+      System.out.print(fc1.getI() + "" + fc2.getI());
+    }
+
+    public static void method2(FeatureClass fc1, FeatureClass fc2) {
+      System.out.print(fc1.getI() + "" + fc2.getI());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java b/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java
index 5b887eb..892b7d2 100644
--- a/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.lang.reflect.TypeVariable;
@@ -59,7 +60,9 @@
   private void test(R8TestBuilder<?> builder) throws Exception {
     builder
         .addKeepRules("-dontoptimize")
-        .addKeepRules("-keepattributes InnerClasses,EnclosingMethod,Signature")
+        .addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
+        .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
+        .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
         .addKeepMainRule(Main.class)
         .addProgramClasses(Main.class)
         .addProgramClassesAndInnerClasses(A.class, B.class, CY.class, CYY.class)
diff --git a/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java b/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java
new file mode 100644
index 0000000..58e43469
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java
@@ -0,0 +1,106 @@
+// 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.b149890887;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MissingLibraryTargetTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("A::foo");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public MissingLibraryTargetTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private static Class<?> MAIN = TestClass.class;
+  private static List<Class<?>> PROGRAM = ImmutableList.of(MAIN, DerivedProgramClass.class);
+  private static List<Class<?>> LIBRARY =
+      ImmutableList.of(PresentLibraryInterface.class, PresentLibraryClass.class);
+
+  private List<Path> runtimeClasspath() throws Exception {
+    return buildOnDexRuntime(
+        parameters,
+        jarTestClasses(
+            ImmutableList.<Class<?>>builder()
+                .addAll(LIBRARY)
+                .add(MissingLibraryClass.class)
+                .build()));
+  }
+
+  private boolean isDesugaring() {
+    return parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.N);
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(PROGRAM)
+        .addRunClasspathFiles(runtimeClasspath())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .allowDiagnosticWarningMessages(isDesugaring())
+        .addProgramClasses(PROGRAM)
+        .addKeepMainRule(MAIN)
+        .addClasspathClasses(LIBRARY)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepRules("-dontwarn")
+        .compile()
+        .addRunClasspathFiles(runtimeClasspath())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  // Non-present class with the actual definition.
+  static class MissingLibraryClass {
+    public void foo() {
+      System.out.println("A::foo");
+    }
+  }
+
+  // Present library interface declaring the method. This is needed to hit the member rebinding
+  // issue. If not here the initial resolution will fail and no search will be done.
+  interface PresentLibraryInterface {
+    void foo();
+  }
+
+  // Present library class to trigger the search for the "first library definition".
+  static class PresentLibraryClass extends MissingLibraryClass implements PresentLibraryInterface {
+    // Intentionally empty.
+  }
+
+  // Program type that needs to be targeted to initiate rebinding.
+  static class DerivedProgramClass extends PresentLibraryClass {
+    // Intentionally empty.
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new DerivedProgramClass().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b150274427/JsrRetRegressionTest.java b/src/test/java/com/android/tools/r8/regress/b150274427/JsrRetRegressionTest.java
new file mode 100644
index 0000000..3c41a63
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b150274427/JsrRetRegressionTest.java
@@ -0,0 +1,92 @@
+// 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.b150274427;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.transformers.MethodTransformer;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class JsrRetRegressionTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public JsrRetRegressionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testUnreachableJsrRet() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClassFileData(getTransformClass(false))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(RuntimeException.class);
+  }
+
+  @Test
+  public void testReachableJsrRet() throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClassFileData(getTransformClass(true))
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertFailureWithErrorThatThrows(VerifyError.class);
+      return;
+    }
+    try {
+      testForD8()
+          .addProgramClassFileData(getTransformClass(true))
+          .setMinApi(parameters.getApiLevel())
+          .compileWithExpectedDiagnostics(
+              diagnostics -> {
+                diagnostics.assertErrorMessageThatMatches(containsString("RET"));
+              });
+      fail();
+    } catch (CompilationFailedException e) {
+      // Expected error.
+    }
+  }
+
+  private byte[] getTransformClass(boolean replaceThrow) throws IOException {
+    return transformer(TestClass.class)
+        .setVersion(50)
+        .addMethodTransformer(
+            new MethodTransformer() {
+              @Override
+              public void visitInsn(int opcode) {
+                if (opcode == Opcodes.ATHROW) {
+                  if (!replaceThrow) {
+                    super.visitInsn(opcode);
+                  }
+                  super.visitVarInsn(Opcodes.RET, 0);
+                } else {
+                  super.visitInsn(opcode);
+                }
+              }
+            })
+        .transform();
+  }
+
+  private static class TestClass {
+
+    public static void main(String[] args) {
+      throw new RuntimeException();
+      // Reachable or unreachable JSR inserted here.
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java b/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java
new file mode 100644
index 0000000..68c1d36
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java
@@ -0,0 +1,77 @@
+// 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.b150400371;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import it.unimi.dsi.fastutil.ints.IntArraySet;
+import it.unimi.dsi.fastutil.ints.IntSet;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DebuginfoForInlineFrameRegressionTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntime(Version.DEFAULT).withAllApiLevels().build();
+  }
+
+  public DebuginfoForInlineFrameRegressionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testNoLinesForNonInline() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(DebuginfoForInlineFrameRegressionTest.class)
+        .addKeepMainRule(InlineInto.class)
+        .addKeepRules("-keepparameternames")
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), InlineInto.class)
+        .assertSuccessWithOutputLines("42foo")
+        .inspect(
+            (inspector) -> {
+              MethodSubject main =
+                  inspector.method(InlineInto.class.getDeclaredMethod("main", String[].class));
+              IntSet lines = new IntArraySet(main.getLineNumberTable().getLines());
+              assertEquals(2, lines.size());
+            });
+  }
+
+  public static class InlineInto {
+    public static void main(String[] args) {
+      boolean late = System.currentTimeMillis() > 2;
+      String a = late ? "42" : "43";
+      String b = late ? "foo" : "bar";
+      String foo = InlineFrom.foo();
+      String result;
+      if (System.currentTimeMillis() < 2) {
+        result = a + b + foo + " that will never happen";
+      } else {
+        result = a + b;
+      }
+      System.out.println(late ? result : "never");
+      if (!late) {
+        System.out.println(late ? result : "never");
+        System.out.println(late ? result : "never");
+      }
+    }
+  }
+
+  public static class InlineFrom {
+    public static String foo() {
+      return "bar" + System.currentTimeMillis();
+    }
+  }
+}
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
new file mode 100644
index 0000000..ffc9114
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
@@ -0,0 +1,381 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.virtualtargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import 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;
+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.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KeptTargetsIncompleteLookupTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public KeptTargetsIncompleteLookupTest(TestParameters parameters) {
+    // Empty to satisfy construction of none-runtime.
+  }
+
+  private LookupResultSuccess testLookup(Class<?> methodToBeKept) throws Exception {
+    return testLookup(B.class, methodToBeKept, methodToBeKept, A.class, C.class);
+  }
+
+  private LookupResultSuccess testLookup(
+      Class<?> initial,
+      Class<?> methodToBeKept,
+      Class<?> classToBeKept,
+      Class<?>... expectedMethodHolders)
+      throws Exception {
+    return testLookup(
+        initial,
+        methodToBeKept,
+        classToBeKept,
+        Arrays.asList(expectedMethodHolders),
+        Arrays.asList(I.class, A.class, B.class, C.class));
+  }
+
+  private LookupResultSuccess testLookup(
+      Class<?> initial,
+      Class<?> methodToBeKept,
+      Class<?> classToBeKept,
+      Collection<Class<?>> expectedMethodHolders,
+      Collection<Class<?>> classes)
+      throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(classes).build(),
+            factory -> {
+              List<ProguardConfigurationRule> rules = new ArrayList<>();
+              rules.addAll(buildKeepRuleForClassAndMethods(methodToBeKept, factory));
+              rules.addAll(buildKeepRuleForClass(classToBeKept, factory));
+              return rules;
+            });
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexMethod method = buildNullaryVoidMethod(initial, "foo", appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Unrelated.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    assertTrue(lookupResult.isLookupResultSuccess());
+    LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
+    Set<String> targets =
+        lookupResultSuccess.getMethodTargets().stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    Set<String> expected =
+        expectedMethodHolders.stream()
+            .map(c -> c.getTypeName() + ".foo")
+            .collect(Collectors.toSet());
+    assertEquals(expected, targets);
+    return lookupResultSuccess;
+  }
+
+  @Test
+  public void testCompleteLookupResultWhenKeepingUnrelated() throws Exception {
+    // I {
+    //   foo()
+    // }
+    //
+    // A implements I {
+    //   foo() <-- resolved
+    // }
+    //
+    // B extends A { } <-- initial
+    //
+    // C extends B {
+    //   foo()
+    // }
+    assertTrue(
+        testLookup(B.class, Unrelated.class, Unrelated.class, A.class, C.class).isComplete());
+  }
+
+  @Test
+  public void testKeptResolvedAndNoKeepInSubtreeFromInitial() throws Exception {
+    // I {
+    //   foo()
+    // }
+    //
+    // A implements I {
+    //   foo() <-- kept, resolved
+    // }
+    //
+    // B extends A { } <-- initial
+    //
+    // C extends B {
+    //   foo()
+    // }
+    assertTrue(testLookup(A.class).isIncomplete());
+  }
+
+  @Test
+  public void testIncompleteLookupResultWhenKeepingStaticReceiver() throws Exception {
+    // I {
+    //   foo()
+    // }
+    //
+    // A implements I {
+    //   foo() <-- resolved
+    // }
+    //
+    // B extends A { } <-- initial, kept
+    //
+    // C extends B {
+    //   foo()
+    // }
+    assertTrue(testLookup(B.class).isIncomplete());
+  }
+
+  @Test
+  public void testIncompleteLookupResultWhenKeepingSubTypeMethod() throws Exception {
+    // I {
+    //   foo()
+    // }
+    //
+    // A implements I {
+    //   foo() <-- resolved
+    // }
+    //
+    // B extends A { } <-- initial
+    //
+    // C extends B {
+    //   foo() <-- kept
+    // }
+    assertTrue(testLookup(C.class).isIncomplete());
+  }
+
+  @Test
+  public void testIncompleteLookupResultWhenKeepingMethodOnParentToResolveAndKeepClass()
+      throws Exception {
+    // I {
+    //   foo()
+    // }
+    //
+    // A implements I {
+    //   foo() <-- kept
+    // }
+    //
+    // B extends A { }
+    //
+    // C extends B { <-- initial, resolved, kept
+    //   foo()
+    // }
+    assertTrue(testLookup(C.class, A.class, C.class, C.class).isIncomplete());
+  }
+
+  @Test
+  public void testCompleteLookupResultWhenKeepingMethodOnParentToResolveAndNotKeepClass()
+      throws Exception {
+    // I {
+    //   foo()
+    // }
+    //
+    // A implements I {
+    //   foo() <-- kept
+    // }
+    //
+    // B extends A { }
+    //
+    // C extends B { <-- initial, resolved
+    //   foo()
+    // }
+    assertTrue(testLookup(C.class, A.class, Unrelated.class, C.class).isComplete());
+  }
+
+  @Test
+  public void testLibraryWithNoOverride() throws Exception {
+    // ----- Library -----
+    // I {
+    //   foo()
+    // }
+    //
+    // A implements I {
+    //   foo() <-- resolved
+    // }
+    //
+    // ----- Program -----
+    // B extends A { } <-- initial
+    AppView<AppInfoWithSubtyping> appView =
+        computeAppViewWithSubtyping(
+            buildClasses(Collections.singletonList(B.class), Arrays.asList(A.class, I.class))
+                .build());
+    AppInfoWithSubtyping appInfo = appView.appInfo();
+    DexMethod method = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+    DexType typeA = buildType(A.class, appInfo.dexItemFactory());
+    DexType typeB = buildType(B.class, appInfo.dexItemFactory());
+    DexProgramClass classB = appInfo.definitionForProgramType(typeB);
+    LookupResult lookupResult =
+        resolutionResult.lookupVirtualDispatchTargets(
+            classB,
+            appView,
+            (type, subTypeConsumer, callSiteConsumer) -> {
+              if (type == typeA) {
+                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> expected = ImmutableSet.of(A.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+    assertTrue(lookupResultSuccess.isComplete());
+  }
+
+  @Test
+  public void testPrivateKeep() throws Exception {
+    // Unrelated { <-- kept
+    //   private foo() <-- kept
+    // }
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(buildClasses(Unrelated.class).build(), Unrelated.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexMethod method = buildNullaryVoidMethod(Unrelated.class, "foo", appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+    DexProgramClass context =
+        appView.definitionForProgramType(buildType(Unrelated.class, appInfo.dexItemFactory()));
+    LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appView);
+    assertTrue(lookupResult.isLookupResultSuccess());
+    LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
+    Set<String> targets =
+        lookupResultSuccess.getMethodTargets().stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    Set<String> expected = ImmutableSet.of(Unrelated.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+    assertTrue(lookupResultSuccess.isIncomplete());
+  }
+
+  @Test
+  public void testInterfaceKept() throws Exception {
+    // I {
+    //   foo() <-- kept
+    // }
+    //
+    // A implements I {
+    //   foo() <-- resolved
+    // }
+    //
+    // B extends A { } <-- initial, kept
+    //
+    // C extends B {
+    //   foo()
+    // }
+    assertTrue(testLookup(B.class, I.class, B.class, A.class, C.class).isIncomplete());
+  }
+
+  @Test
+  public void testInterfaceKeptWithoutKeepInLookup() throws Exception {
+    // I {
+    //   foo() <-- kept
+    // }
+    //
+    // A implements I {
+    //   foo() <-- resolved
+    // }
+    //
+    // B extends A { } <-- initial
+    //
+    // C extends B {
+    //   foo()
+    // }
+    assertTrue(testLookup(B.class, I.class, Unrelated.class, A.class, C.class).isComplete());
+  }
+
+  @Test
+  public void testInterfaceKeptAndImplementedInSupType() throws Exception {
+    // X {
+    //   foo() <-- resolved
+    // }
+    //
+    // I {
+    //   foo() <-- kept
+    // }
+    //
+    // Y extends X implements I { } <-- initial
+    //
+    // Z extends Y { } <-- kept
+    assertTrue(
+        testLookup(
+                Y.class,
+                I.class,
+                Z.class,
+                Collections.singleton(X.class),
+                Arrays.asList(X.class, I.class, Y.class, Z.class))
+            .isIncomplete());
+  }
+
+  // TODO(b/148769279): We need to look at the call site to see if it overrides
+  //   a method that is kept.
+
+  public static class Unrelated {
+
+    private void foo() {
+      System.out.println("Unrelated.foo");
+    }
+  }
+
+  public interface I {
+    void foo();
+  }
+
+  public static class A implements I {
+    @Override
+    public void foo() {
+      System.out.println("A.foo");
+    }
+  }
+
+  public static class B extends A {}
+
+  public static class C extends B {
+    @Override
+    public void foo() {
+      System.out.println("C.foo");
+    }
+  }
+
+  public static class X {
+
+    public void foo() {
+      System.out.println("X.foo");
+    }
+  }
+
+  public static class Y extends X implements I {}
+
+  public static class Z extends Y {}
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingLineSeparatorTest.java b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingLineSeparatorTest.java
new file mode 100644
index 0000000..9b828fc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingLineSeparatorTest.java
@@ -0,0 +1,120 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.rewrite;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNull;
+import static junit.framework.TestCase.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.rewrite.ServiceLoaderRewritingTest.MainRunner;
+import com.android.tools.r8.rewrite.ServiceLoaderRewritingTest.Service;
+import com.android.tools.r8.rewrite.ServiceLoaderRewritingTest.ServiceImpl;
+import com.android.tools.r8.rewrite.ServiceLoaderRewritingTest.ServiceImpl2;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.zip.ZipFile;
+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 ServiceLoaderRewritingLineSeparatorTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final Separator lineSeparator;
+
+  private final String EXPECTED_OUTPUT =
+      StringUtils.lines("Hello World!", "Hello World!", "Hello World!");
+
+  enum Separator {
+    WINDOWS,
+    LINUX;
+
+    public String getSeparator() {
+      switch (this) {
+        case WINDOWS:
+          return "\r\n";
+        case LINUX:
+          return "\n";
+        default:
+          assert false;
+      }
+      return null;
+    }
+  }
+
+  @Parameters(name = "{0}, separator: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), Separator.values());
+  }
+
+  public ServiceLoaderRewritingLineSeparatorTest(TestParameters parameters, Separator separator) {
+    this.parameters = parameters;
+    this.lineSeparator = separator;
+  }
+
+  @Test
+  public void testRewritingWithMultipleWithLineSeparator()
+      throws IOException, CompilationFailedException, ExecutionException {
+    Path path = temp.newFile("out.zip").toPath();
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ServiceLoaderRewritingTest.class)
+        .addKeepMainRule(MainRunner.class)
+        .setMinApi(parameters.getApiLevel())
+        .addDataEntryResources(
+            DataEntryResource.fromBytes(
+                StringUtils.join(
+                        lineSeparator.getSeparator(),
+                        ServiceImpl.class.getTypeName(),
+                        ServiceImpl2.class.getTypeName())
+                    .getBytes(),
+                "META-INF/services/" + Service.class.getTypeName(),
+                Origin.unknown()))
+        .compile()
+        .writeToZip(path)
+        .run(parameters.getRuntime(), MainRunner.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT + StringUtils.lines("Hello World 2!"))
+        .inspect(
+            inspector -> {
+              // Check that we have actually rewritten the calls to ServiceLoader.load.
+              assertEquals(0, getServiceLoaderLoads(inspector));
+            });
+
+    // Check that we have removed the service configuration from META-INF/services.
+    ZipFile zip = new ZipFile(path.toFile());
+    assertNull(zip.getEntry("META-INF/services"));
+  }
+
+  private static long getServiceLoaderLoads(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(MainRunner.class);
+    assertTrue(classSubject.isPresent());
+    return classSubject.allMethods().stream()
+        .mapToLong(
+            method ->
+                method
+                    .streamInstructions()
+                    .filter(ServiceLoaderRewritingLineSeparatorTest::isServiceLoaderLoad)
+                    .count())
+        .sum();
+  }
+
+  private static boolean isServiceLoaderLoad(InstructionSubject instruction) {
+    return instruction.isInvokeStatic()
+        && instruction.getMethod().qualifiedName().contains("ServiceLoader.load");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java
index ccfefda..4749163 100644
--- a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java
@@ -137,7 +137,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public ServiceLoaderRewritingTest(TestParameters parameters) {
@@ -150,7 +150,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(ServiceLoaderRewritingTest.class)
         .addKeepMainRule(MainRunner.class)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .addDataEntryResources(
             DataEntryResource.fromBytes(
                 StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
@@ -178,7 +178,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(ServiceLoaderRewritingTest.class)
         .addKeepMainRule(MainRunner.class)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .addDataEntryResources(
             DataEntryResource.fromBytes(
                 StringUtils.lines(ServiceImpl.class.getTypeName(), ServiceImpl2.class.getTypeName())
@@ -236,7 +236,7 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(ServiceLoaderRewritingTest.class)
             .addKeepMainRule(OtherRunner.class)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .addDataEntryResources(
                 DataEntryResource.fromBytes(
                     StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
@@ -304,7 +304,7 @@
             .addInnerClasses(ServiceLoaderRewritingTest.class)
             .addKeepMainRule(MainRunner.class)
             .addKeepClassRules(Service.class)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .addDataEntryResources(
                 DataEntryResource.fromBytes(
                     StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/B149729626.java b/src/test/java/com/android/tools/r8/shaking/annotations/B149729626.java
index ab3c663..5b8f418 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/B149729626.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/B149729626.java
@@ -43,13 +43,7 @@
         .addKeepRules(
             "-keepclassmembers @" + Marker.class.getTypeName() + " class * {",
             "  <init>(...);",
-            "}",
-            // TODO(b/149729626): Should not be required.
-            "-keep class " + Marked.class.getTypeName(),
-            // TODO(b/149729626): Should not be required.
-            "-keep class " + TestClass.class.getTypeName() + " { void makeMarkerLive(); }")
-        // TODO(b/149729626): Should not be required.
-        .addKeepRuntimeVisibleAnnotations()
+            "}")
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
@@ -66,11 +60,7 @@
             "-if @" + Marker.class.getTypeName() + " class *",
             "-keep class <1> {",
             "  <init>(...);",
-            "}",
-            // TODO(b/149729626): Should not be required.
-            "-keep class " + TestClass.class.getTypeName() + " { void makeMarkerLive(); }")
-        // TODO(b/149729626): Should not be required.
-        .addKeepRuntimeVisibleAnnotations()
+            "}")
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
@@ -101,10 +91,6 @@
     public static void main(String[] args) {
       System.out.println(Marked.class);
     }
-
-    static void makeMarkerLive() {
-      System.out.println(Marker.class);
-    }
   }
 
   @Target(ElementType.TYPE)
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalKeepIfKeptTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalKeepIfKeptTest.java
index 50443fc..790ea8d 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalKeepIfKeptTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalKeepIfKeptTest.java
@@ -8,6 +8,7 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -62,9 +63,9 @@
               ClassSubject classSubject = inspector.clazz(StaticallyReferenced.class);
               assertThat(classSubject, isPresent());
               assertEquals(0, classSubject.allFields().size());
-              // TODO(b/132318799): Full mode no-marker should not keep <init>() when not specified.
-              assertEquals(useMarker ? 0 : 1, classSubject.allMethods().size());
-              assertEquals(!useMarker, classSubject.init().isPresent());
+              // TODO(b/132318799): Should not keep <init>() when not specified.
+              assertEquals(1, classSubject.allMethods().size());
+              assertTrue(classSubject.init().isPresent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/KeepClassesWithAnnotatedFieldsReferencedFromGetterTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/KeepClassesWithAnnotatedFieldsReferencedFromGetterTest.java
new file mode 100644
index 0000000..6811351
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/KeepClassesWithAnnotatedFieldsReferencedFromGetterTest.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.ifrule;
+
+
+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.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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepClassesWithAnnotatedFieldsReferencedFromGetterTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public KeepClassesWithAnnotatedFieldsReferencedFromGetterTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(KeepClassesWithAnnotatedFieldsReferencedFromGetterTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules(
+            "-if class *",
+            "-keepclasseswithmembers class <1> {",
+            "  <init>(...);",
+            "  @" + typeName(MyAnnotation.class) + " <fields>;",
+            "}")
+        .enableInliningAnnotations()
+        .enableMemberValuePropagationAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(LiveDataClass.class), isPresent());
+              assertThat(inspector.clazz(DeadDataClass.class), not(isPresent()));
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      LiveDataClass obj = (LiveDataClass) getDataClass().getDeclaredConstructor().newInstance();
+      setField(obj, "Hello world!");
+      System.out.println(obj.getField());
+    }
+
+    @NeverInline
+    @NeverPropagateValue
+    static Class<?> getDataClass() {
+      return LiveDataClass.class;
+    }
+
+    @NeverInline
+    static void setField(Object object, String value) throws Exception {
+      getDataClass().getDeclaredField("field").set(object, value);
+    }
+  }
+
+  static class LiveDataClass {
+
+    @MyAnnotation String field;
+
+    String getField() {
+      return field;
+    }
+  }
+
+  static class DeadDataClass {
+
+    @MyAnnotation String field;
+
+    String getField() {
+      return field;
+    }
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.FIELD)
+  @interface MyAnnotation {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/KeepClassesWithAnnotatedFieldsTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/KeepClassesWithAnnotatedFieldsTest.java
new file mode 100644
index 0000000..76244dd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/KeepClassesWithAnnotatedFieldsTest.java
@@ -0,0 +1,83 @@
+// 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.ifrule;
+
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@RunWith(Parameterized.class)
+public class KeepClassesWithAnnotatedFieldsTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public KeepClassesWithAnnotatedFieldsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(KeepClassesWithAnnotatedFieldsTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules(
+            "-if class * { @" + typeName(MyAnnotation.class) + " <fields>; }",
+            "-keep class <1> { <init>(); }",
+            "-keepclassmembers class * {",
+            "  @" + typeName(MyAnnotation.class) + " <fields>;",
+            "}")
+        .enableInliningAnnotations()
+        .enableMemberValuePropagationAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      DataClass obj = (DataClass) getDataClass().getDeclaredConstructor().newInstance();
+      setField(obj, "Hello world!");
+      System.out.println(obj.field);
+    }
+
+    @NeverInline
+    @NeverPropagateValue
+    static Class<?> getDataClass() {
+      return DataClass.class;
+    }
+
+    @NeverInline
+    static void setField(Object object, String value) throws Exception {
+      getDataClass().getDeclaredField("field").set(object, value);
+    }
+  }
+
+  static class DataClass {
+
+    @MyAnnotation
+    String field;
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.FIELD)
+  @interface MyAnnotation {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepInterfaceMethodTest.java
new file mode 100644
index 0000000..550957e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepInterfaceMethodTest.java
@@ -0,0 +1,128 @@
+// 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.keepclassmembers;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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 KeepInterfaceMethodTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public KeepInterfaceMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testIProguard() throws CompilationFailedException, IOException, ExecutionException {
+    testForProguard()
+        .addProgramClasses(I.class)
+        .addKeepRules(
+            "-keepclassmembers class " + I.class.getTypeName() + " { void foo(); }",
+            "-keep class " + I.class.getTypeName() + " { }",
+            "-dontwarn")
+        .compile()
+        .inspect(this::inspectIClassAndMethodIsPresent);
+  }
+
+  @Test
+  public void testIR8() throws CompilationFailedException, IOException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class)
+        .addKeepRules("-keepclassmembers class " + I.class.getTypeName() + " { void foo(); }")
+        .addKeepRules("-keep class " + I.class.getTypeName() + " { }")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspectIClassAndMethodIsPresent);
+  }
+
+  @Test
+  public void testAProguard() throws CompilationFailedException, IOException, ExecutionException {
+    assumeTrue(parameters.isCfRuntime());
+    testForProguard()
+        .addProgramClasses(I.class, A.class, B.class, Main.class)
+        .addKeepRules(
+            "-keepclassmembers class " + I.class.getTypeName() + " { void foo(); }", "-dontwarn")
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A.foo")
+        .inspect(this::inspectIClassAndMethodIsPresent);
+  }
+
+  @Test
+  public void testAR8() throws CompilationFailedException, IOException, ExecutionException {
+    testForR8Compat(parameters.getBackend())
+        .addProgramClasses(I.class, A.class, B.class, Main.class)
+        .addKeepRules("-keepclassmembers class " + I.class.getTypeName() + " { void foo(); }")
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A.foo")
+        .inspect(this::inspectIClassAndMethodIsPresent);
+  }
+
+  private void inspectIClassAndMethodIsPresent(CodeInspector inspector) {
+    checkClassAndMethodIsPresent(inspector, I.class);
+  }
+
+  private void checkClassAndMethodIsPresent(CodeInspector inspector, Class<?> clazz) {
+    ClassSubject clazzSubject = inspector.clazz(clazz);
+    assertThat(clazzSubject, isPresent());
+    MethodSubject foo = clazzSubject.uniqueMethodWithName("foo");
+    assertThat(foo, isNotRenamed());
+  }
+
+  public interface I {
+    void foo();
+  }
+
+  public static class A implements I {
+    @Override
+    public void foo() {
+      System.out.println("A.foo");
+    }
+  }
+
+  public static class B implements I {
+
+    @Override
+    public void foo() {
+      System.out.println("B.foo");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      runI(args.length == 0 ? new A() : new B());
+    }
+
+    private static void runI(I i) {
+      i.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index baadc68..530b5b5 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -191,6 +191,21 @@
         });
   }
 
+  public ClassFileTransformer setVersion(int newVersion) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public void visit(
+              int version,
+              int access,
+              String name,
+              String signature,
+              String superName,
+              String[] interfaces) {
+            super.visit(newVersion, access, name, signature, superName, interfaces);
+          }
+        });
+  }
 
   public ClassFileTransformer setMinVersion(CfVm jdk) {
     return setMinVersion(jdk.getClassfileVersion());
diff --git a/third_party/youtube/youtube.android_15.08.tar.gz.sha1 b/third_party/youtube/youtube.android_15.08.tar.gz.sha1
new file mode 100644
index 0000000..285c854
--- /dev/null
+++ b/third_party/youtube/youtube.android_15.08.tar.gz.sha1
@@ -0,0 +1 @@
+803d7565c1d56568cfe5d8da18e7fe82bfcac006
\ No newline at end of file
diff --git a/third_party/youtube/youtube.android_15.09.tar.gz.sha1 b/third_party/youtube/youtube.android_15.09.tar.gz.sha1
new file mode 100644
index 0000000..b5c44a1
--- /dev/null
+++ b/third_party/youtube/youtube.android_15.09.tar.gz.sha1
@@ -0,0 +1 @@
+6acec0dc032536d90798060badaed13154c92685
\ No newline at end of file
diff --git a/tools/compiledump.py b/tools/compiledump.py
new file mode 100755
index 0000000..6f78eef
--- /dev/null
+++ b/tools/compiledump.py
@@ -0,0 +1,189 @@
+#!/usr/bin/env python
+# 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.
+
+import argparse
+import os
+import subprocess
+import sys
+import zipfile
+
+import archive
+import jdk
+import retrace
+import utils
+
+
+def make_parser():
+  parser = argparse.ArgumentParser(description = 'Compile a dump artifact.')
+  parser.add_argument(
+    '-d',
+    '--dump',
+    help='Dump file to compile',
+    default=None)
+  parser.add_argument(
+    '-c',
+    '--compiler',
+    help='Compiler to use (default read from version file)',
+    default=None)
+  parser.add_argument(
+    '-v',
+    '--version',
+    help='Compiler version to use (default read from dump version file).'
+      'Valid arguments are:'
+      '  "master" to run from your own tree,'
+      '  "X.Y.Z" to run a specific version, or'
+      '  <hash> to run that hash from master.',
+    default=None)
+  parser.add_argument(
+    '--nolib',
+    help='Use the non-lib distribution (default uses the lib distribution)',
+    default=False,
+    action='store_true')
+  parser.add_argument(
+    '--ea',
+    help='Enable Java assertions when running the compiler (default disabled)',
+    default=False,
+    action='store_true')
+  parser.add_argument(
+      '--debug-agent',
+      help='Enable Java debug agent and suspend compilation (default disabled)',
+      default=False,
+      action='store_true')
+  return parser
+
+def error(msg):
+  print msg
+  sys.exit(1)
+
+class Dump(object):
+
+  def __init__(self, directory):
+    self.directory = directory
+
+  def if_exists(self, name):
+    f = os.path.join(self.directory, name)
+    if os.path.exists(f):
+      return f
+    return None
+
+  def program_jar(self):
+    return self.if_exists('program.jar')
+
+  def library_jar(self):
+    return self.if_exists('library.jar')
+
+  def classpath_jar(self):
+    return self.if_exists('classpath.jar')
+
+  def config_file(self):
+    return self.if_exists('proguard.config')
+
+  def version_file(self):
+    return self.if_exists('r8-version')
+
+  def version(self):
+    f = self.version_file()
+    if f:
+      return open(f).read().split(' ')[0]
+    return None
+
+def read_dump(args, temp):
+  if args.dump is None:
+    error("A dump file must be specified")
+  dump_file = zipfile.ZipFile(args.dump, 'r')
+  with utils.ChangedWorkingDirectory(temp):
+    dump_file.extractall()
+    return Dump(temp)
+
+def determine_version(args, dump):
+  if args.version is None:
+    return dump.version()
+  return args.version
+
+def determine_compiler(args, dump):
+  compilers = ('d8', 'r8', 'r8full')
+  if args.compiler not in compilers:
+    error("Unable to determine a compiler to use. Valid options: %s" % compilers.join(', '))
+  return args.compiler
+
+def determine_output(args, temp):
+  return os.path.join(temp, 'out.jar')
+
+def download_distribution(args, version, temp):
+  if version == 'master':
+    return utils.R8_JAR if args.nolib else utils.R8LIB_JAR
+  name = 'r8.jar' if args.nolib else 'r8lib.jar'
+  source = archive.GetUploadDestination(version, name, is_hash(version))
+  dest = os.path.join(temp, 'r8.jar')
+  utils.download_file_from_cloud_storage(source, dest)
+  return dest
+
+def prepare_wrapper(dist, temp):
+  wrapper_file = os.path.join(
+      utils.REPO_ROOT,
+      'src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java')
+  subprocess.check_output([
+    jdk.GetJavacExecutable(),
+    wrapper_file,
+    '-d', temp,
+    '-cp', dist,
+    ])
+  return temp
+
+def is_hash(version):
+  return len(version) == 40
+
+def run(args, otherargs):
+  with utils.TempDir() as 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)
+    wrapper_dir = prepare_wrapper(jar, temp)
+    cmd = [jdk.GetJavaExecutable()]
+    if args.debug_agent:
+      if not args.nolib:
+        print "WARNING: Running debugging agent on r8lib is questionable..."
+      cmd.append(
+          '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005')
+    if args.ea:
+      cmd.append('-ea')
+    cmd.extend(['-cp', '%s:%s' % (wrapper_dir, jar)])
+    if compiler == 'd8':
+      cmd.append('com.android.tools.r8.D8')
+    if compiler.startswith('r8'):
+      cmd.append('com.android.tools.r8.utils.CompileDumpCompatR8')
+    if compiler == 'r8':
+      cmd.append('--compat')
+    cmd.append(dump.program_jar())
+    cmd.extend(['--output', out])
+    if dump.library_jar():
+      cmd.extend(['--lib', dump.library_jar()])
+    if dump.classpath_jar():
+      cmd.extend(['--classpath', dump.classpath_jar()])
+    if compiler != 'd8' and dump.config_file():
+      cmd.extend(['--pg-conf', dump.config_file()])
+    cmd.extend(otherargs)
+    utils.PrintCmd(cmd)
+    try:
+      print subprocess.check_output(cmd, stderr=subprocess.STDOUT)
+      return 0
+    except subprocess.CalledProcessError, e:
+      print e.output
+      if not args.nolib:
+        stacktrace = os.path.join(temp, 'stacktrace')
+        open(stacktrace, 'w+').write(e.output)
+        local_map = utils.R8LIB_MAP if version == 'master' else None
+        hash_or_version = None if version == 'master' else version
+        print "=" * 80
+        print " RETRACED OUTPUT"
+        print "=" * 80
+        retrace.run(local_map, hash_or_version, stacktrace, is_hash(version))
+      return 1
+
+if __name__ == '__main__':
+  (args, otherargs) = make_parser().parse_known_args(sys.argv[1:])
+  sys.exit(run(args, otherargs))
diff --git a/tools/retrace.py b/tools/retrace.py
index e8fdf89..771f23d 100755
--- a/tools/retrace.py
+++ b/tools/retrace.py
@@ -53,16 +53,18 @@
 
 def main():
   args = parse_arguments()
-  r8lib_map_path = args.map
   if args.tag:
     hash_or_version = find_version_or_hash_from_tag(args.tag)
   else:
     hash_or_version = args.commit_hash or args.version
+  return run(args.map, hash_or_version, args.stacktrace, args.commit_hash is not None)
+
+def run(r8lib_map_path, hash_or_version, stacktrace, is_hash):
   if hash_or_version:
     download_path = archive.GetUploadDestination(
         hash_or_version,
         'r8lib.jar.map',
-        args.commit_hash is not None)
+        is_hash)
     if utils.file_exists_on_cloud_storage(download_path):
       r8lib_map_path = tempfile.NamedTemporaryFile().name
       utils.download_file_from_cloud_storage(download_path, r8lib_map_path)
@@ -78,8 +80,8 @@
     r8lib_map_path
   ]
 
-  if args.stacktrace:
-    retrace_args.append(args.stacktrace)
+  if stacktrace:
+    retrace_args.append(stacktrace)
 
   return subprocess.call(retrace_args)
 
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index ab01595..b96ccf4 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -189,6 +189,8 @@
 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
 ]
 
 def get_permutations():
diff --git a/tools/utils.py b/tools/utils.py
index 6690ba2..16a0696 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -46,6 +46,7 @@
 D8_JAR = os.path.join(LIBS, 'd8.jar')
 R8_JAR = os.path.join(LIBS, 'r8.jar')
 R8LIB_JAR = os.path.join(LIBS, 'r8lib.jar')
+R8LIB_MAP = os.path.join(LIBS, 'r8lib.jar.map')
 R8_SRC_JAR = os.path.join(LIBS, 'r8-src.jar')
 R8LIB_EXCLUDE_DEPS_JAR = os.path.join(LIBS, 'r8lib-exclude-deps.jar')
 R8_FULL_EXCLUDE_DEPS_JAR = os.path.join(LIBS, 'r8-full-exclude-deps.jar')
diff --git a/tools/youtube_data.py b/tools/youtube_data.py
index 157d71e..e8accdd 100644
--- a/tools/youtube_data.py
+++ b/tools/youtube_data.py
@@ -28,6 +28,12 @@
 V14_44_BASE = os.path.join(BASE, 'youtube.android_14.44')
 V14_44_PREFIX = os.path.join(V14_44_BASE, 'YouTubeRelease')
 
+V15_08_BASE = os.path.join(BASE, 'youtube.android_15.08')
+V15_08_PREFIX = os.path.join(V15_08_BASE, 'YouTubeRelease')
+
+V15_09_BASE = os.path.join(BASE, 'youtube.android_15.09')
+V15_09_PREFIX = os.path.join(V15_09_BASE, 'YouTubeRelease')
+
 # NOTE: we always use android.jar for SDK v25, later we might want to revise it
 #       to use proper android.jar version for each of youtube version separately.
 ANDROID_JAR = utils.get_android_jar(25)
@@ -178,4 +184,64 @@
       'min-api' : ANDROID_L_API,
     }
   },
+  '15.08': {
+    'dex' : {
+      'inputs': [os.path.join(V15_08_BASE, 'YouTubeRelease_unsigned.apk')],
+      'pgmap': '%s_proguard.map' % V15_08_PREFIX,
+      'libraries' : [ANDROID_JAR],
+      'min-api' : ANDROID_L_API,
+    },
+    'deploy' : {
+      # When -injars and -libraryjars are used for specifying inputs library
+      # sanitization is on by default. For this version of YouTube -injars and
+      # -libraryjars are not used, but library sanitization is still required.
+      'sanitize_libraries': True,
+      'inputs': ['%s_deploy.jar' % V15_08_PREFIX],
+      'libraries' : [os.path.join(V15_08_BASE, 'legacy_YouTubeRelease_combined_library_jars.jar')],
+      'pgconf': [
+          '%s_proguard.config' % 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'),
+          os.path.join(V15_08_BASE, 'main_dex_YouTubeRelease_proguard.cfg')],
+      'min-api' : ANDROID_H_MR2_API,
+    },
+    'proguarded' : {
+      'inputs': ['%s_proguard.jar' % V15_08_PREFIX],
+      'pgmap': '%s_proguard.map' % V15_08_PREFIX,
+      'min-api' : ANDROID_L_API,
+    }
+  },
+  '15.09': {
+    'dex' : {
+      'inputs': [os.path.join(V15_09_BASE, 'YouTubeRelease_unsigned.apk')],
+      'pgmap': '%s_proguard.map' % V15_09_PREFIX,
+      'libraries' : [ANDROID_JAR],
+      'min-api' : ANDROID_L_API,
+    },
+    'deploy' : {
+      # When -injars and -libraryjars are used for specifying inputs library
+      # sanitization is on by default. For this version of YouTube -injars and
+      # -libraryjars are not used, but library sanitization is still required.
+      'sanitize_libraries': True,
+      'inputs': ['%s_deploy.jar' % V15_09_PREFIX],
+      'libraries' : [os.path.join(V15_09_BASE, 'legacy_YouTubeRelease_combined_library_jars.jar')],
+      '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'),
+          os.path.join(V15_09_BASE, 'main_dex_YouTubeRelease_proguard.cfg')],
+      'min-api' : ANDROID_H_MR2_API,
+    },
+    'proguarded' : {
+      'inputs': ['%s_proguard.jar' % V15_09_PREFIX],
+      'pgmap': '%s_proguard.map' % V15_09_PREFIX,
+      'min-api' : ANDROID_L_API,
+    }
+  },
 }