Merge commit '995cc548cab16a4a51873229d7238b1331771a39' into dev-release
diff --git a/src/main/java/com/android/tools/r8/ExtractMarker.java b/src/main/java/com/android/tools/r8/ExtractMarker.java
index c9f2032..bda3597 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarker.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -101,7 +101,7 @@
     options.minApiLevel = AndroidApiLevel.P.getLevel();
     DexApplication dexApp =
         new ApplicationReader(app, options, new Timing("ExtractMarker")).read();
-    return dexApp.dexItemFactory.extractMarker();
+    return dexApp.dexItemFactory.extractMarkers();
   }
 
   public static void main(String[] args)
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 6e95cac..4549d8e 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -7,17 +7,14 @@
 import static com.android.tools.r8.graph.ClassKind.LIBRARY;
 import static com.android.tools.r8.graph.ClassKind.PROGRAM;
 import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
-import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
 
 import com.android.tools.r8.ClassFileResourceProvider;
-import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.DataResourceProvider;
 import com.android.tools.r8.ProgramResource;
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.StringResource;
-import com.android.tools.r8.Version;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.ClassKind;
 import com.android.tools.r8.graph.DexApplication;
@@ -43,38 +40,26 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
-import com.android.tools.r8.utils.ZipUtils;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.io.ByteStreams;
-import com.google.common.io.Closer;
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.OpenOption;
 import java.nio.file.Paths;
-import java.nio.file.StandardOpenOption;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.Queue;
-import java.util.Set;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.stream.Collectors;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-import org.objectweb.asm.ClassVisitor;
 
 public class ApplicationReader {
 
   private final InternalOptions options;
   private final DexItemFactory itemFactory;
   private final Timing timing;
-  private final AndroidApp inputApp;
+  private AndroidApp inputApp;
 
   public interface ProgramClassConflictResolver {
     DexProgramClass resolveClassConflict(DexProgramClass a, DexProgramClass b);
@@ -123,7 +108,7 @@
       throws IOException, ExecutionException {
     assert verifyMainDexOptionsCompatible(inputApp, options);
     if (options.dumpInputToFile != null) {
-      dumpInputToFile();
+      inputApp = dumpInputToFile(inputApp, options);
       throw options.reporter.fatalError("Dumped compilation inputs to: " + options.dumpInputToFile);
     }
     timing.begin("DexApplication.read");
@@ -160,141 +145,9 @@
     return builder.build();
   }
 
-  private void dumpInputToFile() throws IOException  {
-    try {
-      List<ProgramResourceProvider> programResourceProviders =
-          inputApp.getProgramResourceProviders();
-      Set<DataEntryResource> dataEntryResources = inputApp.getDataEntryResourcesForTesting();
-      List<ProgramResource> programResourcesWithDescriptors = new ArrayList<>();
-      for (ProgramResourceProvider programResourceProvider : programResourceProviders) {
-        addProgramResourcesWithDescriptor(
-            programResourcesWithDescriptors, programResourceProvider.getProgramResources());
-      }
-
-      List<ProgramResource> libraryProgramResourcesWithDescriptors =
-          getProgramResourcesWithDescriptors(inputApp.getLibraryResourceProviders());
-
-      List<ProgramResource> classpathProgramResourcesWithDescriptors =
-          getProgramResourcesWithDescriptors(inputApp.getClasspathResourceProviders());
-
-      OpenOption[] openOptions =
-          new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
-      try (Closer closer = Closer.create()) {
-        try (ZipOutputStream out =
-            new ZipOutputStream(
-                Files.newOutputStream(Paths.get(options.dumpInputToFile), openOptions))) {
-          writeToZip(
-              dataEntryResources, programResourcesWithDescriptors, closer, out, "program.jar");
-          writeToZip(
-              ImmutableSet.of(),
-              libraryProgramResourcesWithDescriptors,
-              closer,
-              out,
-              "library.jar");
-          writeToZip(
-              ImmutableSet.of(),
-              classpathProgramResourcesWithDescriptors,
-              closer,
-              out,
-              "classpath.jar");
-          if (options.hasProguardConfiguration()) {
-            String proguardConfig = options.getProguardConfiguration().getParsedConfiguration();
-            ZipUtils.writeToZipStream(
-                out, "proguard.config", proguardConfig.getBytes(), ZipEntry.DEFLATED);
-          }
-
-          ZipUtils.writeToZipStream(
-              out, "r8-version", Version.getVersionString().getBytes(), ZipEntry.DEFLATED);
-        }
-      }
-    } catch (ResourceException e) {
-      options.reporter.fatalError("Failed to write input:" + e.getMessage());
-    }
-  }
-
-  private static void writeToZip(
-      Set<DataEntryResource> dataEntryResources,
-      List<ProgramResource> programResourcesWithDescriptors,
-      Closer closer,
-      ZipOutputStream out,
-      String entry)
-      throws IOException, ResourceException {
-    try (ByteArrayOutputStream programByteStream = new ByteArrayOutputStream()) {
-      try (ZipOutputStream archiveOutputStream = new ZipOutputStream(programByteStream)) {
-        ZipUtils.writeResourcesToZip(
-            programResourcesWithDescriptors, dataEntryResources, closer, archiveOutputStream);
-      }
-      ZipUtils.writeToZipStream(out, entry, programByteStream.toByteArray(), ZipEntry.DEFLATED);
-    }
-  }
-
-  private static List<ProgramResource> getProgramResourcesWithDescriptors(
-      List<ClassFileResourceProvider> classFileResourceProviders)
-      throws IOException, ResourceException {
-    ArrayList<ProgramResource> programResourcesWithDescriptors = new ArrayList<>();
-    for (ClassFileResourceProvider libraryResourceProvider : classFileResourceProviders) {
-      List<ProgramResource> programResources = new ArrayList<>();
-      for (String classDescriptor : libraryResourceProvider.getClassDescriptors()) {
-        programResources.add(libraryResourceProvider.getProgramResource(classDescriptor));
-      }
-      addProgramResourcesWithDescriptor(programResourcesWithDescriptors, programResources);
-    }
-    return programResourcesWithDescriptors;
-  }
-
-  private static void addProgramResourcesWithDescriptor(
-      List<ProgramResource> programResourcesWithDescriptors,
-      Collection<ProgramResource> programResources)
-      throws IOException, ResourceException {
-    for (ProgramResource programResource : programResources) {
-      if (programResource.getKind() != Kind.CF) {
-        continue;
-      }
-      try (InputStream inputStream = programResource.getByteStream()) {
-        byte[] bytes = ByteStreams.toByteArray(inputStream);
-        String descriptor = extractClassInternalType(bytes);
-        programResourcesWithDescriptors.add(
-            ProgramResource.fromBytes(
-                programResource.getOrigin(),
-                programResource.getKind(),
-                bytes,
-                ImmutableSet.of(descriptor)));
-      }
-    }
-  }
-
-  private static String extractClassInternalType(byte[] bytes) throws IOException {
-    class ClassNameExtractor extends ClassVisitor {
-      private String className;
-
-      private ClassNameExtractor() {
-        super(ASM_VERSION);
-      }
-
-      @Override
-      public void visit(
-          int version,
-          int access,
-          String name,
-          String signature,
-          String superName,
-          String[] interfaces) {
-        className = name;
-      }
-
-      String getDescriptor() {
-        return "L" + className + ";";
-      }
-    }
-
-    org.objectweb.asm.ClassReader reader = new org.objectweb.asm.ClassReader(bytes);
-    ClassNameExtractor extractor = new ClassNameExtractor();
-    reader.accept(
-        extractor,
-        org.objectweb.asm.ClassReader.SKIP_CODE
-            | org.objectweb.asm.ClassReader.SKIP_DEBUG
-            | org.objectweb.asm.ClassReader.SKIP_FRAMES);
-    return extractor.getDescriptor();
+  private static AndroidApp dumpInputToFile(AndroidApp app, InternalOptions options) {
+    return app.dump(
+        Paths.get(options.dumpInputToFile), options.getProguardConfiguration(), options.reporter);
   }
 
   private static boolean verifyMainDexOptionsCompatible(
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 24a33b9..5daaccc 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -246,7 +246,7 @@
       application.dexItemFactory.sort(namingLens);
       assert markers == null
           || markers.isEmpty()
-          || application.dexItemFactory.extractMarker() != null;
+          || application.dexItemFactory.extractMarkers() != null;
 
       SortAnnotations sortAnnotations = new SortAnnotations();
       application.classes().forEach((clazz) -> clazz.addDependencies(sortAnnotations));
diff --git a/src/main/java/com/android/tools/r8/dex/ClassesChecksum.java b/src/main/java/com/android/tools/r8/dex/ClassesChecksum.java
index 07d2176..540502b 100644
--- a/src/main/java/com/android/tools/r8/dex/ClassesChecksum.java
+++ b/src/main/java/com/android/tools/r8/dex/ClassesChecksum.java
@@ -7,11 +7,13 @@
 import com.google.gson.JsonSyntaxException;
 import it.unimi.dsi.fastutil.objects.Object2LongMap;
 import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
+import java.io.UTFDataFormatException;
 import java.util.Comparator;
 import java.util.Map;
 
 public class ClassesChecksum {
 
+  private static final String PREFIX = "~~~";
   private static final char PREFIX_CHAR0 = '~';
   private static final char PREFIX_CHAR1 = '~';
   private static final char PREFIX_CHAR2 = '~';
@@ -19,6 +21,10 @@
   private Object2LongMap<String> dictionary = null;
 
   public ClassesChecksum() {
+    assert PREFIX.length() == 3;
+    assert PREFIX.charAt(0) == PREFIX_CHAR0;
+    assert PREFIX.charAt(1) == PREFIX_CHAR1;
+    assert PREFIX.charAt(2) == PREFIX_CHAR2;
   }
 
   private void ensureMap() {
@@ -71,12 +77,28 @@
     }
   }
 
-  public static boolean preceedChecksumMarker(DexString string) {
-    return string.size < 1 ||
-        string.content[0] < PREFIX_CHAR0 ||
-        string.size < 2 ||
-        string.content[1] < PREFIX_CHAR1 ||
-        string.size < 3 ||
-        string.content[2] < PREFIX_CHAR2;
+  /**
+   * Check if this string will definitely preceded the checksum marker.
+   *
+   * <p>If true is returned the string passed definitely preceded the checksum marker. If false is
+   * returned the string passed might still preceded, so this can give false negatives.
+   *
+   * @param string String to check if definitely preceded the checksum marker.
+   * @return If the string passed definitely preceded the checksum marker
+   */
+  public static boolean definitelyPreceedChecksumMarker(DexString string) {
+    try {
+      assert PREFIX.length() == 3;
+      char[] prefix = string.decodePrefix(3);
+      return prefix.length == 0
+          || (prefix.length == 1 && prefix[0] <= PREFIX_CHAR0)
+          || (prefix.length == 2 && prefix[0] == PREFIX_CHAR0 && prefix[1] <= PREFIX_CHAR1)
+          || (prefix.length == 3
+              && prefix[0] == PREFIX_CHAR0
+              && prefix[1] == PREFIX_CHAR1
+              && prefix[2] < PREFIX_CHAR2);
+    } catch (UTFDataFormatException e) {
+      throw new RuntimeException("Bad format", e);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
index 2e87405..7a2c325 100644
--- a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
+++ b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.Pair;
 import com.google.common.collect.Sets;
 import java.util.Map;
 import java.util.Set;
@@ -35,16 +34,23 @@
 
   abstract void recordClass(DexType type);
 
+  abstract void recordClassAllAccesses(DexType type);
+
   abstract boolean isNop();
 
   abstract void generateKeepRules(InternalOptions options);
 
   public static class DesugaredLibraryCodeToKeep extends CodeToKeep {
 
+    private static class KeepStruct {
+      Set<DexField> fields = Sets.newConcurrentHashSet();
+      Set<DexMethod> methods = Sets.newConcurrentHashSet();
+      boolean all = false;
+    }
+
     private final NamingLens namingLens;
     private final Set<DexType> emulatedInterfaces = Sets.newIdentityHashSet();
-    private final Map<DexType, Pair<Set<DexField>, Set<DexMethod>>> toKeep =
-        new ConcurrentHashMap<>();
+    private final Map<DexType, KeepStruct> toKeep = new ConcurrentHashMap<>();
     private final InternalOptions options;
 
     public DesugaredLibraryCodeToKeep(NamingLens namingLens, InternalOptions options) {
@@ -62,7 +68,7 @@
     void recordMethod(DexMethod method) {
       if (shouldKeep(method.holder)) {
         keepClass(method.holder);
-        toKeep.get(method.holder).getSecond().add(method);
+        toKeep.get(method.holder).methods.add(method);
       }
       if (shouldKeep(method.proto.returnType)) {
         keepClass(method.proto.returnType);
@@ -78,7 +84,7 @@
     void recordField(DexField field) {
       if (shouldKeep(field.holder)) {
         keepClass(field.holder);
-        toKeep.get(field.holder).getFirst().add(field);
+        toKeep.get(field.holder).fields.add(field);
       }
       if (shouldKeep(field.type)) {
         keepClass(field.type);
@@ -92,10 +98,17 @@
       }
     }
 
+    @Override
+    void recordClassAllAccesses(DexType type) {
+      if (shouldKeep(type)) {
+        keepClass(type);
+        toKeep.get(type).all = true;
+      }
+    }
+
     private void keepClass(DexType type) {
       DexType baseType = type.lookupBaseType(options.itemFactory);
-      toKeep.putIfAbsent(
-          baseType, new Pair<>(Sets.newConcurrentHashSet(), Sets.newConcurrentHashSet()));
+      toKeep.putIfAbsent(baseType, new KeepStruct());
     }
 
     @Override
@@ -115,23 +128,26 @@
       StringBuilder sb = new StringBuilder();
       String cr = System.lineSeparator();
       for (DexType type : toKeep.keySet()) {
-        Set<DexField> fieldsToKeep = toKeep.get(type).getFirst();
-        Set<DexMethod> methodsToKeep = toKeep.get(type).getSecond();
+        KeepStruct keepStruct = toKeep.get(type);
         sb.append("-keep class ").append(convertType(type));
-        if (fieldsToKeep.isEmpty() && methodsToKeep.isEmpty()) {
+        if (keepStruct.all) {
+          sb.append(" { *; }").append(cr);
+          continue;
+        }
+        if (keepStruct.fields.isEmpty() && keepStruct.methods.isEmpty()) {
           sb.append(cr);
           continue;
         }
         sb.append(" {").append(cr);
-        for (DexField field : fieldsToKeep) {
+        for (DexField field : keepStruct.fields) {
           sb.append("    ")
-              .append(convertType(type))
+              .append(convertType(field.type))
               .append(" ")
               .append(field.name)
               .append(";")
               .append(cr);
         }
-        for (DexMethod method : methodsToKeep) {
+        for (DexMethod method : keepStruct.methods) {
           sb.append("    ")
               .append(convertType(method.proto.returnType))
               .append(" ")
@@ -164,6 +180,9 @@
     void recordClass(DexType type) {}
 
     @Override
+    void recordClassAllAccesses(DexType type) {}
+
+    @Override
     boolean isNop() {
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 2f249a7..5989981 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -950,7 +950,7 @@
     ClassesChecksum parsedChecksums = new ClassesChecksum();
     for (int i = stringIDs.length - 1; i >= 0; i--) {
       DexString value = indexedItems.getString(i);
-      if (ClassesChecksum.preceedChecksumMarker(value)) {
+      if (ClassesChecksum.definitelyPreceedChecksumMarker(value)) {
         break;
       }
       parsedChecksums.tryParseAndAppend(value);
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 367ea2e..e77be34 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -645,8 +645,10 @@
 
   private void writeClassData(DexProgramClass clazz) {
     assert clazz.hasMethodsOrFields();
-    desugaredLibraryCodeToKeep.recordClass(clazz.superType);
+    desugaredLibraryCodeToKeep.recordClassAllAccesses(clazz.superType);
     for (DexType itf : clazz.interfaces.values) {
+      // No need for recordClassAllAccesses here because default methods are desugared away
+      // with concrete implementations or not supported.
       desugaredLibraryCodeToKeep.recordClass(itf);
     }
     mixedSectionOffsets.setOffsetFor(clazz, dest.position());
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
index 85d3568..7ab2a56 100644
--- a/src/main/java/com/android/tools/r8/dex/Marker.java
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -21,6 +21,7 @@
   public static final String MIN_API = "min-api";
   public static final String SHA1 = "sha-1";
   public static final String COMPILATION_MODE = "compilation-mode";
+  public static final String HAS_CHECKSUMS = "has-checksums";
   public static final String PG_MAP_ID = "pg-map-id";
 
   public enum Tool {
@@ -107,6 +108,16 @@
     return this;
   }
 
+  public boolean getHasChecksums() {
+    return jsonObject.get(HAS_CHECKSUMS).getAsBoolean();
+  }
+
+  public Marker setHasChecksums(boolean hasChecksums) {
+    assert !jsonObject.has(HAS_CHECKSUMS);
+    jsonObject.addProperty(HAS_CHECKSUMS, hasChecksums);
+    return this;
+  }
+
   public String getPgMapId() {
     return jsonObject.get(PG_MAP_ID).getAsString();
   }
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 773f4ad..d569852 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -6,8 +6,9 @@
 import com.android.tools.r8.graph.ResolutionResult.ArrayCloneMethodResult;
 import com.android.tools.r8.graph.ResolutionResult.ClassNotFoundResult;
 import com.android.tools.r8.graph.ResolutionResult.IncompatibleClassResult;
-import com.android.tools.r8.graph.ResolutionResult.MultiResult;
+import com.android.tools.r8.graph.ResolutionResult.MultiResolutionResult;
 import com.android.tools.r8.graph.ResolutionResult.NoSuchMethodResult;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
@@ -190,7 +191,7 @@
   public DexEncodedMethod lookupStaticTarget(DexMethod method) {
     assert checkIfObsolete();
     ResolutionResult resolutionResult = resolveMethod(method.holder, method);
-    DexEncodedMethod target = resolutionResult.asSingleTarget();
+    DexEncodedMethod target = resolutionResult.getSingleTarget();
     return target == null || target.isStatic() ? target : null;
   }
 
@@ -215,7 +216,7 @@
     // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokespecial, use
     // the "symbolic reference" if the "symbolic reference" does not name a class.
     if (definitionFor(method.holder).isInterface()) {
-      return resolveMethodOnInterface(method.holder, method).asSingleTarget();
+      return resolveMethodOnInterface(method.holder, method).getSingleTarget();
     }
     // Then, resume on the search, but this time, starting from the holder of the caller.
     DexClass contextClass = definitionFor(invocationContext);
@@ -223,7 +224,7 @@
       return null;
     }
     resolutionResult = resolveMethod(contextClass.superType, method);
-    DexEncodedMethod target = resolutionResult.asSingleTarget();
+    DexEncodedMethod target = resolutionResult.getSingleTarget();
     return target == null || !target.isStatic() ? target : null;
   }
 
@@ -238,7 +239,7 @@
   public DexEncodedMethod lookupDirectTarget(DexMethod method) {
     assert checkIfObsolete();
     ResolutionResult resolutionResult = resolveMethod(method.holder, method);
-    DexEncodedMethod target = resolutionResult.asSingleTarget();
+    DexEncodedMethod target = resolutionResult.getSingleTarget();
     return target == null || target.isDirectMethod() ? target : null;
   }
 
@@ -252,7 +253,7 @@
     assert checkIfObsolete();
     assert type.isClassType() || type.isArrayType();
     ResolutionResult resolutionResult = resolveMethod(type, method);
-    DexEncodedMethod target = resolutionResult.asSingleTarget();
+    DexEncodedMethod target = resolutionResult.getSingleTarget();
     return target == null || target.isVirtualMethod() ? target : null;
   }
 
@@ -352,7 +353,7 @@
     // Step 2:
     DexEncodedMethod singleTarget = resolveMethodOnClassStep2(clazz, method);
     if (singleTarget != null) {
-      return singleTarget;
+      return new SingleResolutionResult(singleTarget);
     }
     // Finally Step 3:
     return resolveMethodStep3(clazz, method);
@@ -414,7 +415,7 @@
       return result;
     }
     // Return any of the non-default methods.
-    return anyTarget == null ? NoSuchMethodResult.INSTANCE : anyTarget;
+    return anyTarget == null ? NoSuchMethodResult.INSTANCE : new SingleResolutionResult(anyTarget);
   }
 
   /**
@@ -491,7 +492,7 @@
     // Step 2: Look for exact method on interface.
     DexEncodedMethod result = definition.lookupMethod(desc);
     if (result != null) {
-      return result;
+      return new SingleResolutionResult(result);
     }
     // Step 3: Look for matching method on object class.
     DexClass objectClass = definitionFor(dexItemFactory.objectType);
@@ -500,7 +501,7 @@
     }
     result = objectClass.lookupMethod(desc);
     if (result != null && result.accessFlags.isPublic() && !result.accessFlags.isAbstract()) {
-      return result;
+      return new SingleResolutionResult(result);
     }
     // Step 3: Look for maximally-specific superinterface methods or any interface definition.
     //         This is the same for classes and interfaces.
@@ -583,7 +584,7 @@
    */
   public DexEncodedMethod dispatchStaticInvoke(ResolutionResult resolvedMethod) {
     assert checkIfObsolete();
-    DexEncodedMethod target = resolvedMethod.asSingleTarget();
+    DexEncodedMethod target = resolvedMethod.getSingleTarget();
     if (target != null && target.accessFlags.isStatic()) {
       return target;
     }
@@ -597,7 +598,7 @@
    */
   public DexEncodedMethod dispatchDirectInvoke(ResolutionResult resolvedMethod) {
     assert checkIfObsolete();
-    DexEncodedMethod target = resolvedMethod.asSingleTarget();
+    DexEncodedMethod target = resolvedMethod.getSingleTarget();
     if (target != null && !target.accessFlags.isStatic()) {
       return target;
     }
@@ -669,10 +670,12 @@
 
     ResolutionResult build() {
       if (builder != null) {
-        return new MultiResult(builder.build().asList());
-      } else {
-        return singleResult;
+        return new MultiResolutionResult(builder.build().asList());
       }
+      if (singleResult != null) {
+        return new SingleResolutionResult(singleResult);
+      }
+      return null;
     }
   }
 
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 3425946..faa25c2 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -141,6 +141,7 @@
     super(previous);
     missingClasses.addAll(previous.missingClasses);
     subtypeMap.putAll(previous.subtypeMap);
+    supertypesForSynthesizedClasses.putAll(previous.supertypesForSynthesizedClasses);
     typeInfo = new ConcurrentHashMap<>(previous.typeInfo);
     assert app() instanceof DirectMappedDexApplication;
   }
@@ -746,7 +747,7 @@
     if (clazz.isProgramClass()) {
       if (lookUpwards) {
         DexEncodedMethod resolutionResult =
-            resolveMethod(type, dexItemFactory().objectMethods.finalize).asSingleTarget();
+            resolveMethod(type, dexItemFactory().objectMethods.finalize).getSingleTarget();
         if (resolutionResult != null && resolutionResult.isProgramMethod(this)) {
           mayHaveFinalizeMethodDirectlyOrIndirectlyCache.put(type, true);
           return true;
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 7eda1e6..3803b2c 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteShrinker;
 import com.android.tools.r8.ir.analysis.proto.ProtoShrinker;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
 import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -36,6 +37,7 @@
   private GraphLense graphLense;
   private final InternalOptions options;
   private RootSet rootSet;
+  private final AbstractValueFactory abstractValueFactory = new AbstractValueFactory();
 
   // Desugared library prefix rewriter.
   public final PrefixRewritingMapper rewritePrefix;
@@ -107,6 +109,10 @@
     return new AppView<>(appInfo, WholeProgramOptimizations.OFF, options, mapper);
   }
 
+  public AbstractValueFactory abstractValueFactory() {
+    return abstractValueFactory;
+  }
+
   public T appInfo() {
     return appInfo;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 33c8dde..eafa809 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
@@ -121,7 +122,10 @@
   private void writeIR(DexEncodedMethod method, PrintStream ps) {
     CfgPrinter printer = new CfgPrinter();
     new IRConverter(appInfo, options, timing, printer)
-        .processMethod(method, OptimizationFeedbackIgnore.getInstance(), null, null, null);
+        .processMethod(
+            method,
+            OptimizationFeedbackIgnore.getInstance(),
+            OneTimeMethodProcessor.getInstance());
     ps.println(printer.toString());
   }
 
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 200240c..776cbaf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -77,7 +77,7 @@
 import java.util.function.IntPredicate;
 import org.objectweb.asm.Opcodes;
 
-public class DexEncodedMethod extends KeyedDexItem<DexMethod> implements ResolutionResult {
+public class DexEncodedMethod extends KeyedDexItem<DexMethod> {
 
   public static final String CONFIGURATION_DEBUGGING_PREFIX = "Shaking error: Missing method in ";
 
@@ -1311,46 +1311,4 @@
       return result;
     }
   }
-
-  @Override
-  public boolean isValidVirtualTarget(InternalOptions options) {
-    return options.canUseNestBasedAccess()
-        ? (!accessFlags.isStatic() && !accessFlags.isConstructor())
-        : isVirtualMethod();
-  }
-
-  @Override
-  public boolean isValidVirtualTargetForDynamicDispatch() {
-    return isVirtualMethod();
-  }
-
-  @Override
-  public DexEncodedMethod asResultOfResolve() {
-    checkIfObsolete();
-    return this;
-  }
-
-  @Override
-  public DexEncodedMethod asSingleTarget() {
-    checkIfObsolete();
-    return this;
-  }
-
-  @Override
-  public boolean hasSingleTarget() {
-    checkIfObsolete();
-    return true;
-  }
-
-  @Override
-  public List<DexEncodedMethod> asListOfTargets() {
-    checkIfObsolete();
-    return Collections.singletonList(this);
-  }
-
-  @Override
-  public void forEachTarget(Consumer<DexEncodedMethod> consumer) {
-    checkIfObsolete();
-    consumer.accept(this);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index ad03301..dbeb35f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -516,6 +516,7 @@
   // If not, that library method should not be added here because it literally has side effects.
   public Map<DexMethod, Predicate<InvokeMethod>> libraryMethodsWithoutSideEffects =
       Streams.<Pair<DexMethod, Predicate<InvokeMethod>>>concat(
+              Stream.of(new Pair<>(enumMethods.constructor, alwaysTrue())),
               Stream.of(new Pair<>(objectMethods.constructor, alwaysTrue())),
               mapToPredicate(classMethods.getNames, alwaysTrue()),
               mapToPredicate(
@@ -550,7 +551,7 @@
           .build();
 
   public final Set<DexType> libraryClassesWithoutStaticInitialization =
-      ImmutableSet.of(objectType, stringBufferType, stringBuilderType);
+      ImmutableSet.of(enumType, objectType, stringBufferType, stringBuilderType);
 
   private boolean skipNameValidationForTesting = false;
 
@@ -750,6 +751,8 @@
     public final DexMethod name;
     public final DexMethod toString;
 
+    public final DexMethod constructor =
+        createMethod(enumType, createProto(voidType, stringType, intType), constructorMethodName);
     public final DexMethod finalize =
         createMethod(enumType, createProto(voidType), finalizeMethodName);
 
@@ -1196,19 +1199,6 @@
   }
 
   // Debugging support to extract marking string.
-  public synchronized Collection<Marker> extractMarker() {
-    // This is slow but it is not needed for any production code yet.
-    List<Marker> markers = new ArrayList<>();
-    for (DexString dexString : strings.keySet()) {
-      Marker result = Marker.parse(dexString);
-      if (result != null) {
-        markers.add(result);
-      }
-    }
-    return markers;
-  }
-
-  // Debugging support to extract marking string.
   // Find all markers.
   public synchronized List<Marker> extractMarkers() {
     // This is slow but it is not needed for any production code yet.
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index bac6fe3..a6591b4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -143,6 +143,51 @@
     }
   }
 
+  public char[] decodePrefix(int prefixLength) throws UTFDataFormatException {
+    int s = 0;
+    int p = 0;
+    char[] out = new char[prefixLength];
+    while (true) {
+      char a = (char) (content[p++] & 0xff);
+      if (a == 0) {
+        if (s == prefixLength) {
+          return out;
+        }
+        char[] result = new char[s];
+        System.arraycopy(out, 0, result, 0, s);
+        return result;
+      }
+      out[s] = a;
+      if (a < '\u0080') {
+        s++;
+        if (s == prefixLength) {
+          return out;
+        }
+      } else if ((a & 0xe0) == 0xc0) {
+        int b = content[p++] & 0xff;
+        if ((b & 0xC0) != 0x80) {
+          throw new UTFDataFormatException("bad second byte");
+        }
+        out[s++] = (char) (((a & 0x1F) << 6) | (b & 0x3F));
+        if (s == prefixLength) {
+          return out;
+        }
+      } else if ((a & 0xf0) == 0xe0) {
+        int b = content[p++] & 0xff;
+        int c = content[p++] & 0xff;
+        if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) {
+          throw new UTFDataFormatException("bad second or third byte");
+        }
+        out[s++] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F));
+        if (s == prefixLength) {
+          return out;
+        }
+      } else {
+        throw new UTFDataFormatException("bad byte");
+      }
+    }
+  }
+
   public int decodedHashCode() throws UTFDataFormatException {
     if (size == 0) {
       assert decode().hashCode() == 0;
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 dfd31fb..6a9a22a 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -11,30 +11,30 @@
 import java.util.Set;
 import java.util.function.Consumer;
 
-public interface ResolutionResult {
+public abstract class ResolutionResult {
 
   // TODO(b/140214802): Remove this method as its usage is questionable.
-  DexEncodedMethod asResultOfResolve();
+  public abstract DexEncodedMethod asResultOfResolve();
 
-  DexEncodedMethod asSingleTarget();
+  public abstract DexEncodedMethod getSingleTarget();
 
-  boolean hasSingleTarget();
+  public abstract boolean hasSingleTarget();
 
-  List<DexEncodedMethod> asListOfTargets();
+  public abstract List<DexEncodedMethod> asListOfTargets();
 
-  void forEachTarget(Consumer<DexEncodedMethod> consumer);
+  public abstract void forEachTarget(Consumer<DexEncodedMethod> consumer);
 
-  boolean isValidVirtualTarget(InternalOptions options);
+  public abstract boolean isValidVirtualTarget(InternalOptions options);
 
-  boolean isValidVirtualTargetForDynamicDispatch();
+  public abstract boolean isValidVirtualTargetForDynamicDispatch();
 
-  default Set<DexEncodedMethod> lookupVirtualDispatchTargets(
+  public Set<DexEncodedMethod> lookupVirtualDispatchTargets(
       boolean isInterface, AppInfoWithSubtyping appInfo) {
     return isInterface ? lookupInterfaceTargets(appInfo) : lookupVirtualTargets(appInfo);
   }
 
   // TODO(b/140204899): Leverage refined receiver type if available.
-  default Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
+  public Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
     assert isValidVirtualTarget(appInfo.app().options);
     // First add the target for receiver type method.type.
     Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
@@ -60,7 +60,7 @@
   }
 
   // TODO(b/140204899): Leverage refined receiver type if available.
-  default Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
+  public Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
     assert isValidVirtualTarget(appInfo.app().options);
     Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
     if (hasSingleTarget()) {
@@ -86,7 +86,7 @@
       //     public void bar() { }
       //   }
       //
-      DexEncodedMethod singleTarget = asSingleTarget();
+      DexEncodedMethod singleTarget = getSingleTarget();
       if (singleTarget.getCode() != null
           && appInfo.hasAnyInstantiatedLambdas(singleTarget.method.holder)) {
         result.add(singleTarget);
@@ -128,11 +128,61 @@
     return result;
   }
 
-  class MultiResult implements ResolutionResult {
+  public static class SingleResolutionResult extends ResolutionResult {
+    final DexEncodedMethod resolutionTarget;
+
+    public static boolean isValidVirtualTarget(InternalOptions options, DexEncodedMethod target) {
+      return options.canUseNestBasedAccess()
+          ? (!target.accessFlags.isStatic() && !target.accessFlags.isConstructor())
+          : target.isVirtualMethod();
+    }
+
+    public SingleResolutionResult(DexEncodedMethod resolutionTarget) {
+      assert resolutionTarget != null;
+      this.resolutionTarget = resolutionTarget;
+    }
+
+    @Override
+    public boolean isValidVirtualTarget(InternalOptions options) {
+      return isValidVirtualTarget(options, resolutionTarget);
+    }
+
+    @Override
+    public boolean isValidVirtualTargetForDynamicDispatch() {
+      return resolutionTarget.isVirtualMethod();
+    }
+
+    @Override
+    public DexEncodedMethod asResultOfResolve() {
+      return resolutionTarget;
+    }
+
+    @Override
+    public DexEncodedMethod getSingleTarget() {
+      return resolutionTarget;
+    }
+
+    @Override
+    public boolean hasSingleTarget() {
+      return true;
+    }
+
+    @Override
+    public List<DexEncodedMethod> asListOfTargets() {
+      return Collections.singletonList(resolutionTarget);
+    }
+
+    @Override
+    public void forEachTarget(Consumer<DexEncodedMethod> consumer) {
+      consumer.accept(resolutionTarget);
+    }
+  }
+
+  public static class MultiResolutionResult extends ResolutionResult {
 
     private final ImmutableList<DexEncodedMethod> methods;
 
-    MultiResult(ImmutableList<DexEncodedMethod> results) {
+    public MultiResolutionResult(ImmutableList<DexEncodedMethod> results) {
       assert results.size() > 1;
       this.methods = results;
     }
@@ -140,7 +190,7 @@
     @Override
     public boolean isValidVirtualTarget(InternalOptions options) {
       for (DexEncodedMethod method : methods) {
-        if (!method.isValidVirtualTarget(options)) {
+        if (!SingleResolutionResult.isValidVirtualTarget(options, method)) {
           return false;
         }
       }
@@ -164,7 +214,7 @@
     }
 
     @Override
-    public DexEncodedMethod asSingleTarget() {
+    public DexEncodedMethod getSingleTarget() {
       // There is no single target that is guaranteed to be called.
       return null;
     }
@@ -185,7 +235,7 @@
     }
   }
 
-  abstract class EmptyResult implements ResolutionResult {
+  public abstract static class EmptyResult extends ResolutionResult {
 
     @Override
     public DexEncodedMethod asResultOfResolve() {
@@ -193,7 +243,7 @@
     }
 
     @Override
-    public DexEncodedMethod asSingleTarget() {
+    public DexEncodedMethod getSingleTarget() {
       return null;
     }
 
@@ -223,7 +273,7 @@
     }
   }
 
-  class ArrayCloneMethodResult extends EmptyResult {
+  public static class ArrayCloneMethodResult extends EmptyResult {
 
     static final ArrayCloneMethodResult INSTANCE = new ArrayCloneMethodResult();
 
@@ -242,7 +292,7 @@
     }
   }
 
-  abstract class FailedResolutionResult extends EmptyResult {
+  public abstract static class FailedResolutionResult extends EmptyResult {
 
     @Override
     public boolean isValidVirtualTarget(InternalOptions options) {
@@ -255,7 +305,7 @@
     }
   }
 
-  class ClassNotFoundResult extends FailedResolutionResult {
+  public static class ClassNotFoundResult extends FailedResolutionResult {
     static final ClassNotFoundResult INSTANCE = new ClassNotFoundResult();
 
     private ClassNotFoundResult() {
@@ -263,7 +313,7 @@
     }
   }
 
-  class IncompatibleClassResult extends FailedResolutionResult {
+  public static class IncompatibleClassResult extends FailedResolutionResult {
     static final IncompatibleClassResult INSTANCE = new IncompatibleClassResult();
 
     private IncompatibleClassResult() {
@@ -271,7 +321,7 @@
     }
   }
 
-  class NoSuchMethodResult extends FailedResolutionResult {
+  public static class NoSuchMethodResult extends FailedResolutionResult {
     static final NoSuchMethodResult INSTANCE = new NoSuchMethodResult();
 
     private NoSuchMethodResult() {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
index e8583f6..827529a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
@@ -339,7 +339,7 @@
       if (!resolutionResult.hasSingleTarget()) {
         return false;
       }
-      DexType holder = resolutionResult.asSingleTarget().method.holder;
+      DexType holder = resolutionResult.getSingleTarget().method.holder;
       return appView.isSubtype(holder, type).isTrue();
     }
 
@@ -397,7 +397,7 @@
       if (!resolutionResult.hasSingleTarget()) {
         return false;
       }
-      DexType holder = resolutionResult.asSingleTarget().method.holder;
+      DexType holder = resolutionResult.getSingleTarget().method.holder;
       return appView.isSubtype(holder, type).isTrue();
     }
 
@@ -433,7 +433,7 @@
       if (!resolutionResult.hasSingleTarget()) {
         return false;
       }
-      DexType holder = resolutionResult.asSingleTarget().method.holder;
+      DexType holder = resolutionResult.getSingleTarget().method.holder;
       return appView.isSubtype(holder, type).isTrue();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
index d654c87..96dada1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -29,6 +29,8 @@
   private final IRCode code;
   private final DexType context;
 
+  private final Set<Value> knownNotToDependOnEnvironment = Sets.newIdentityHashSet();
+
   public ValueMayDependOnEnvironmentAnalysis(AppView<?> appView, IRCode code) {
     this.appView = appView;
     this.code = code;
@@ -36,20 +38,57 @@
   }
 
   public boolean valueMayDependOnEnvironment(Value value) {
+    return valueMayDependOnEnvironment(value, Sets.newIdentityHashSet());
+  }
+
+  private boolean valueMayDependOnEnvironment(
+      Value value, Set<Value> assumedNotToDependOnEnvironment) {
     Value root = value.getAliasedValue();
+    if (assumedNotToDependOnEnvironment.contains(root)) {
+      return false;
+    }
+    if (knownNotToDependOnEnvironment.contains(root)) {
+      return false;
+    }
     if (root.isConstant()) {
       return false;
     }
-    if (isConstantArrayThroughoutMethod(root)) {
+    if (isConstantArrayThroughoutMethod(root, assumedNotToDependOnEnvironment)) {
       return false;
     }
-    if (isNewInstanceWithoutEnvironmentDependentFields(root)) {
+    if (isNewInstanceWithoutEnvironmentDependentFields(root, assumedNotToDependOnEnvironment)) {
       return false;
     }
     return true;
   }
 
+  private boolean valueMayNotDependOnEnvironmentAssumingArrayDoesNotDependOnEnvironment(
+      Value value, Value array, Set<Value> assumedNotToDependOnEnvironment) {
+    assert !value.hasAliasedValue();
+    assert !array.hasAliasedValue();
+
+    if (assumedNotToDependOnEnvironment.add(array)) {
+      boolean valueMayDependOnEnvironment =
+          valueMayDependOnEnvironment(value, assumedNotToDependOnEnvironment);
+      boolean changed = assumedNotToDependOnEnvironment.remove(array);
+      assert changed;
+      return !valueMayDependOnEnvironment;
+    }
+    return !valueMayDependOnEnvironment(value, assumedNotToDependOnEnvironment);
+  }
+
+  /**
+   * Used to identify if an array is "constant" in the sense that none of the values written into
+   * the array may depend on the environment.
+   *
+   * <p>Examples include {@code new int[] {1,2,3}} and {@code new Object[]{new Object()}}.
+   */
   public boolean isConstantArrayThroughoutMethod(Value value) {
+    return isConstantArrayThroughoutMethod(value, Sets.newIdentityHashSet());
+  }
+
+  private boolean isConstantArrayThroughoutMethod(
+      Value value, Set<Value> assumedNotToDependOnEnvironment) {
     Value root = value.getAliasedValue();
     if (root.isPhi()) {
       // Would need to track the aliases, just give up.
@@ -96,6 +135,7 @@
 
     // Allow array-put and new-array-filled-data instructions that immediately follow the array
     // creation.
+    Set<Value> arrayValues = Sets.newIdentityHashSet();
     Set<Instruction> consumedInstructions = Sets.newIdentityHashSet();
 
     for (Instruction instruction : definition.getBlock().instructionsAfter(definition)) {
@@ -118,10 +158,19 @@
           return false;
         }
 
-        if (!arrayPut.value().isConstant()) {
+        // Check if the value being written into the array may depend on the environment.
+        //
+        // When analyzing if the value may depend on the environment, we assume that the current
+        // array does not depend on the environment. Otherwise, we would classify the value as
+        // possibly depending on the environment since it could escape via the array and then
+        // be mutated indirectly.
+        Value rhs = arrayPut.value().getAliasedValue();
+        if (!valueMayNotDependOnEnvironmentAssumingArrayDoesNotDependOnEnvironment(
+            rhs, root, assumedNotToDependOnEnvironment)) {
           return false;
         }
 
+        arrayValues.add(rhs);
         consumedInstructions.add(arrayPut);
         continue;
       }
@@ -149,10 +198,21 @@
     // Currently, we only allow the array to flow into static-put instructions that are not
     // followed by an instruction that may have side effects. Instructions that do not have any
     // side effects are ignored because they cannot mutate the array.
-    return !valueMayBeMutatedBeforeMethodExit(root, consumedInstructions);
+    if (valueMayBeMutatedBeforeMethodExit(
+        root, assumedNotToDependOnEnvironment, consumedInstructions)) {
+      return false;
+    }
+
+    if (assumedNotToDependOnEnvironment.isEmpty()) {
+      knownNotToDependOnEnvironment.add(root);
+      knownNotToDependOnEnvironment.addAll(arrayValues);
+    }
+
+    return true;
   }
 
-  private boolean isNewInstanceWithoutEnvironmentDependentFields(Value value) {
+  private boolean isNewInstanceWithoutEnvironmentDependentFields(
+      Value value, Set<Value> assumedNotToDependOnEnvironment) {
     assert !value.hasAliasedValue();
 
     if (value.isPhi() || !value.definition.isNewInstance()) {
@@ -203,16 +263,26 @@
     // Check that none of the arguments to the constructor depend on the environment.
     for (int i = 1; i < constructorInvoke.arguments().size(); i++) {
       Value argument = constructorInvoke.arguments().get(i);
-      if (valueMayDependOnEnvironment(argument)) {
+      if (valueMayDependOnEnvironment(argument, assumedNotToDependOnEnvironment)) {
         return false;
       }
     }
 
     // Finally, check that the object does not escape.
-    return !valueMayBeMutatedBeforeMethodExit(value, ImmutableSet.of(constructorInvoke));
+    if (valueMayBeMutatedBeforeMethodExit(
+        value, assumedNotToDependOnEnvironment, ImmutableSet.of(constructorInvoke))) {
+      return false;
+    }
+
+    if (assumedNotToDependOnEnvironment.isEmpty()) {
+      knownNotToDependOnEnvironment.add(value);
+    }
+
+    return true;
   }
 
-  private boolean valueMayBeMutatedBeforeMethodExit(Value value, Set<Instruction> whitelist) {
+  private boolean valueMayBeMutatedBeforeMethodExit(
+      Value value, Set<Value> assumedNotToDependOnEnvironment, Set<Instruction> whitelist) {
     assert !value.hasAliasedValue();
 
     if (value.numberOfPhiUsers() > 0) {
@@ -226,6 +296,14 @@
         continue;
       }
 
+      if (user.isArrayPut()) {
+        ArrayPut arrayPut = user.asArrayPut();
+        if (value == arrayPut.value()
+            && !valueMayDependOnEnvironment(arrayPut.array(), assumedNotToDependOnEnvironment)) {
+          continue;
+        }
+      }
+
       if (user.isStaticPut()) {
         StaticPut staticPut = user.asStaticPut();
         if (visited.contains(staticPut)) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
index 79081ee..72ac893 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
@@ -88,7 +88,7 @@
               if (value.definition != evaluatedConst) {
                 if (value.isPhi()) {
                   // D8 relies on dead code removal to get rid of the dead phi itself.
-                  if (value.numberOfAllUsers() != 0) {
+                  if (value.hasAnyUsers()) {
                     BasicBlock block = value.asPhi().getBlock();
                     blockToAnalyze.add(block);
                     // Create a new constant, because it can be an existing constant that flow
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 1ef58fb..5f85ba6 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
@@ -4,20 +4,29 @@
 
 package com.android.tools.r8.ir.analysis.fieldvalueanalysis;
 
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
+import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleEnumValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.DominatorTree;
 import com.android.tools.r8.ir.code.DominatorTree.Assumption;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
@@ -33,6 +42,7 @@
 public class FieldValueAnalysis {
 
   private final AppView<AppInfoWithLiveness> appView;
+  private final DexProgramClass clazz;
   private final IRCode code;
   private final OptimizationFeedback feedback;
   private final DexEncodedMethod method;
@@ -45,9 +55,11 @@
       OptimizationFeedback feedback,
       DexEncodedMethod method) {
     this.appView = appView;
+    this.clazz = appView.definitionFor(method.method.holder).asProgramClass();
     this.code = code;
     this.feedback = feedback;
     this.method = method;
+    assert this.clazz != null;
   }
 
   public static void run(
@@ -244,16 +256,92 @@
   }
 
   private void updateFieldOptimizationInfo(DexEncodedField field, Value value) {
+    // Abstract value.
+    Value root = value.getAliasedValue();
+    AbstractValue abstractValue = computeAbstractValue(root);
+    if (!abstractValue.isUnknown()) {
+      feedback.recordFieldHasAbstractValue(field, abstractValue);
+    }
+
+    // Dynamic upper bound type.
     TypeLatticeElement fieldType =
         TypeLatticeElement.fromDexType(field.field.type, Nullability.maybeNull(), appView);
     TypeLatticeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
     if (dynamicUpperBoundType.strictlyLessThan(fieldType, appView)) {
       feedback.markFieldHasDynamicUpperBoundType(field, dynamicUpperBoundType);
     }
+
+    // Dynamic lower bound type.
     ClassTypeLatticeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
     if (dynamicLowerBoundType != null) {
       assert dynamicLowerBoundType.lessThanOrEqual(dynamicUpperBoundType, appView);
       feedback.markFieldHasDynamicLowerBoundType(field, dynamicLowerBoundType);
     }
   }
+
+  private AbstractValue computeAbstractValue(Value value) {
+    assert !value.hasAliasedValue();
+    if (clazz.isEnum()) {
+      SingleEnumValue singleEnumValue = getSingleEnumValue(value);
+      if (singleEnumValue != null) {
+        return singleEnumValue;
+      }
+    }
+    return UnknownValue.getInstance();
+  }
+
+  /**
+   * If {@param value} is defined by a new-instance instruction that instantiates the enclosing enum
+   * class, and the value is assigned into exactly one static enum field on the enclosing enum
+   * class, then returns a {@link SingleEnumValue} instance. Otherwise, returns {@code null}.
+   */
+  private SingleEnumValue getSingleEnumValue(Value value) {
+    assert clazz.isEnum();
+    assert !value.hasAliasedValue();
+    if (value.isPhi() || !value.definition.isNewInstance()) {
+      return null;
+    }
+
+    NewInstance newInstance = value.definition.asNewInstance();
+    if (newInstance.clazz != clazz.type) {
+      return null;
+    }
+
+    if (value.hasDebugUsers() || value.hasPhiUsers()) {
+      return null;
+    }
+
+    DexEncodedField enumField = null;
+    for (Instruction user : value.uniqueUsers()) {
+      switch (user.opcode()) {
+        case INVOKE_DIRECT:
+          // Check that this is the corresponding constructor call.
+          InvokeDirect invoke = user.asInvokeDirect();
+          if (!appView.dexItemFactory().isConstructor(invoke.getInvokedMethod())
+              || invoke.getReceiver() != value) {
+            return null;
+          }
+          break;
+
+        case STATIC_PUT:
+          DexEncodedField field = clazz.lookupStaticField(user.asStaticPut().getField());
+          if (field != null && field.accessFlags.isEnum()) {
+            if (enumField != null) {
+              return null;
+            }
+            enumField = field;
+          }
+          break;
+
+        default:
+          return null;
+      }
+    }
+
+    if (enumField == null) {
+      return null;
+    }
+
+    return appView.abstractValueFactory().createSingleEnumValue(enumField.field);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
index 614c521..624cf90 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.analysis.proto;
 
-import static com.google.common.base.Predicates.alwaysFalse;
 import static com.google.common.base.Predicates.not;
 
 import com.android.tools.r8.graph.AppView;
@@ -17,9 +16,8 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
-import com.android.tools.r8.ir.conversion.CallSiteInformation;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.optimize.Outliner;
+import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -96,9 +94,7 @@
             converter.processMethod(
                 method,
                 OptimizationFeedbackIgnore.getInstance(),
-                alwaysFalse(),
-                CallSiteInformation.empty(),
-                Outliner::noProcessing));
+                OneTimeMethodProcessor.getInstance()));
   }
 
   private void forEachFindLiteExtensionByNumberMethod(Consumer<DexEncodedMethod> consumer) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index 62a9bb5..f2b354f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.ir.analysis.proto.ProtoUtils.getInfoValueFromMessageInfoConstructionInvoke;
 import static com.android.tools.r8.ir.analysis.proto.ProtoUtils.getObjectsValueFromMessageInfoConstructionInvoke;
 import static com.android.tools.r8.ir.analysis.proto.ProtoUtils.setObjectsValueForMessageInfoConstructionInvoke;
-import static com.google.common.base.Predicates.alwaysFalse;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -28,9 +27,8 @@
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.CallSiteInformation;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.optimize.Outliner;
+import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.List;
@@ -76,9 +74,7 @@
             converter.processMethod(
                 method,
                 OptimizationFeedbackIgnore.getInstance(),
-                alwaysFalse(),
-                CallSiteInformation.empty(),
-                Outliner::noProcessing));
+                OneTimeMethodProcessor.getInstance()));
   }
 
   private void forEachDynamicMethod(Consumer<DexEncodedMethod> consumer) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
new file mode 100644
index 0000000..e7a1e8b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+public abstract class AbstractValue {
+
+  /**
+   * Returns true if this abstract value represents a single concrete value (i.e., the
+   * concretization of this abstract value has size 1).
+   */
+  public boolean isSingleValue() {
+    return false;
+  }
+
+  public boolean isSingleEnumValue() {
+    return false;
+  }
+
+  public SingleEnumValue asSingleEnumValue() {
+    return null;
+  }
+
+  public boolean isUnknown() {
+    return false;
+  }
+
+  @Override
+  public abstract boolean equals(Object o);
+
+  @Override
+  public abstract int hashCode();
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
new file mode 100644
index 0000000..c58addb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+import com.android.tools.r8.graph.DexField;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class AbstractValueFactory {
+
+  private ConcurrentHashMap<DexField, SingleEnumValue> singleEnumValues = new ConcurrentHashMap<>();
+
+  public SingleEnumValue createSingleEnumValue(DexField field) {
+    return singleEnumValues.computeIfAbsent(field, SingleEnumValue::new);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java
new file mode 100644
index 0000000..6a3d96b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+import com.android.tools.r8.graph.DexField;
+
+public class SingleEnumValue extends AbstractValue {
+
+  private final DexField field;
+
+  /** Intentionally package private, use {@link AbstractValueFactory} instead. */
+  SingleEnumValue(DexField field) {
+    this.field = field;
+  }
+
+  @Override
+  public boolean isSingleValue() {
+    return true;
+  }
+
+  @Override
+  public boolean isSingleEnumValue() {
+    return true;
+  }
+
+  @Override
+  public SingleEnumValue asSingleEnumValue() {
+    return this;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    return this == o;
+  }
+
+  @Override
+  public int hashCode() {
+    return System.identityHashCode(this);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java
new file mode 100644
index 0000000..bbae7bd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+public class UnknownValue extends AbstractValue {
+
+  private static final UnknownValue INSTANCE = new UnknownValue();
+
+  private UnknownValue() {}
+
+  public static UnknownValue getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public boolean isUnknown() {
+    return true;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    return this == o;
+  }
+
+  @Override
+  public int hashCode() {
+    return System.identityHashCode(this);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 72e4c9d..0e0c975 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -191,7 +191,7 @@
         DexEncodedMethod resolutionResult =
             appInfo
                 .resolveMethod(clazz.type, dexItemFactory.objectMethods.finalize)
-                .asSingleTarget();
+                .getSingleTarget();
         return resolutionResult != null && resolutionResult.isProgramMethod(appInfo);
       }
 
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 955182b..bd5ea0a 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
@@ -878,13 +878,13 @@
   private boolean verifyNoValueWithOnlyAssumeInstructionAsUsers() {
     Predicate<Value> verifyValue =
         v -> {
-          assert v.numberOfUsers() == 0
-              || v.uniqueUsers().stream().anyMatch(i -> !i.isAssume())
-              || (!v.isPhi() && v.definition.isArgument())
-              || v.numberOfDebugUsers() == 0
-              || v.debugUsers().stream().anyMatch(i -> !i.isAssume())
-              || v.numberOfPhiUsers() > 0
-                  : StringUtils.join(v.uniqueUsers(), System.lineSeparator());
+          assert !v.hasUsers()
+                  || v.uniqueUsers().stream().anyMatch(i -> !i.isAssume())
+                  || (!v.isPhi() && v.definition.isArgument())
+                  || !v.hasDebugUsers()
+                  || v.debugUsers().stream().anyMatch(i -> !i.isAssume())
+                  || v.numberOfPhiUsers() > 0
+              : StringUtils.join(v.uniqueUsers(), System.lineSeparator());
           return true;
         };
     return verifySSATypeLattice(wrapSSAVerifierWithStackValueHandling(verifyValue));
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 6c22405..c6f919d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -109,7 +109,7 @@
       ResolutionResult refinedResolution =
           appView.appInfo().resolveMethod(refinedReceiverType, method);
       if (refinedResolution.hasSingleTarget()) {
-        DexEncodedMethod refinedTarget = refinedResolution.asSingleTarget();
+        DexEncodedMethod refinedTarget = refinedResolution.getSingleTarget();
         Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
         for (DexEncodedMethod target : targets) {
           if (target == refinedTarget
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 7a38882..d9d65e5 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
@@ -176,7 +176,7 @@
     ResolutionResult finalizeResolutionResult =
         appView.appInfo().resolveMethod(clazz, dexItemFactory.objectMethods.finalize);
     if (finalizeResolutionResult.hasSingleTarget()) {
-      DexMethod finalizeMethod = finalizeResolutionResult.asSingleTarget().method;
+      DexMethod finalizeMethod = finalizeResolutionResult.getSingleTarget().method;
       if (finalizeMethod != dexItemFactory.enumMethods.finalize
           && finalizeMethod != dexItemFactory.objectMethods.finalize) {
         return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index ae145b3..47eb9d4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -290,7 +290,7 @@
 
   public void removeDeadPhi() {
     // First, make sure it is indeed dead, i.e., no non-phi users.
-    assert numberOfUsers() == 0;
+    assert !hasUsers();
     // Then, manually clean up this from all of the operands.
     for (Value operand : getOperands()) {
       operand.removePhiUser(this);
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 74add42..c7aeff1 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
@@ -10,12 +10,13 @@
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
@@ -466,6 +467,22 @@
     return debugData == null ? null : Collections.unmodifiableSet(debugData.users.keySet());
   }
 
+  public boolean hasAnyUsers() {
+    return hasUsers() || hasPhiUsers() || hasDebugUsers();
+  }
+
+  public boolean hasDebugUsers() {
+    return debugData != null && !debugData.users.isEmpty();
+  }
+
+  public boolean hasPhiUsers() {
+    return !phiUsers.isEmpty();
+  }
+
+  public boolean hasUsers() {
+    return !users.isEmpty();
+  }
+
   public int numberOfUsers() {
     int size = users.size();
     if (size <= 1) {
@@ -830,33 +847,25 @@
     return definition.isOutConstant() && !hasLocalInfo();
   }
 
-  public DexEncodedField getEnumField(AppView<?> appView) {
+  public AbstractValue getAbstractValue(AppView<?> appView) {
     if (!appView.enableWholeProgramOptimizations()) {
-      return null;
+      return UnknownValue.getInstance();
     }
 
     Value root = getAliasedValue();
-    if (root.isPhi() || !root.definition.isStaticGet()) {
-      return null;
+    if (root.isPhi()) {
+      return UnknownValue.getInstance();
     }
 
-    StaticGet staticGet = root.definition.asStaticGet();
-    DexField field = staticGet.getField();
-    if (field.type != field.holder) {
-      return null;
+    if (root.definition.isFieldGet()) {
+      FieldInstruction fieldGet = root.definition.asFieldInstruction();
+      DexEncodedField field = appView.appInfo().resolveField(fieldGet.getField());
+      if (field != null) {
+        return field.getOptimizationInfo().getAbstractValue();
+      }
     }
 
-    DexEncodedField encodedField = appView.definitionFor(field);
-    if (encodedField == null) {
-      return null;
-    }
-
-    DexClass clazz = appView.definitionFor(encodedField.field.holder);
-    if (clazz == null || !clazz.isEnum()) {
-      return null;
-    }
-
-    return encodedField;
+    return UnknownValue.getInstance();
   }
 
   public boolean isPhi() {
@@ -1007,7 +1016,7 @@
 
     // If the value has debug users we cannot eliminate it since it represents a value in a local
     // variable that should be visible in the debugger.
-    if (numberOfDebugUsers() != 0) {
+    if (hasDebugUsers()) {
       return false;
     }
     // This is a candidate for a dead value. Guard against looping by adding it to the set of
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index b9f72f7..c4f9072 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -9,11 +9,8 @@
 import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator.CycleEliminationResult;
 import com.android.tools.r8.ir.conversion.CallSiteInformation.CallGraphBasedCallSiteInformation;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Timing;
 import java.util.Set;
 import java.util.TreeSet;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
 
 /**
  * Call graph representation.
@@ -169,13 +166,6 @@
     return new CallGraphBuilder(appView);
   }
 
-  static MethodProcessor createMethodProcessor(
-      AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
-      throws ExecutionException {
-    CallGraph callGraph = CallGraph.builder(appView).build(executorService, timing);
-    return new MethodProcessor(appView, callGraph);
-  }
-
   CallSiteInformation createCallSiteInformation(AppView<AppInfoWithLiveness> appView) {
     // Don't leverage single/dual call site information when we are not tree shaking.
     return appView.options().isShrinking()
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CodeOptimization.java b/src/main/java/com/android/tools/r8/ir/conversion/CodeOptimization.java
new file mode 100644
index 0000000..5d73cd5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CodeOptimization.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+
+/**
+ * An abstraction of {@link IRCode}-level optimization.
+ */
+public interface CodeOptimization {
+
+  // TODO(b/140766440): if some other code optimizations require more info to pass, i.e., requires
+  //  a signature change, we may need to turn this interface into an abstract base, instead of
+  //  rewriting every affected optimization.
+  // Note that a code optimization can be a collection of other code optimizations.
+  // In that way, IRConverter will serve as the default full processing of all optimizations.
+  void optimize(
+      AppView<?> appView,
+      IRCode code,
+      OptimizationFeedback feedback,
+      MethodProcessor methodProcessor);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
index 46a0cc4..ceaf87e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 
 public interface FieldOptimizationFeedback {
 
@@ -19,4 +20,6 @@
   void markFieldHasDynamicUpperBoundType(DexEncodedField field, TypeLatticeElement type);
 
   void markFieldBitsRead(DexEncodedField field, int bitsRead);
+
+  void recordFieldHasAbstractValue(DexEncodedField field, AbstractValue abstractValue);
 }
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 80b7e61..358a7fe 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
@@ -48,6 +48,7 @@
 import com.android.tools.r8.ir.desugar.StringConcatRewriter;
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.ir.optimize.AliasIntroducer;
+import com.android.tools.r8.ir.optimize.Assumer;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.ConstantCanonicalizer;
@@ -93,9 +94,9 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.base.Predicates;
 import com.google.common.base.Suppliers;
 import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
@@ -109,9 +110,8 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
-import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 import java.util.function.Function;
-import java.util.function.Predicate;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
@@ -156,13 +156,12 @@
   private final ServiceLoaderRewriter serviceLoaderRewriter;
 
   // Assumers that will insert Assume instructions.
-  private final AliasIntroducer aliasIntroducer;
+  public final Collection<Assumer> assumers = new ArrayList<>();
   private final DynamicTypeOptimization dynamicTypeOptimization;
-  private final NonNullTracker nonNullTracker;
 
   final DeadCodeRemover deadCodeRemover;
 
-  final MethodOptimizationInfoCollector methodOptimizationInfoCollector;
+  private final MethodOptimizationInfoCollector methodOptimizationInfoCollector;
 
   private final OptimizationFeedbackDelayed delayedOptimizationFeedback =
       new OptimizationFeedbackDelayed();
@@ -210,7 +209,7 @@
       // InterfaceMethodRewriter is needed for emulated interfaces.
       // LambdaRewriter is needed because if it is missing there are invoke custom on
       // default/static interface methods, and this is not supported by the compiler.
-      // The rest is nulled out. In addition the rewriting logic fails without lambda rewritting.
+      // The rest is nulled out. In addition the rewriting logic fails without lambda rewriting.
       this.backportedMethodRewriter = new BackportedMethodRewriter(appView, this);
       this.interfaceMethodRewriter =
           options.desugaredLibraryConfiguration.getEmulateLibraryInterface().isEmpty()
@@ -220,8 +219,6 @@
       this.twrCloseResourceRewriter = null;
       this.lambdaMerger = null;
       this.covariantReturnTypeAnnotationTransformer = null;
-      this.aliasIntroducer = null;
-      this.nonNullTracker = null;
       this.dynamicTypeOptimization = null;
       this.classInliner = null;
       this.classStaticizer = null;
@@ -259,9 +256,12 @@
         options.processCovariantReturnTypeAnnotations
             ? new CovariantReturnTypeAnnotationTransformer(this, appView.dexItemFactory())
             : null;
-    this.aliasIntroducer =
-        options.testing.forceAssumeNoneInsertion ? new AliasIntroducer(appView) : null;
-    this.nonNullTracker = options.enableNonNullTracking ? new NonNullTracker(appView) : null;
+    if (options.testing.forceAssumeNoneInsertion) {
+      assumers.add(new AliasIntroducer(appView));
+    }
+    if (options.enableNonNullTracking) {
+      assumers.add(new NonNullTracker(appView));
+    }
     this.desugaredLibraryAPIConverter =
         appView.rewritePrefix.isRewriting() ? new DesugaredLibraryAPIConverter(appView) : null;
     if (appView.enableWholeProgramOptimizations()) {
@@ -278,6 +278,9 @@
           options.enableDynamicTypeOptimization
               ? new DynamicTypeOptimization(appViewWithLiveness)
               : null;
+      if (dynamicTypeOptimization != null) {
+        assumers.add(dynamicTypeOptimization);
+      }
       this.fieldBitAccessAnalysis =
           options.enableFieldBitAccessAnalysis
               ? new FieldBitAccessAnalysis(appViewWithLiveness)
@@ -291,7 +294,7 @@
       this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness, lambdaRewriter);
       this.inliner =
           new Inliner(appViewWithLiveness, mainDexClasses, lambdaMerger, lensCodeRewriter);
-      this.outliner = new Outliner(appViewWithLiveness, this);
+      this.outliner = new Outliner(appViewWithLiveness);
       this.memberValuePropagation =
           options.enableValuePropagation ? new MemberValuePropagation(appViewWithLiveness) : null;
       this.methodOptimizationInfoCollector =
@@ -590,8 +593,10 @@
         if (options.isGeneratingClassFiles()
             || !(options.passthroughDexCode && method.getCode().isDexCode())) {
           // We do not process in call graph order, so anything could be a leaf.
-          rewriteCode(method, simpleOptimizationFeedback, x -> true, CallSiteInformation.empty(),
-              Outliner::noProcessing);
+          rewriteCode(
+              method,
+              simpleOptimizationFeedback,
+              OneTimeMethodProcessor.getInstance(ImmutableList.of(method)));
         } else {
           assert method.getCode().isDexCode();
         }
@@ -630,29 +635,26 @@
     //    reprocessing IR code of methods, e.g., outlining, double-inlining, class staticizer, etc.
     //    - a side effect is candidates for those optimizations are identified.
     // 2) Revisit DexEncodedMethods for the collected candidates.
-    // TODO(b/127694949): unified framework to reprocess methods only once.
 
     printPhase("Primary optimization pass");
 
     // Process the application identifying outlining candidates.
     GraphLense graphLenseForIR = appView.graphLense();
     OptimizationFeedbackDelayed feedback = delayedOptimizationFeedback;
+    PostMethodProcessor.Builder postMethodProcessorBuilder =
+        new PostMethodProcessor.Builder(getOptimizationsForPostIRProcessing());
     {
       timing.begin("Build call graph");
-      MethodProcessor methodProcessor =
-          CallGraph.createMethodProcessor(appView.withLiveness(), executorService, timing);
+      PrimaryMethodProcessor primaryMethodProcessor =
+          PrimaryMethodProcessor.create(
+              appView.withLiveness(), postMethodProcessorBuilder, executorService, timing);
       timing.end();
       timing.begin("IR conversion phase 1");
-      BiConsumer<IRCode, DexEncodedMethod> outlineHandler =
-          outliner == null ? Outliner::noProcessing : outliner.identifyCandidateMethods();
-      methodProcessor.forEachMethod(
-          (method, isProcessedConcurrently) ->
-              processMethod(
-                  method,
-                  feedback,
-                  isProcessedConcurrently,
-                  methodProcessor.getCallSiteInformation(),
-                  outlineHandler),
+      if (outliner != null) {
+        outliner.createOutlineMethodIdentifierGenerator();
+      }
+      primaryMethodProcessor.forEachMethod(
+          method -> processMethod(method, feedback, primaryMethodProcessor),
           this::waveStart,
           this::waveDone,
           executorService);
@@ -666,27 +668,31 @@
       libraryMethodOverrideAnalysis.finish();
     }
 
-    // Second pass for methods whose collected call site information become more precise.
+    // 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.
+    printPhase("Post optimization pass");
     if (appView.callSiteOptimizationInfoPropagator() != null) {
-      printPhase("2nd round of method processing after inter-procedural analysis.");
-      timing.begin("IR conversion phase 2");
-      appView.callSiteOptimizationInfoPropagator().revisitMethods(this, executorService);
-      feedback.updateVisibleOptimizationInfo();
-      timing.end();
+      postMethodProcessorBuilder.put(appView.callSiteOptimizationInfoPropagator());
     }
-
-    // Second inlining pass for dealing with double inline callers.
     if (inliner != null) {
-      printPhase("Double caller inlining");
-      assert graphLenseForIR == appView.graphLense();
-      inliner.processDoubleInlineCallers(this, executorService);
+      postMethodProcessorBuilder.put(inliner);
+    }
+    timing.begin("IR conversion phase 2");
+    assert graphLenseForIR == appView.graphLense();
+    PostMethodProcessor postMethodProcessor =
+        postMethodProcessorBuilder.build(appView.withLiveness(), executorService, timing);
+    if (postMethodProcessor != null) {
+      postMethodProcessor.forEachWave(feedback, executorService);
       feedback.updateVisibleOptimizationInfo();
       assert graphLenseForIR == appView.graphLense();
     }
+    timing.end();
 
     // TODO(b/112831361): Implement support for staticizeClasses in CF backend.
     if (!options.isGeneratingClassFiles()) {
       printPhase("Class staticizer post processing");
+      // TODO(b/127694949): Adapt to PostOptimization.
       staticizeClasses(feedback, executorService);
       feedback.updateVisibleOptimizationInfo();
     }
@@ -707,6 +713,7 @@
     handleSynthesizedClassMapping(builder);
 
     printPhase("Lambda merging finalization");
+    // TODO(b/127694949): Adapt to PostOptimization.
     finalizeLambdaMerging(application, feedback, builder, executorService);
 
     printPhase("Desugared library API Conversion finalization");
@@ -721,24 +728,25 @@
     // Update optimization info for all synthesized methods at once.
     feedback.updateVisibleOptimizationInfo();
 
+    // TODO(b/127694949): Adapt to PostOptimization.
     if (outliner != null) {
       printPhase("Outlining");
       timing.begin("IR conversion phase 3");
       if (outliner.selectMethodsForOutlining()) {
         forEachSelectedOutliningMethod(
-            (code, method) -> {
+            code -> {
               printMethod(code, "IR before outlining (SSA)", null);
-              outliner.identifyOutlineSites(code, method);
+              outliner.identifyOutlineSites(code);
             },
             executorService);
         DexProgramClass outlineClass = outliner.buildOutlinerClass(computeOutlineClassType());
         appView.appInfo().addSynthesizedClass(outlineClass);
         optimizeSynthesizedClass(outlineClass, executorService);
         forEachSelectedOutliningMethod(
-            (code, method) -> {
-              outliner.applyOutliningCandidate(code, method);
+            code -> {
+              outliner.applyOutliningCandidate(code);
               printMethod(code, "IR after outlining (SSA)", null);
-              finalizeIR(method, code, OptimizationFeedbackIgnore.getInstance());
+              finalizeIR(code.method, code, OptimizationFeedbackIgnore.getInstance());
             },
             executorService);
         feedback.updateVisibleOptimizationInfo();
@@ -827,7 +835,7 @@
   }
 
   private void forEachSelectedOutliningMethod(
-      BiConsumer<IRCode, DexEncodedMethod> consumer, ExecutorService executorService)
+      Consumer<IRCode> consumer, ExecutorService executorService)
       throws ExecutionException {
     assert !options.skipIR;
     Set<DexEncodedMethod> methods = outliner.getMethodsSelectedForOutlining();
@@ -844,7 +852,7 @@
           codeRewriter.rewriteMoveResult(code);
           deadCodeRemover.run(code);
           CodeRewriter.removeAssumeInstructions(appView, code);
-          consumer.accept(code, method);
+          consumer.accept(code);
         },
         executorService);
   }
@@ -962,40 +970,41 @@
       processMethod(
           method,
           delayedOptimizationFeedback,
-          Predicates.alwaysFalse(),
-          CallSiteInformation.empty(),
-          Outliner::noProcessing);
+          OneTimeMethodProcessor.getInstance());
     }
   }
 
   public void processMethodsConcurrently(
       Collection<DexEncodedMethod> methods, ExecutorService executorService)
       throws ExecutionException {
-    ThreadUtils.processItems(
-        methods,
-        method -> processMethod(
-            method,
-            delayedOptimizationFeedback,
-            methods::contains,
-            CallSiteInformation.empty(),
-            Outliner::noProcessing),
-        executorService);
+    OneTimeMethodProcessor processor = OneTimeMethodProcessor.getInstance(methods);
+    processor.forEachWave(
+        method -> processMethod(method, delayedOptimizationFeedback, processor), executorService);
   }
 
   private String logCode(InternalOptions options, DexEncodedMethod method) {
     return options.useSmaliSyntax ? method.toSmaliString(null) : method.codeToString();
   }
 
+  List<CodeOptimization> getOptimizationsForPrimaryIRProcessing() {
+    // TODO(b/140766440): Remove unnecessary steps once all sub steps are converted.
+    return ImmutableList.of(this::optimize);
+  }
+
+  List<CodeOptimization> getOptimizationsForPostIRProcessing() {
+    // TODO(b/140766440): Remove unnecessary steps once all sub steps are converted.
+    return ImmutableList.of(this::optimize);
+  }
+
+  // TODO(b/140766440): Make this receive a list of CodeOptimizations to conduct.
   public void processMethod(
       DexEncodedMethod method,
       OptimizationFeedback feedback,
-      Predicate<DexEncodedMethod> isProcessedConcurrently,
-      CallSiteInformation callSiteInformation,
-      BiConsumer<IRCode, DexEncodedMethod> outlineHandler) {
+      MethodProcessor methodProcessor) {
     Code code = method.getCode();
     boolean matchesMethodFilter = options.methodMatchesFilter(method);
     if (code != null && matchesMethodFilter) {
-      rewriteCode(method, feedback, isProcessedConcurrently, callSiteInformation, outlineHandler);
+      rewriteCode(method, feedback, methodProcessor);
     } else {
       // Mark abstract methods as processed as well.
       method.markProcessed(ConstraintWithTarget.NEVER);
@@ -1011,15 +1020,10 @@
   }
 
   private void rewriteCode(
-      DexEncodedMethod method,
-      OptimizationFeedback feedback,
-      Predicate<DexEncodedMethod> isProcessedConcurrently,
-      CallSiteInformation callSiteInformation,
-      BiConsumer<IRCode, DexEncodedMethod> outlineHandler) {
+      DexEncodedMethod method, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
     Origin origin = appView.appInfo().originFor(method.method.holder);
     try {
-      rewriteCodeInternal(
-          method, feedback, isProcessedConcurrently, callSiteInformation, outlineHandler, origin);
+      rewriteCodeInternal(method, feedback, methodProcessor, origin);
     } catch (CompilationError e) {
       // If rewriting throws a compilation error, attach the origin and method if missing.
       throw e.withAdditionalOriginAndPositionInfo(origin, new MethodPosition(method.method));
@@ -1035,9 +1039,7 @@
   private void rewriteCodeInternal(
       DexEncodedMethod method,
       OptimizationFeedback feedback,
-      Predicate<DexEncodedMethod> isProcessedConcurrently,
-      CallSiteInformation callSiteInformation,
-      BiConsumer<IRCode, DexEncodedMethod> outlineHandler,
+      MethodProcessor methodProcessor,
       Origin origin) {
 
     if (options.verbose) {
@@ -1057,6 +1059,17 @@
       feedback.markProcessed(method, ConstraintWithTarget.NEVER);
       return;
     }
+    optimize(appView, code, feedback, methodProcessor);
+  }
+
+  // TODO(b/140766440): Convert all sub steps an implementer of CodeOptimization
+  private void optimize(
+      AppView<?> appView,
+      IRCode code,
+      OptimizationFeedback feedback,
+      MethodProcessor methodProcessor) {
+    DexEncodedMethod method = code.method;
+
     if (Log.ENABLED) {
       Log.debug(getClass(), "Initial (SSA) flow graph for %s:\n%s", method.toSourceString(), code);
     }
@@ -1137,28 +1150,16 @@
 
     previous = printMethod(code, "IR after disable assertions (SSA)", previous);
 
-    if (aliasIntroducer != null) {
-      aliasIntroducer.insertAssumeInstructions(code);
-      assert code.isConsistentSSA();
-    }
+    CodeRewriter.insertAssumeInstructions(code, assumers);
 
-    if (nonNullTracker != null) {
-      nonNullTracker.insertAssumeInstructions(code);
-      assert code.isConsistentSSA();
-    }
-
-    if (dynamicTypeOptimization != null) {
-      assert appView.enableWholeProgramOptimizations();
-      dynamicTypeOptimization.insertAssumeInstructions(code);
-      assert code.isConsistentSSA();
-    }
+    previous = printMethod(code, "IR after inserting assume instructions (SSA)", previous);
 
     appView.withGeneratedMessageLiteShrinker(shrinker -> shrinker.run(method, code));
 
-    previous = printMethod(code, "IR after null tracking (SSA)", previous);
+    previous = printMethod(code, "IR after generated message lite shrinking (SSA)", previous);
 
     if (!isDebugMode && options.enableInlining && inliner != null) {
-      inliner.performInlining(method, code, feedback, isProcessedConcurrently, callSiteInformation);
+      inliner.performInlining(method, code, feedback, methodProcessor);
       assert code.verifyTypes(appView);
     }
 
@@ -1276,15 +1277,14 @@
           stringOptimizer,
           method,
           code,
-          isProcessedConcurrently,
+          methodProcessor::isProcessedConcurrently,
           inliner,
           Suppliers.memoize(
               () ->
                   inliner.createDefaultOracle(
                       method,
                       code,
-                      isProcessedConcurrently,
-                      callSiteInformation,
+                      methodProcessor,
                       options.classInliningInstructionLimit,
                       // Inlining instruction allowance is not needed for the class inliner since it
                       // always uses a force inlining oracle for inlining.
@@ -1332,8 +1332,10 @@
 
     previous = printMethod(code, "IR after lambda merger (SSA)", previous);
 
-    if (options.outline.enabled) {
-      outlineHandler.accept(code, method);
+    // TODO(b/140766440): an ideal solution would be puttting CodeOptimization for this into
+    //  the list for primary processing only.
+    if (options.outline.enabled && outliner != null && methodProcessor.isPrimary()) {
+      outliner.getOutlineMethodIdentifierGenerator().accept(code);
       assert code.isConsistentSSA();
     }
 
@@ -1383,7 +1385,7 @@
       collectOptimizationInfo(code, feedback);
     }
 
-    if (aliasIntroducer != null || nonNullTracker != null || dynamicTypeOptimization != null) {
+    if (!assumers.isEmpty()) {
       CodeRewriter.removeAssumeInstructions(appView, code);
       assert code.isConsistentSSA();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index 6a7633a..b2bf648 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -1,112 +1,27 @@
 // Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-
 package com.android.tools.r8.ir.conversion;
 
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.conversion.CallGraph.Node;
-import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Action;
-import com.android.tools.r8.utils.IROrdering;
-import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.ThrowingBiConsumer;
-import com.google.common.collect.Sets;
-import java.util.ArrayDeque;
-import java.util.Collection;
-import java.util.Deque;
-import java.util.Iterator;
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
 
-public class MethodProcessor {
+public interface MethodProcessor {
 
-  private final CallSiteInformation callSiteInformation;
-  private final Deque<Collection<DexEncodedMethod>> waves;
-
-  MethodProcessor(AppView<AppInfoWithLiveness> appView, CallGraph callGraph) {
-    this.callSiteInformation = callGraph.createCallSiteInformation(appView);
-    this.waves = createWaves(appView, callGraph, callSiteInformation);
+  enum Phase {
+    ONE_TIME,
+    PRIMARY,
+    POST
   }
 
-  public CallSiteInformation getCallSiteInformation() {
-    return callSiteInformation;
+  Phase getPhase();
+
+  default boolean isPrimary() {
+    return getPhase() == Phase.PRIMARY;
   }
 
-  public static Deque<Collection<DexEncodedMethod>> createWaves(
-      AppView<?> appView, CallGraph callGraph, CallSiteInformation callSiteInformation) {
-    IROrdering shuffle = appView.options().testing.irOrdering;
-    Deque<Collection<DexEncodedMethod>> waves = new ArrayDeque<>();
-
-    Set<Node> nodes = callGraph.nodes;
-    Set<DexEncodedMethod> reprocessing = Sets.newIdentityHashSet();
-    while (!nodes.isEmpty()) {
-      Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
-      extractLeaves(
-          nodes,
-          leaf -> {
-            wave.add(leaf.method);
-
-            // Reprocess methods that invoke a method with a single call site.
-            if (callSiteInformation.hasSingleCallSite(leaf.method.method)) {
-              callGraph.cycleEliminationResult.forEachRemovedCaller(
-                  leaf, caller -> reprocessing.add(caller.method));
-            }
-          });
-      waves.addLast(shuffle.order(wave));
-    }
-    // TODO(b/127694949): Reprocess these methods using a general framework for reprocessing
-    //  methods.
-    if (!reprocessing.isEmpty()) {
-      waves.addLast(shuffle.order(reprocessing));
-    }
-    return waves;
+  default CallSiteInformation getCallSiteInformation() {
+    return CallSiteInformation.empty();
   }
 
-  /**
-   * Extract the next set of leaves (nodes with an outgoing call degree of 0) if any.
-   *
-   * <p>All nodes in the graph are extracted if called repeatedly until null is returned. Please
-   * note that there are no cycles in this graph (see {@link CycleEliminator#breakCycles}).
-   */
-  static void extractLeaves(Set<Node> nodes, Consumer<Node> fn) {
-    Set<Node> removed = Sets.newIdentityHashSet();
-    Iterator<Node> nodeIterator = nodes.iterator();
-    while (nodeIterator.hasNext()) {
-      Node node = nodeIterator.next();
-      if (node.isLeaf()) {
-        fn.accept(node);
-        nodeIterator.remove();
-        removed.add(node);
-      }
-    }
-    removed.forEach(Node::cleanCallersForRemoval);
-  }
-
-  /**
-   * Applies the given method to all leaf nodes of the graph.
-   *
-   * <p>As second parameter, a predicate that can be used to decide whether another method is
-   * processed at the same time is passed. This can be used to avoid races in concurrent processing.
-   */
-  public <E extends Exception> void forEachMethod(
-      ThrowingBiConsumer<DexEncodedMethod, Predicate<DexEncodedMethod>, E> consumer,
-      Consumer<Collection<DexEncodedMethod>> waveStart,
-      Action waveDone,
-      ExecutorService executorService)
-      throws ExecutionException {
-    while (!waves.isEmpty()) {
-      Collection<DexEncodedMethod> wave = waves.removeFirst();
-      assert wave.size() > 0;
-      waveStart.accept(wave);
-      ThreadUtils.processItems(
-          wave, method -> consumer.accept(method, wave::contains), executorService);
-      waveDone.execute();
-    }
-  }
+  boolean isProcessedConcurrently(DexEncodedMethod method);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
new file mode 100644
index 0000000..43b801e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import java.util.Collection;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * A {@link MethodProcessor} that doesn't persist; rather just processes the given methods one-time,
+ * along with a default abstraction of concurrent processing.
+ */
+public class OneTimeMethodProcessor implements MethodProcessor {
+
+  private Collection<DexEncodedMethod> wave;
+
+  private OneTimeMethodProcessor(Collection<DexEncodedMethod> methodsToProcess) {
+    this.wave = methodsToProcess;
+  }
+
+  public static OneTimeMethodProcessor getInstance() {
+    return new OneTimeMethodProcessor(null);
+  }
+
+  static OneTimeMethodProcessor getInstance(Collection<DexEncodedMethod> methodsToProcess) {
+    return new OneTimeMethodProcessor(methodsToProcess);
+  }
+
+  @Override
+  public Phase getPhase() {
+    return Phase.ONE_TIME;
+  }
+
+  @Override
+  public boolean isProcessedConcurrently(DexEncodedMethod method) {
+    return wave != null && wave.contains(method);
+  }
+
+  <E extends Exception> void forEachWave(
+      ThrowingConsumer<DexEncodedMethod, E> consumer,
+      ExecutorService executorService)
+      throws ExecutionException {
+    ThreadUtils.processItems(wave, consumer, executorService);
+  }
+}
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 13566359..5107b18 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
@@ -4,13 +4,121 @@
 
 package com.android.tools.r8.ir.conversion;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.IROrdering;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Deque;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
 
-public class PostMethodProcessor {
+class PostMethodProcessor implements MethodProcessor {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final Map<DexEncodedMethod, Collection<CodeOptimization>> methodsMap;
+  private final Deque<Collection<DexEncodedMethod>> waves;
+  private Collection<DexEncodedMethod> wave;
+
+  private PostMethodProcessor(
+      AppView<AppInfoWithLiveness> appView,
+      Map<DexEncodedMethod, Collection<CodeOptimization>> methodsMap,
+      CallGraph callGraph) {
+    this.appView = appView;
+    this.methodsMap = methodsMap;
+    this.waves = createWaves(appView, callGraph);
+  }
+
+  @Override
+  public Phase getPhase() {
+    return Phase.POST;
+  }
+
+  static class Builder {
+    private final Collection<CodeOptimization> defaultCodeOptimizations;
+    private final Map<DexEncodedMethod, Collection<CodeOptimization>> methodsMap =
+        Maps.newIdentityHashMap();
+
+    Builder(Collection<CodeOptimization> defaultCodeOptimizations) {
+      this.defaultCodeOptimizations = defaultCodeOptimizations;
+    }
+
+    private void put(
+        Set<DexEncodedMethod> methodsToRevisit, Collection<CodeOptimization> codeOptimizations) {
+      if (codeOptimizations.isEmpty()) {
+        // Nothing to conduct.
+        return;
+      }
+      for (DexEncodedMethod method : methodsToRevisit) {
+        methodsMap
+            .computeIfAbsent(
+                method,
+                // Optimization order might matter, hence a collection that preserves orderings.
+                k -> new LinkedHashSet<>())
+            .addAll(codeOptimizations);
+      }
+    }
+
+    void put(Set<DexEncodedMethod> methodsToRevisit) {
+      put(methodsToRevisit, defaultCodeOptimizations);
+    }
+
+    void put(PostOptimization postOptimization) {
+      Collection<CodeOptimization> codeOptimizations =
+          postOptimization.codeOptimizationsForPostProcessing();
+      if (codeOptimizations == null) {
+        codeOptimizations = defaultCodeOptimizations;
+      }
+      put(postOptimization.methodsToRevisit(), codeOptimizations);
+    }
+
+    PostMethodProcessor build(
+        AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
+        throws ExecutionException {
+      if (methodsMap.keySet().isEmpty()) {
+        // Nothing to revisit.
+        return null;
+      }
+      CallGraph callGraph =
+          new PartialCallGraphBuilder(appView, methodsMap.keySet())
+              .build(executorService, timing);
+      return new PostMethodProcessor(appView, methodsMap, callGraph);
+    }
+  }
+
+  private Deque<Collection<DexEncodedMethod>> createWaves(AppView<?> appView, CallGraph callGraph) {
+    IROrdering shuffle = appView.options().testing.irOrdering;
+    Deque<Collection<DexEncodedMethod>> waves = new ArrayDeque<>();
+
+    Set<Node> nodes = callGraph.nodes;
+    int waveCount = 1;
+    while (!nodes.isEmpty()) {
+      Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
+      extractRoots(nodes, n -> wave.add(n.method));
+      waves.addLast(shuffle.order(wave));
+      if (Log.ENABLED && Log.isLoggingEnabledFor(PostMethodProcessor.class)) {
+        Log.info(getClass(), "Wave #%d: %d", waveCount++, wave.size());
+      }
+    }
+
+    return waves;
+  }
 
   /**
    * Extract the next set of roots (nodes with an incoming call degree of 0) if any.
@@ -30,4 +138,47 @@
     }
     removed.forEach(Node::cleanCalleesForRemoval);
   }
+
+  @Override
+  public boolean isProcessedConcurrently(DexEncodedMethod method) {
+    return wave != null && wave.contains(method);
+  }
+
+  void forEachWave(OptimizationFeedback feedback, ExecutorService executorService)
+      throws ExecutionException {
+    while (!waves.isEmpty()) {
+      wave = waves.removeFirst();
+      assert wave.size() > 0;
+      ThreadUtils.processItems(
+          wave,
+          method -> {
+            Collection<CodeOptimization> codeOptimizations = methodsMap.get(method);
+            assert codeOptimizations != null && !codeOptimizations.isEmpty();
+            forEachMethod(method, codeOptimizations, feedback);
+          },
+          executorService);
+    }
+  }
+
+  private void forEachMethod(
+      DexEncodedMethod method,
+      Collection<CodeOptimization> codeOptimizations,
+      OptimizationFeedback feedback) {
+    // TODO(b/140766440): Make IRConverter#process receive a list of CodeOptimization to conduct.
+    //   Then, we can share IRCode creation there.
+    Origin origin = appView.appInfo().originFor(method.method.holder);
+    if (appView.options().skipIR) {
+      feedback.markProcessed(method, ConstraintWithTarget.NEVER);
+      return;
+    }
+    IRCode code = method.buildIR(appView, origin);
+    if (code == null) {
+      feedback.markProcessed(method, ConstraintWithTarget.NEVER);
+      return;
+    }
+    // TODO(b/140768815): Reprocessing may trigger more methods to revisit. Update waves on-the-fly.
+    for (CodeOptimization codeOptimization : codeOptimizations) {
+      codeOptimization.optimize(appView, code, feedback, this);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java b/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java
new file mode 100644
index 0000000..5ab18b3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * An abstraction of optimizations that require post processing of methods.
+ */
+public interface PostOptimization {
+
+  /**
+   * @return a set of methods that need post processing.
+   */
+  Set<DexEncodedMethod> methodsToRevisit();
+
+  // TODO(b/127694949): different CodeOptimization for primary processor v.s. post processor?
+  //  In that way, instead of internal state changes, such as COLLECT v.s. APPLY or REVISIT,
+  //  optimizers that need post processing can return what to do at each processor.
+  // Collection<CodeOptimization> codeOptimizationsForPrimaryProcessing();
+
+  /**
+   * @return specific collection of {@link CodeOptimization}s to conduct during post processing.
+   *   Otherwise, i.e., if the default one---IRConverter's full processing---is okay,
+   *   returns {@code null}.
+   */
+  Collection<CodeOptimization> codeOptimizationsForPostProcessing();
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
new file mode 100644
index 0000000..1787587
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -0,0 +1,145 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.conversion.CallGraph.Node;
+import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator;
+import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.IROrdering;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
+
+/**
+ * A {@link MethodProcessor} that processes methods in the whole program in a bottom-up manner,
+ * i.e., from leaves to roots.
+ */
+class PrimaryMethodProcessor implements MethodProcessor {
+
+  private final CallSiteInformation callSiteInformation;
+  private final PostMethodProcessor.Builder postMethodProcessorBuilder;
+  private final Deque<Collection<DexEncodedMethod>> waves;
+  private Collection<DexEncodedMethod> wave;
+
+  private PrimaryMethodProcessor(
+      AppView<AppInfoWithLiveness> appView,
+      PostMethodProcessor.Builder postMethodProcessorBuilder,
+      CallGraph callGraph) {
+    this.callSiteInformation = callGraph.createCallSiteInformation(appView);
+    this.postMethodProcessorBuilder = postMethodProcessorBuilder;
+    this.waves = createWaves(appView, callGraph, callSiteInformation);
+  }
+
+  static PrimaryMethodProcessor create(
+      AppView<AppInfoWithLiveness> appView,
+      PostMethodProcessor.Builder postMethodProcessorBuilder,
+      ExecutorService executorService,
+      Timing timing)
+      throws ExecutionException {
+    CallGraph callGraph = CallGraph.builder(appView).build(executorService, timing);
+    return new PrimaryMethodProcessor(appView, postMethodProcessorBuilder, callGraph);
+  }
+
+  @Override
+  public Phase getPhase() {
+    return Phase.PRIMARY;
+  }
+
+  @Override
+  public CallSiteInformation getCallSiteInformation() {
+    return callSiteInformation;
+  }
+
+  private Deque<Collection<DexEncodedMethod>> createWaves(
+      AppView<?> appView, CallGraph callGraph, CallSiteInformation callSiteInformation) {
+    IROrdering shuffle = appView.options().testing.irOrdering;
+    Deque<Collection<DexEncodedMethod>> waves = new ArrayDeque<>();
+
+    Set<Node> nodes = callGraph.nodes;
+    Set<DexEncodedMethod> reprocessing = Sets.newIdentityHashSet();
+    int waveCount = 1;
+    while (!nodes.isEmpty()) {
+      Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
+      extractLeaves(
+          nodes,
+          leaf -> {
+            wave.add(leaf.method);
+
+            // Reprocess methods that invoke a method with a single call site.
+            if (callSiteInformation.hasSingleCallSite(leaf.method.method)) {
+              callGraph.cycleEliminationResult.forEachRemovedCaller(
+                  leaf, caller -> reprocessing.add(caller.method));
+            }
+          });
+      waves.addLast(shuffle.order(wave));
+      if (Log.ENABLED && Log.isLoggingEnabledFor(PrimaryMethodProcessor.class)) {
+        Log.info(getClass(), "Wave #%d: %d", waveCount++, wave.size());
+      }
+    }
+    if (!reprocessing.isEmpty()) {
+      postMethodProcessorBuilder.put(reprocessing);
+    }
+    return waves;
+  }
+
+  /**
+   * Extract the next set of leaves (nodes with an outgoing call degree of 0) if any.
+   *
+   * <p>All nodes in the graph are extracted if called repeatedly until null is returned. Please
+   * note that there are no cycles in this graph (see {@link CycleEliminator#breakCycles}).
+   */
+  static void extractLeaves(Set<Node> nodes, Consumer<Node> fn) {
+    Set<Node> removed = Sets.newIdentityHashSet();
+    Iterator<Node> nodeIterator = nodes.iterator();
+    while (nodeIterator.hasNext()) {
+      Node node = nodeIterator.next();
+      if (node.isLeaf()) {
+        fn.accept(node);
+        nodeIterator.remove();
+        removed.add(node);
+      }
+    }
+    removed.forEach(Node::cleanCallersForRemoval);
+  }
+
+  @Override
+  public boolean isProcessedConcurrently(DexEncodedMethod method) {
+    return wave != null && wave.contains(method);
+  }
+
+  /**
+   * Applies the given method to all leaf nodes of the graph.
+   *
+   * <p>As second parameter, a predicate that can be used to decide whether another method is
+   * processed at the same time is passed. This can be used to avoid races in concurrent processing.
+   */
+  <E extends Exception> void forEachMethod(
+      ThrowingConsumer<DexEncodedMethod, E> consumer,
+      Consumer<Collection<DexEncodedMethod>> waveStart,
+      Action waveDone,
+      ExecutorService executorService)
+      throws ExecutionException {
+    while (!waves.isEmpty()) {
+      wave = waves.removeFirst();
+      assert wave.size() > 0;
+      waveStart.accept(wave);
+      ThreadUtils.processItems(wave, consumer, executorService);
+      waveDone.execute();
+    }
+  }
+}
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 899277d..54ac5c0 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
@@ -1063,9 +1063,15 @@
   }
 
   private void warnMissingType(DexMethod referencedFrom, DexType missing) {
-    // Companion/Emulated interface classes for desugared library won't be missing,
+    // Companion/Emulated interface/Conversion classes for desugared library won't be missing,
     // they are in the desugared library.
-    if (appView.rewritePrefix.hasRewrittenType(missing)) {
+    if (appView.rewritePrefix.hasRewrittenType(missing)
+        || appView
+            .options()
+            .desugaredLibraryConfiguration
+            .getCustomConversions()
+            .values()
+            .contains(missing)) {
       return;
     }
     DexMethod method = appView.graphLense().getOriginalMethodSignature(referencedFrom);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Assumer.java b/src/main/java/com/android/tools/r8/ir/optimize/Assumer.java
index 10e7314..dfabec5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Assumer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Assumer.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import com.android.tools.r8.Keep;
 import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
@@ -13,10 +14,14 @@
 /**
  * One that assumes. Inherited tracker/optimization insert necessary variants of {@link Assume}.
  */
-interface Assumer {
+// TODO(b/143590191): should not need an explicit keep annotation to prevent the default interface
+//  method from being shrunk.
+@Keep
+public interface Assumer {
   default void insertAssumeInstructions(IRCode code) {
     insertAssumeInstructionsInBlocks(code, code.listIterator(), Predicates.alwaysTrue());
   }
+
   void insertAssumeInstructionsInBlocks(
       IRCode code, ListIterator<BasicBlock> blockIterator, Predicate<BasicBlock> blockTester);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index 2f6c563..cbedf91 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -20,7 +20,8 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.CodeOptimization;
+import com.android.tools.r8.ir.conversion.PostOptimization;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MutableCallSiteOptimizationInfo;
 import com.android.tools.r8.logging.Log;
@@ -30,10 +31,8 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
 
-public class CallSiteOptimizationInfoPropagator {
+public class CallSiteOptimizationInfoPropagator implements PostOptimization {
 
   // TODO(b/139246447): should we revisit new targets over and over again?
   //   Maybe piggy-back on MethodProcessor's wave/batch processing?
@@ -231,8 +230,9 @@
     }
   }
 
-  public void revisitMethods(IRConverter converter, ExecutorService executorService)
-      throws ExecutionException {
+  @Override
+  public Set<DexEncodedMethod> methodsToRevisit() {
+    mode = Mode.REVISIT;
     Set<DexEncodedMethod> targetsToRevisit = Sets.newIdentityHashSet();
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       for (DexEncodedMethod method : clazz.methods()) {
@@ -248,13 +248,15 @@
         }
       }
     }
-    mode = Mode.REVISIT;
-    if (targetsToRevisit.isEmpty()) {
-      return;
-    }
     if (revisitedMethods != null) {
       revisitedMethods.addAll(targetsToRevisit);
     }
-    converter.processMethodsConcurrently(targetsToRevisit, executorService);
+    return targetsToRevisit;
+  }
+
+  @Override
+  public Collection<CodeOptimization> codeOptimizationsForPostProcessing() {
+    // Run IRConverter#optimize.
+    return null;
   }
 }
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 f6fba1d..f2c9031 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
@@ -14,7 +14,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -27,6 +26,7 @@
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.AlwaysMaterializingNop;
 import com.android.tools.r8.ir.code.ArrayLength;
 import com.android.tools.r8.ir.code.ArrayPut;
@@ -159,6 +159,13 @@
     this.dexItemFactory = appView.dexItemFactory();
   }
 
+  public static void insertAssumeInstructions(IRCode code, Collection<Assumer> assumers) {
+    for (Assumer assumer : assumers) {
+      assumer.insertAssumeInstructions(code);
+      assert code.isConsistentSSA();
+    }
+  }
+
   public static void removeAssumeInstructions(AppView<?> appView, IRCode code) {
     // We need to update the types of all values whose definitions depend on a non-null value.
     // This is needed to preserve soundness of the types after the Assume<NonNullAssumption>
@@ -1086,12 +1093,12 @@
           // If the original input to the switch is now unused, remove it too. It is not dead
           // as it might have side-effects but we ignore these here.
           Instruction arrayGet = info.arrayGet;
-          if (arrayGet.outValue().numberOfUsers() == 0) {
+          if (!arrayGet.outValue().hasUsers()) {
             arrayGet.inValues().forEach(v -> v.removeUser(arrayGet));
             arrayGet.getBlock().removeInstruction(arrayGet);
           }
           Instruction staticGet = info.staticGet;
-          if (staticGet.outValue().numberOfUsers() == 0) {
+          if (!staticGet.outValue().hasUsers()) {
             assert staticGet.inValues().isEmpty();
             staticGet.getBlock().removeInstruction(staticGet);
           }
@@ -1392,7 +1399,7 @@
     while (it.hasNext()) {
       Instruction current = it.next();
       if (current.isCheckCast()) {
-        boolean hasPhiUsers = current.outValue().numberOfPhiUsers() != 0;
+        boolean hasPhiUsers = current.outValue().hasPhiUsers();
         RemoveCheckCastInstructionIfTrivialResult removeResult =
             removeCheckCastInstructionIfTrivial(current.asCheckCast(), it, code, affectedValues);
         if (removeResult != RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS) {
@@ -1402,7 +1409,7 @@
           affectedValues.clear();
         }
       } else if (current.isInstanceOf()) {
-        boolean hasPhiUsers = current.outValue().numberOfPhiUsers() != 0;
+        boolean hasPhiUsers = current.outValue().hasPhiUsers();
         if (removeInstanceOfInstructionIfTrivial(current.asInstanceOf(), it, code)) {
           needToRemoveTrivialPhis |= hasPhiUsers;
         }
@@ -1749,8 +1756,8 @@
             dominatorTreeMemoization,
             addConstantInBlock,
             insn ->
-                (insn.isConstNumber() && insn.outValue().numberOfAllUsers() != 0)
-                    || (insn.isConstString() && insn.outValue().numberOfAllUsers() != 0));
+                (insn.isConstNumber() && insn.outValue().hasAnyUsers())
+                    || (insn.isConstString() && insn.outValue().hasAnyUsers()));
       } else {
         // For all following blocks only process ConstString with just one use.
         shortenLiveRangesInsideBlock(
@@ -1778,12 +1785,12 @@
         // except if they are used by phi instructions or they are a string constants.
         assert constants instanceof LinkedHashMap;
         for (Instruction constantInstruction : constants.values()) {
-          if (constantInstruction.outValue().numberOfPhiUsers() == 0
+          if (!constantInstruction.outValue().hasPhiUsers()
               && !constantInstruction.isConstString()) {
             assert constantInstruction.isConstNumber();
             ConstNumber constNumber = constantInstruction.asConstNumber();
             Value constantValue = constantInstruction.outValue();
-            assert constantValue.numberOfUsers() != 0;
+            assert constantValue.hasUsers();
             assert constantValue.numberOfUsers() == constantValue.numberOfAllUsers();
             for (Instruction user : constantValue.uniqueUsers()) {
               ConstNumber newCstNum = ConstNumber.copyOf(code, constNumber);
@@ -2532,12 +2539,12 @@
               }
             }
           } else {
-            DexEncodedField enumField = lhs.getEnumField(appView);
-            if (enumField != null) {
-              DexEncodedField otherEnumField = rhs.getEnumField(appView);
-              if (enumField == otherEnumField) {
+            AbstractValue abstractValue = lhs.getAbstractValue(appView);
+            if (abstractValue.isSingleEnumValue()) {
+              AbstractValue otherAbstractValue = rhs.getAbstractValue(appView);
+              if (abstractValue == otherAbstractValue) {
                 simplifyIfWithKnownCondition(code, block, theIf, 0);
-              } else if (otherEnumField != null) {
+              } else if (otherAbstractValue.isSingleEnumValue()) {
                 simplifyIfWithKnownCondition(code, block, theIf, 1);
               }
             }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index 8dc27fd..b14a47a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -16,9 +16,10 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
+import java.util.ArrayDeque;
 import java.util.Collection;
+import java.util.Deque;
 import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.Queue;
 
 public class DeadCodeRemover {
@@ -33,13 +34,14 @@
 
   public void run(IRCode code) {
     removeUnneededCatchHandlers(code);
-    Queue<BasicBlock> worklist = new LinkedList<>();
+    Deque<BasicBlock> worklist = new ArrayDeque<>();
     // We may encounter unneeded catch handlers again, e.g., if a dead instruction (due to
     // const-string canonicalization for example) is the only throwing instruction in a block.
     // Removing unneeded catch handlers can lead to more dead instructions.
     do {
-      worklist.addAll(code.blocks);
-      for (BasicBlock block = worklist.poll(); block != null; block = worklist.poll()) {
+      worklist.addAll(code.topologicallySortedBlocks());
+      while (!worklist.isEmpty()) {
+        BasicBlock block = worklist.removeLast();
         removeDeadInstructions(worklist, code, block);
         removeDeadPhis(worklist, code, block);
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 893cf2d..fea8d6c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.ir.code.Monitor;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.CallSiteInformation;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -53,6 +54,7 @@
   private final Inliner inliner;
   private final DexEncodedMethod method;
   private final IRCode code;
+  private final MethodProcessor methodProcessor;
   private final CallSiteInformation callSiteInformation;
   private final Predicate<DexEncodedMethod> isProcessedConcurrently;
   private final int inliningInstructionLimit;
@@ -63,16 +65,16 @@
       Inliner inliner,
       DexEncodedMethod method,
       IRCode code,
-      CallSiteInformation callSiteInformation,
-      Predicate<DexEncodedMethod> isProcessedConcurrently,
+      MethodProcessor methodProcessor,
       int inliningInstructionLimit,
       int inliningInstructionAllowance) {
     this.appView = appView;
     this.inliner = inliner;
     this.method = method;
     this.code = code;
-    this.callSiteInformation = callSiteInformation;
-    this.isProcessedConcurrently = isProcessedConcurrently;
+    this.methodProcessor = methodProcessor;
+    this.callSiteInformation = methodProcessor.getCallSiteInformation();
+    this.isProcessedConcurrently = methodProcessor::isProcessedConcurrently;
     this.inliningInstructionLimit = inliningInstructionLimit;
     this.instructionAllowance = inliningInstructionAllowance;
   }
@@ -428,8 +430,7 @@
       if (Log.ENABLED) {
         Log.verbose(getClass(), "Forcing extra inline on " + target.toSourceString());
       }
-      inliner.performInlining(
-          target, inlinee, feedback, isProcessedConcurrently, callSiteInformation);
+      inliner.performInlining(target, inlinee, feedback, methodProcessor);
     }
   }
 
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 606a489..70013de 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
@@ -39,8 +39,10 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.CallSiteInformation;
-import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.CodeOptimization;
 import com.android.tools.r8.ir.conversion.LensCodeRewriter;
+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.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
@@ -58,17 +60,15 @@
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.function.Predicate;
 
-public class Inliner {
+public class Inliner implements PostOptimization {
 
   protected final AppView<AppInfoWithLiveness> appView;
   private final Set<DexMethod> blacklist;
@@ -237,17 +237,16 @@
     }
   }
 
-  public void processDoubleInlineCallers(
-      IRConverter converter, ExecutorService executorService)
-      throws ExecutionException {
-    if (doubleInlineCallers.isEmpty()) {
-      return;
-    }
+  @Override
+  public Set<DexEncodedMethod> methodsToRevisit() {
     applyDoubleInlining = true;
-    converter.processMethodsConcurrently(doubleInlineCallers, executorService);
-    doubleInlineCallers.forEach(method -> {
-      assert method.isProcessed();
-    });
+    return doubleInlineCallers;
+  }
+
+  @Override
+  public Collection<CodeOptimization> codeOptimizationsForPostProcessing() {
+    // Run IRConverter#optimize.
+    return null;  // Technically same as return converter.getOptimizationForPostIRProcessing();
   }
 
   /**
@@ -824,15 +823,13 @@
       DexEncodedMethod method,
       IRCode code,
       OptimizationFeedback feedback,
-      Predicate<DexEncodedMethod> isProcessedConcurrently,
-      CallSiteInformation callSiteInformation) {
+      MethodProcessor methodProcessor) {
     InternalOptions options = appView.options();
     DefaultInliningOracle oracle =
         createDefaultOracle(
             method,
             code,
-            isProcessedConcurrently,
-            callSiteInformation,
+            methodProcessor,
             options.inliningInstructionLimit,
             options.inliningInstructionAllowance - numberOfInstructions(code));
     performInliningImpl(oracle, oracle, method, code, feedback);
@@ -841,8 +838,7 @@
   public DefaultInliningOracle createDefaultOracle(
       DexEncodedMethod method,
       IRCode code,
-      Predicate<DexEncodedMethod> isProcessedConcurrently,
-      CallSiteInformation callSiteInformation,
+      MethodProcessor methodProcessor,
       int inliningInstructionLimit,
       int inliningInstructionAllowance) {
     return new DefaultInliningOracle(
@@ -850,8 +846,7 @@
         this,
         method,
         code,
-        callSiteInformation,
-        isProcessedConcurrently,
+        methodProcessor,
         inliningInstructionLimit,
         inliningInstructionAllowance);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index cea6258..adc1112 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -270,7 +270,7 @@
       // references that have actual definitions are marked by the root set builder. So, here, we
       // try again with a resolved target, not the direct definition, which may not exist.
       DexEncodedMethod resolutionTarget =
-          appView.appInfo().resolveMethod(invokedHolder, invokedMethod).asSingleTarget();
+          appView.appInfo().resolveMethod(invokedHolder, invokedMethod).getSingleTarget();
       lookup = lookupMemberRule(resolutionTarget);
     }
     boolean invokeReplaced = false;
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 0795424..7f055de 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
@@ -55,7 +55,6 @@
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.code.ValueTypeConstraint;
 import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.SourceCode;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -77,7 +76,7 @@
 import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
-import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
 /**
  * Support class for implementing outlining (i.e. extracting common code patterns as methods).
@@ -86,27 +85,26 @@
  *
  * <ul>
  *   <li>First, all methods are converted to IR and passed to {@link
- *       Outliner#identifyCandidateMethods()} to identify outlining candidates and the methods
- *       containing each candidate. IR is converted to the output format (DEX or CF) and thrown away
- *       along with the outlining candidates; only a list of lists of methods is kept, where each
- *       list of methods corresponds to methods containing an outlining candidate.
+ *       Outliner#createOutlineMethodIdentifierGenerator()}} to identify outlining candidates and
+ *       the methods containing each candidate. IR is converted to the output format (DEX or CF) and
+ *       thrown away along with the outlining candidates; only a list of lists of methods is kept,
+ *       where each list of methods corresponds to methods containing an outlining candidate.
  *   <li>Second, {@link Outliner#selectMethodsForOutlining()} is called to retain the lists of
  *       methods found in the first step that are large enough (see {@link InternalOptions#outline}
  *       {@link OutlineOptions#threshold}), and the methods to be further analyzed for outlining is
  *       returned by {@link Outliner#getMethodsSelectedForOutlining}. Each selected method is then
- *       converted back to IR and passed to {@link Outliner#identifyOutlineSites(IRCode,
- *       DexEncodedMethod)}, which then stores concrete outlining candidates in {@link
- *       Outliner#outlineSites}.
+ *       converted back to IR and passed to {@link Outliner#identifyOutlineSites(IRCode)}, which
+ *       then stores concrete outlining candidates in {@link Outliner#outlineSites}.
  *   <li>Third, {@link Outliner#buildOutlinerClass(DexType)} is called to construct the <em>outline
  *       support class</em> containing a static helper method for each outline candidate that occurs
  *       frequently enough. Each selected method is then converted to IR, passed to {@link
- *       Outliner#applyOutliningCandidate(IRCode, DexEncodedMethod)} to perform the outlining, and
+ *       Outliner#applyOutliningCandidate(IRCode)} to perform the outlining, and
  *       converted back to the output format (DEX or CF).
  * </ul>
  */
 public class Outliner {
 
-  /** Result of first step (see {@link Outliner#identifyCandidateMethods()}. */
+  /** Result of first step (see {@link Outliner#createOutlineMethodIdentifierGenerator()}. */
   private final List<List<DexEncodedMethod>> candidateMethodLists = new ArrayList<>();
   /** Result of second step (see {@link Outliner#selectMethodsForOutlining()}. */
   private final Set<DexEncodedMethod> methodsSelectedForOutlining = Sets.newIdentityHashSet();
@@ -1188,9 +1186,11 @@
     int argumentsMapIndex;
 
     OutlineRewriter(
-        DexEncodedMethod method, IRCode code,
-        ListIterator<BasicBlock> blocksIterator, BasicBlock block, List<Integer> toRemove) {
-      super(method, block);
+        IRCode code,
+        ListIterator<BasicBlock> blocksIterator,
+        BasicBlock block,
+        List<Integer> toRemove) {
+      super(code.method, block);
       this.code = code;
       this.blocksIterator = blocksIterator;
       this.toRemove = toRemove;
@@ -1271,30 +1271,39 @@
     }
   }
 
-  public Outliner(AppView<AppInfoWithLiveness> appView, IRConverter converter) {
+  public Outliner(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
     this.inliningConstraints = new InliningConstraints(appView, GraphLense.getIdentityLense());
   }
 
-  public BiConsumer<IRCode, DexEncodedMethod> identifyCandidateMethods() {
+  public void createOutlineMethodIdentifierGenerator() {
     // Since optimizations may change the map identity of Outline objects (e.g. by setting the
     // out-value of invokes to null), this map must not be used except for identifying methods
     // potentially relevant to outlining. OutlineMethodIdentifier will add method lists to
     // candidateMethodLists whenever it adds an entry to candidateMap.
     Map<Outline, List<DexEncodedMethod>> candidateMap = new HashMap<>();
     assert candidateMethodLists.isEmpty();
-    return (code, method) -> {
-      assert !(method.getCode() instanceof OutlineCode);
-      for (BasicBlock block : code.blocks) {
-        new OutlineMethodIdentifier(method, block, candidateMap).process();
-      }
-    };
+    assert outlineMethodIdentifierGenerator == null;
+    outlineMethodIdentifierGenerator =
+        code -> {
+          assert !code.method.getCode().isOutlineCode();
+          for (BasicBlock block : code.blocks) {
+            new OutlineMethodIdentifier(code.method, block, candidateMap).process();
+          }
+        };
   }
 
-  public void identifyOutlineSites(IRCode code, DexEncodedMethod method) {
-    assert !(method.getCode() instanceof OutlineCode);
+  private Consumer<IRCode> outlineMethodIdentifierGenerator;
+
+  public Consumer<IRCode> getOutlineMethodIdentifierGenerator() {
+    assert outlineMethodIdentifierGenerator != null;
+    return outlineMethodIdentifierGenerator;
+  }
+
+  public void identifyOutlineSites(IRCode code) {
+    assert !code.method.getCode().isOutlineCode();
     for (BasicBlock block : code.blocks) {
-      new OutlineSiteIdentifier(method, block).process();
+      new OutlineSiteIdentifier(code.method, block).process();
     }
   }
 
@@ -1391,13 +1400,13 @@
     return result;
   }
 
-  public void applyOutliningCandidate(IRCode code, DexEncodedMethod method) {
-    assert !(method.getCode() instanceof OutlineCode);
+  public void applyOutliningCandidate(IRCode code) {
+    assert !code.method.getCode().isOutlineCode();
     ListIterator<BasicBlock> blocksIterator = code.listIterator();
     while (blocksIterator.hasNext()) {
       BasicBlock block = blocksIterator.next();
       List<Integer> toRemove = new ArrayList<>();
-      new OutlineRewriter(method, code, blocksIterator, block, toRemove).process();
+      new OutlineRewriter(code, blocksIterator, block, toRemove).process();
       block.removeInstructions(toRemove);
     }
   }
@@ -1409,10 +1418,6 @@
     return true;
   }
 
-  static public void noProcessing(IRCode code, DexEncodedMethod method) {
-    // No operation.
-  }
-
   private class OutlineSourceCode implements SourceCode {
 
     final private Outline outline;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index 451788f..29ad438 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -97,8 +97,7 @@
 
       InvokeStatic serviceLoaderLoad = instruction.asInvokeStatic();
       Value serviceLoaderLoadOut = serviceLoaderLoad.outValue();
-      if (serviceLoaderLoadOut.numberOfAllUsers() != 1
-          || serviceLoaderLoadOut.numberOfPhiUsers() != 0) {
+      if (serviceLoaderLoadOut.numberOfAllUsers() != 1 || serviceLoaderLoadOut.hasPhiUsers()) {
         continue;
       }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 1203a35..40cb024 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -447,7 +447,7 @@
       Assume<?> assumeInstruction = user.asAssume();
       Value src = assumeInstruction.src();
       Value dest = assumeInstruction.outValue();
-      assert dest.numberOfPhiUsers() == 0;
+      assert !dest.hasPhiUsers();
       dest.replaceUsers(src);
       removeInstruction(user);
     }
@@ -550,7 +550,7 @@
       for (FieldValueHelper fieldValueHelper : fieldHelpers.values()) {
         fieldValueHelper.replaceValue(value, newValue);
       }
-      assert value.numberOfAllUsers() == 0;
+      assert !value.hasAnyUsers();
       // `newValue` could be a phi introduced by FieldValueHelper. Its initial type is set as the
       // type of read field, but it could be more precise than that due to (multiple) inlining.
       // In addition to values affected by `newValue`, it's necessary to revisit `newValue` itself.
@@ -655,7 +655,7 @@
       Set<Instruction> indirectUsers) {
     if (!eligibility.returnsReceiver
         || invoke.outValue() == null
-        || invoke.outValue().numberOfAllUsers() == 0) {
+        || !invoke.outValue().hasAnyUsers()) {
       return true;
     }
     // For CF we no longer perform the code-rewrite in CodeRewriter.rewriteMoveResult that removes
@@ -733,7 +733,7 @@
     // signature of the invocation resolves to a private or static method.
     ResolutionResult resolutionResult = appView.appInfo().resolveMethod(callee.holder, callee);
     if (resolutionResult.hasSingleTarget()
-        && !resolutionResult.asSingleTarget().isVirtualMethod()) {
+        && !resolutionResult.getSingleTarget().isVirtualMethod()) {
       return null;
     }
 
@@ -885,7 +885,7 @@
     }
 
     if (parameterUsage.isReturned) {
-      if (!(invoke.outValue() == null || invoke.outValue().numberOfAllUsers() == 0)) {
+      if (invoke.outValue() != null && invoke.outValue().hasAnyUsers()) {
         // Used as return value which is not ignored.
         return false;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
index e82afb7..a30c24f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
@@ -6,6 +6,8 @@
 
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
 
 public class DefaultFieldOptimizationInfo extends FieldOptimizationInfo {
 
@@ -28,6 +30,11 @@
   }
 
   @Override
+  public AbstractValue getAbstractValue() {
+    return UnknownValue.getInstance();
+  }
+
+  @Override
   public int getReadBits() {
     return BitAccessInfo.getNoBitsReadValue();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
index cc89f84..9103459 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 
 public abstract class FieldOptimizationInfo {
 
@@ -13,6 +14,8 @@
 
   public abstract boolean cannotBeKept();
 
+  public abstract AbstractValue getAbstractValue();
+
   /**
    * This should only be used once all methods in the program have been processed. Until then the
    * value returned by this method may not be sound.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index fb442d1..e002c13 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -58,7 +58,6 @@
 import java.util.List;
 import java.util.Set;
 import java.util.function.BiFunction;
-import java.util.function.Function;
 
 public class MethodOptimizationInfoCollector {
   private final AppView<AppInfoWithLiveness> appView;
@@ -291,7 +290,7 @@
     feedback.setTrivialInitializer(
         method,
         method.isInstanceInitializer()
-            ? computeInstanceInitializerInfo(code, clazz, appView::definitionFor)
+            ? computeInstanceInitializerInfo(code, clazz)
             : computeClassInitializerInfo(code, clazz));
   }
 
@@ -389,8 +388,7 @@
   // ** Assigns arguments or non-throwing constants to fields of this class.
   //
   // (Note that this initializer does not have to have zero arguments.)
-  private TrivialInitializer computeInstanceInitializerInfo(
-      IRCode code, DexClass clazz, Function<DexType, DexClass> typeToClass) {
+  private TrivialInitializer computeInstanceInitializerInfo(IRCode code, DexClass clazz) {
     if (clazz.definesFinalizer(options.itemFactory)) {
       // Defining a finalize method can observe the side-effect of Object.<init> GC registration.
       return null;
@@ -423,11 +421,12 @@
         if (invokedMethod.holder != clazz.superType) {
           return null;
         }
-        // java.lang.Object.<init>() is considered trivial.
-        if (invokedMethod == dexItemFactory.objectMethods.constructor) {
+        // java.lang.Enum.<init>() and java.lang.Object.<init>() are considered trivial.
+        if (invokedMethod == dexItemFactory.enumMethods.constructor
+            || invokedMethod == dexItemFactory.objectMethods.constructor) {
           continue;
         }
-        DexClass holder = typeToClass.apply(invokedMethod.holder);
+        DexClass holder = appView.definitionFor(invokedMethod.holder);
         if (holder == 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 9d3bb4a..91aa098 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
@@ -9,6 +9,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import java.util.function.Function;
 
 /**
@@ -20,6 +22,7 @@
  */
 public class MutableFieldOptimizationInfo extends FieldOptimizationInfo {
 
+  private AbstractValue abstractValue = UnknownValue.getInstance();
   private int readBits = 0;
   private boolean cannotBeKept = false;
   private boolean valueHasBeenPropagated = false;
@@ -45,6 +48,15 @@
   }
 
   @Override
+  public AbstractValue getAbstractValue() {
+    return abstractValue;
+  }
+
+  public void setAbstractValue(AbstractValue abstractValue) {
+    this.abstractValue = abstractValue;
+  }
+
+  @Override
   public int getReadBits() {
     return readBits;
   }
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 2a80ce4..63f1095 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
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -121,6 +122,11 @@
     getFieldOptimizationInfoForUpdating(field).joinReadBits(bitsRead);
   }
 
+  @Override
+  public void recordFieldHasAbstractValue(DexEncodedField field, AbstractValue abstractValue) {
+    getFieldOptimizationInfoForUpdating(field).setAbstractValue(abstractValue);
+  }
+
   // METHOD OPTIMIZATION INFO:
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 9b5c988..b42b588 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import java.util.BitSet;
 import java.util.Set;
@@ -45,6 +46,9 @@
   @Override
   public void markFieldBitsRead(DexEncodedField field, int bitsRead) {}
 
+  @Override
+  public void recordFieldHasAbstractValue(DexEncodedField field, AbstractValue abstractValue) {}
+
   // METHOD OPTIMIZATION INFO:
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 474a963..8e0dd11 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import java.util.BitSet;
 import java.util.Set;
@@ -55,6 +56,11 @@
     // Ignored.
   }
 
+  @Override
+  public void recordFieldHasAbstractValue(DexEncodedField field, AbstractValue abstractValue) {
+    // Ignored.
+  }
+
   // METHOD OPTIMIZATION INFO.
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 158fd9c..7625078 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -176,7 +176,7 @@
         trivialPhis.clear();
         boolean onlyHasTrivialPhis = testAndCollectPhisComposedOfThis(
             visited, thisValue.uniquePhiUsers(), thisValue, trivialPhis);
-        if (thisValue.numberOfPhiUsers() != 0 && !onlyHasTrivialPhis) {
+        if (thisValue.hasPhiUsers() && !onlyHasTrivialPhis) {
           fixableThisPointer = false;
           break;
         }
@@ -204,7 +204,7 @@
           trivialPhis.clear();
           boolean onlyHasTrivialPhis = testAndCollectPhisComposedOfSameFieldRead(
               visited, dest.uniquePhiUsers(), read.getField(), trivialPhis);
-          if (dest.numberOfPhiUsers() != 0 && !onlyHasTrivialPhis) {
+          if (dest.hasPhiUsers() && !onlyHasTrivialPhis) {
             fixableFieldReadsPerUsage = false;
             break;
           }
@@ -289,6 +289,7 @@
     Origin origin = appView.appInfo().originFor(method.method.holder);
     IRCode code = method.buildIR(appView, origin);
     codeOptimizations.forEach(codeOptimization -> codeOptimization.accept(code));
+    CodeRewriter.insertAssumeInstructions(code, converter.assumers);
     converter.collectOptimizationInfo(code, feedback);
     CodeRewriter.removeAssumeInstructions(appView, code);
     converter.finalizeIR(method, code, feedback);
@@ -387,7 +388,7 @@
     Set<Phi> trivialPhis = Sets.newIdentityHashSet();
     boolean onlyHasTrivialPhis = testAndCollectPhisComposedOfThis(
         Sets.newIdentityHashSet(), thisValue.uniquePhiUsers(), thisValue, trivialPhis);
-    assert thisValue.numberOfPhiUsers() == 0 || onlyHasTrivialPhis;
+    assert !thisValue.hasPhiUsers() || onlyHasTrivialPhis;
     assert trivialPhis.isEmpty() || onlyHasTrivialPhis;
 
     Set<Instruction> users = SetUtils.newIdentityHashSet(thisValue.aliasedUsers());
@@ -402,7 +403,7 @@
     trivialPhis.forEach(Phi::removeDeadPhi);
 
     // No matter what, number of phi users should be zero too.
-    assert thisValue.numberOfUsers() == 0 && thisValue.numberOfPhiUsers() == 0;
+    assert !thisValue.hasUsers() && !thisValue.hasPhiUsers();
   }
 
   // Re-processing finalized code may create slightly different IR code than what the examining
@@ -478,7 +479,7 @@
     Set<Phi> trivialPhis = Sets.newIdentityHashSet();
     boolean onlyHasTrivialPhis = testAndCollectPhisComposedOfSameFieldRead(
         Sets.newIdentityHashSet(), dest.uniquePhiUsers(), field, trivialPhis);
-    assert dest.numberOfPhiUsers() == 0 || onlyHasTrivialPhis;
+    assert !dest.hasPhiUsers() || onlyHasTrivialPhis;
     assert trivialPhis.isEmpty() || onlyHasTrivialPhis;
 
     Set<Instruction> users = SetUtils.newIdentityHashSet(dest.aliasedUsers());
@@ -493,7 +494,7 @@
     trivialPhis.forEach(Phi::removeDeadPhi);
 
     // No matter what, number of phi users should be zero too.
-    assert dest.numberOfUsers() == 0 && dest.numberOfPhiUsers() == 0;
+    assert !dest.hasUsers() && !dest.hasPhiUsers();
   }
 
   private void fixupStaticizedValueUsers(IRCode code, Set<Instruction> users) {
@@ -681,8 +682,12 @@
         DexEncodedMethod newMethod = method.toTypeSubstitutedMethod(
             factory().createMethod(hostType, method.method.proto, method.method.name));
         newMethods.add(newMethod);
-        staticizedMethods.add(newMethod);
-        staticizedMethods.remove(method);
+        // If the old method from the candidate class has been staticized,
+        if (staticizedMethods.remove(method)) {
+          // Properly update staticized methods to reprocess, i.e., add the corresponding one that
+          // has just been migrated to the host class.
+          staticizedMethods.add(newMethod);
+        }
         DexMethod originalMethod = methodMapping.inverse().get(method.method);
         if (originalMethod == null) {
           methodMapping.put(method.method, newMethod.method);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index 2751153..41e1264 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -320,7 +320,7 @@
 
       Value out = invoke.outValue();
       // Skip the call if the computed name is already discarded or not used anywhere.
-      if (out == null || out.numberOfAllUsers() == 0) {
+      if (out == null || !out.hasAnyUsers()) {
         continue;
       }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index 8101b6b..942cb91 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -85,7 +85,7 @@
       if (kind == InvokeKind.SUPER) {
         // This is a visibility forward, so check for the direct target.
         DexEncodedMethod targetMethod =
-            appView.appInfo().resolveMethod(target.holder, target).asSingleTarget();
+            appView.appInfo().resolveMethod(target.holder, target).getSingleTarget();
         if (targetMethod != null && targetMethod.accessFlags.isPublic()) {
           if (Log.ENABLED) {
             Log.info(
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 829c227..11cc117 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.PresortedComparable;
 import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.optimize.NestUtils;
@@ -354,7 +355,47 @@
   }
 
   private AppInfoWithLiveness(AppInfoWithLiveness previous) {
-    this(previous, previous.app(), null, null);
+    this(
+        previous,
+        previous.liveTypes,
+        previous.instantiatedAnnotationTypes,
+        previous.instantiatedAppServices,
+        previous.instantiatedTypes,
+        previous.targetedMethods,
+        previous.bootstrapMethods,
+        previous.methodsTargetedByInvokeDynamic,
+        previous.virtualMethodsTargetedByInvokeDirect,
+        previous.liveMethods,
+        previous.fieldAccessInfoCollection,
+        previous.instanceFieldsWrittenOnlyInEnclosingInstanceInitializers,
+        previous.staticFieldsWrittenOnlyInEnclosingStaticInitializer,
+        previous.virtualInvokes,
+        previous.interfaceInvokes,
+        previous.superInvokes,
+        previous.directInvokes,
+        previous.staticInvokes,
+        previous.callSites,
+        previous.brokenSuperInvokes,
+        previous.pinnedItems,
+        previous.mayHaveSideEffects,
+        previous.noSideEffects,
+        previous.assumedValues,
+        previous.alwaysInline,
+        previous.forceInline,
+        previous.neverInline,
+        previous.whyAreYouNotInlining,
+        previous.keepConstantArguments,
+        previous.keepUnusedArguments,
+        previous.neverClassInline,
+        previous.neverMerge,
+        previous.neverPropagateValue,
+        previous.identifierNameStrings,
+        previous.prunedTypes,
+        previous.switchMaps,
+        previous.enumValueInfoMaps,
+        previous.instantiatedLambdas,
+        previous.constClassReferences);
+    copyMetadataFromPrevious(previous);
   }
 
   private AppInfoWithLiveness(
@@ -904,7 +945,7 @@
 
   private DexEncodedMethod validateSingleVirtualTarget(
       DexEncodedMethod singleTarget, DexEncodedMethod resolutionResult) {
-    assert resolutionResult.isValidVirtualTarget(options());
+    assert SingleResolutionResult.isValidVirtualTarget(options(), resolutionResult);
 
     if (singleTarget == null || singleTarget == DexEncodedMethod.SENTINEL) {
       return null;
@@ -921,7 +962,7 @@
 
   private boolean isInvalidSingleVirtualTarget(
       DexEncodedMethod singleTarget, DexEncodedMethod resolutionResult) {
-    assert resolutionResult.isValidVirtualTarget(options());
+    assert SingleResolutionResult.isValidVirtualTarget(options(), resolutionResult);
     // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception
     // at runtime.
     return !singleTarget.accessFlags.isAtLeastAsVisibleAs(resolutionResult.accessFlags);
@@ -956,7 +997,7 @@
           if (refinedResolutionResult.hasSingleTarget()
               && refinedResolutionResult.isValidVirtualTargetForDynamicDispatch()) {
             return validateSingleVirtualTarget(
-                refinedResolutionResult.asSingleTarget(), resolutionResult.asSingleTarget());
+                refinedResolutionResult.getSingleTarget(), resolutionResult.getSingleTarget());
           }
         }
         return null;
@@ -998,7 +1039,7 @@
     // Now, resolve the target with the refined receiver type.
     ResolutionResult refinedResolutionResult =
         refinedReceiverIsStrictSubType ? resolveMethodOnClass(refinedHolder, method) : topMethod;
-    DexEncodedMethod topSingleTarget = refinedResolutionResult.asSingleTarget();
+    DexEncodedMethod topSingleTarget = refinedResolutionResult.getSingleTarget();
     DexClass topHolder = definitionFor(topSingleTarget.method.holder);
     // We need to know whether the top method is from an interface, as that would allow it to be
     // shadowed by a default method from an interface further down.
@@ -1012,7 +1053,7 @@
                 topSingleTarget,
                 !refinedHolder.accessFlags.isAbstract(),
                 topIsFromInterface),
-            topMethod.asSingleTarget());
+            topMethod.getSingleTarget());
     assert result != DexEncodedMethod.SENTINEL;
     method.setSingleVirtualMethodCache(refinedReceiverType, result);
     return result;
@@ -1154,7 +1195,7 @@
           if (refinedResolutionResult.hasSingleTarget()
               && refinedResolutionResult.isValidVirtualTargetForDynamicDispatch()) {
             return validateSingleVirtualTarget(
-                refinedResolutionResult.asSingleTarget(), resolutionResult.asSingleTarget());
+                refinedResolutionResult.getSingleTarget(), resolutionResult.getSingleTarget());
           }
         }
         return null;
@@ -1170,8 +1211,8 @@
     }
     // First check that there is a target for this invoke-interface to hit. If there is none,
     // this will fail at runtime.
-    DexEncodedMethod topTarget = resolveMethodOnInterface(holder, method).asSingleTarget();
-    if (topTarget == null || !topTarget.isValidVirtualTarget(options())) {
+    DexEncodedMethod topTarget = resolveMethodOnInterface(holder, method).getSingleTarget();
+    if (topTarget == null || !SingleResolutionResult.isValidVirtualTarget(options(), topTarget)) {
       return null;
     }
     // For kept types we cannot ensure a single target.
@@ -1200,7 +1241,7 @@
         // override them, so we ignore interface methods here. Otherwise, we would look up
         // default methods that are factually never used.
       } else if (!clazz.accessFlags.isAbstract()) {
-        DexEncodedMethod resolutionResult = resolveMethodOnClass(clazz, method).asSingleTarget();
+        DexEncodedMethod resolutionResult = resolveMethodOnClass(clazz, method).getSingleTarget();
         if (resolutionResult == null || isInvalidSingleVirtualTarget(resolutionResult, topTarget)) {
           // This will fail at runtime.
           return null;
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 2306b28..183151c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -39,6 +39,7 @@
 import com.android.tools.r8.graph.KeyedDexItem;
 import com.android.tools.r8.graph.PresortedComparable;
 import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoEnqueuerExtension;
 import com.android.tools.r8.ir.code.ArrayPut;
@@ -1305,7 +1306,7 @@
       reportMissingMethod(method);
       return;
     }
-    DexEncodedMethod encodedMethod = resolutionResult.asSingleTarget();
+    DexEncodedMethod encodedMethod = resolutionResult.getSingleTarget();
     if (encodedMethod == null) {
       // Note: should this be reported too? Or is this unreachable?
       return;
@@ -1927,7 +1928,8 @@
     // Otherwise, the resolution target is marked and cached, and all possible targets identified.
     resolution = findAndMarkResolutionTarget(method, interfaceInvoke, reason);
     virtualTargetsMarkedAsReachable.put(method, resolution);
-    if (resolution.isUnresolved() || !resolution.method.isValidVirtualTarget(options)) {
+    if (resolution.isUnresolved()
+        || !SingleResolutionResult.isValidVirtualTarget(options, resolution.method)) {
       // There is no valid resolution, so any call will lead to a runtime exception.
       return;
     }
@@ -1940,7 +1942,9 @@
 
     assert interfaceInvoke == holder.isInterface();
     Set<DexEncodedMethod> possibleTargets =
-        resolution.method.lookupVirtualDispatchTargets(interfaceInvoke, appInfo);
+        // TODO(b/140214802): Call on the resolution once proper resolution and lookup is resolved.
+        new SingleResolutionResult(resolution.method)
+            .lookupVirtualDispatchTargets(interfaceInvoke, appInfo);
     if (possibleTargets == null || possibleTargets.isEmpty()) {
       return;
     }
@@ -2036,56 +2040,11 @@
     // invoke. This also ensures preserving the errors detailed below.
     if (resolutionTargetClass.isProgramClass()) {
       markMethodAsTargeted(resolutionTargetClass.asProgramClass(), resolutionTarget, reason);
-
-      // If the method of an invoke-virtual instruction resolves to a private or static method, then
-      // the invoke fails with an IllegalAccessError or IncompatibleClassChangeError, respectively.
-      //
-      // Unfortunately the above is not always the case:
-      // Some Art VMs do not fail with an IllegalAccessError or IncompatibleClassChangeError if the
-      // method of an invoke-virtual instruction resolves to a private or static method, but instead
-      // ignores private and static methods during resolution (see also NonVirtualOverrideTest).
-      // Therefore, we need to continue resolution from the super type until we find a virtual
-      // method.
-      if (resolutionTarget.isPrivateMethod() || resolutionTarget.isStatic()) {
-        assert !interfaceInvoke || resolutionTargetClass.isInterface();
-        MarkedResolutionTarget possiblyValidTarget =
-            markPossiblyValidTarget(
-                method, reason, resolutionTarget, resolutionTargetClass.asProgramClass());
-        if (!possiblyValidTarget.isUnresolved()) {
-          // Since some Art runtimes may actually end up targeting this method, it is returned as
-          // the basis of lookup for the enqueuing of virtual dispatches. Not doing so may cause it
-          // to be marked abstract, thus leading to an AbstractMethodError on said Art runtimes.
-          return possiblyValidTarget;
-        }
-      }
     }
 
     return new MarkedResolutionTarget(resolutionTargetClass, resolutionTarget);
   }
 
-  private MarkedResolutionTarget markPossiblyValidTarget(
-      DexMethod method,
-      KeepReason reason,
-      DexEncodedMethod resolutionTarget,
-      DexProgramClass resolutionTargetClass) {
-    while (resolutionTarget.isPrivateMethod() || resolutionTarget.isStatic()) {
-      resolutionTarget =
-          appInfo
-              .resolveMethod(
-                  resolutionTargetClass.superType, method, resolutionTargetClass.isInterface())
-              .asResultOfResolve();
-      if (resolutionTarget == null) {
-        return MarkedResolutionTarget.unresolved();
-      }
-      resolutionTargetClass = getProgramClassOrNull(resolutionTarget.method.holder);
-      if (resolutionTargetClass == null) {
-        return MarkedResolutionTarget.unresolved();
-      }
-    }
-    markMethodAsTargeted(resolutionTargetClass, resolutionTarget, reason);
-    return new MarkedResolutionTarget(resolutionTargetClass, resolutionTarget);
-  }
-
   private DexMethod generatedEnumValuesMethod(DexClass enumClass) {
     DexType arrayOfEnumClass =
         appView
@@ -2644,11 +2603,9 @@
       if (keepClass) {
         markInstantiated(clazz, null, KeepReason.reflectiveUseIn(method));
       }
-      markFieldAsKept(clazz, encodedField, KeepReason.reflectiveUseIn(method));
-      // Fields accessed by reflection is marked as both read and written.
-      registerFieldRead(encodedField.field, method);
-      registerFieldWrite(encodedField.field, method);
-
+      if (pinnedItems.add(encodedField.field)) {
+        markFieldAsKept(clazz, encodedField, KeepReason.reflectiveUseIn(method));
+      }
     } else {
       assert identifierItem.isDexMethod();
       DexMethod targetedMethod = identifierItem.asDexMethod();
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 85da345..a9ec234 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -1283,7 +1283,7 @@
         abortMerge = true;
         return null;
       }
-      DexEncodedMethod actual = resolutionResult.asSingleTarget();
+      DexEncodedMethod actual = resolutionResult.getSingleTarget();
       if (actual != method) {
         assert actual.isVirtualMethod() == method.isVirtualMethod();
         return actual;
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 1c3af7a..14f705e 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -7,6 +7,8 @@
 import static com.android.tools.r8.utils.FileUtils.isArchive;
 import static com.android.tools.r8.utils.FileUtils.isClassFile;
 import static com.android.tools.r8.utils.FileUtils.isDexFile;
+import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
+import static com.android.tools.r8.utils.ZipUtils.writeToZipStream;
 
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.ClassFileResourceProvider;
@@ -25,28 +27,39 @@
 import com.android.tools.r8.Resource;
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.StringResource;
+import com.android.tools.r8.Version;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.shaking.FilteredClassPath;
+import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.io.ByteStreams;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
+import java.nio.file.OpenOption;
 import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import org.objectweb.asm.ClassVisitor;
 
 /**
  * Collection of program files needed for processing.
@@ -382,6 +395,154 @@
     return programResourcesMainDescriptor.get(resource);
   }
 
+  public AndroidApp dump(Path output, ProguardConfiguration configuration, Reporter reporter) {
+    int nextDexIndex = 0;
+    Builder builder = AndroidApp.builder(reporter);
+    OpenOption[] openOptions =
+        new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
+    try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(output, openOptions))) {
+      writeToZipStream(out, "r8-version", Version.getVersionString().getBytes(), ZipEntry.DEFLATED);
+      if (configuration != null) {
+        String proguardConfig = configuration.getParsedConfiguration();
+        writeToZipStream(out, "proguard.config", proguardConfig.getBytes(), ZipEntry.DEFLATED);
+      }
+      nextDexIndex = dumpProgramResources("program.jar", nextDexIndex, out, builder);
+      nextDexIndex =
+          dumpClassFileResources(
+              "classpath.jar", nextDexIndex, out, builder, classpathResourceProviders);
+      nextDexIndex =
+          dumpClassFileResources(
+              "library.jar", nextDexIndex, out, builder, libraryResourceProviders);
+    } catch (IOException | ResourceException e) {
+      reporter.fatalError(new StringDiagnostic("Failed to dump inputs"), e);
+    }
+    return builder.build();
+  }
+
+  private int dumpProgramResources(
+      String archiveName, int nextDexIndex, ZipOutputStream out, Builder builder)
+      throws IOException, ResourceException {
+    try (ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream()) {
+      try (ZipOutputStream archiveOutputStream = new ZipOutputStream(archiveByteStream)) {
+        Set<String> seen = new HashSet<>();
+        Set<DataEntryResource> dataEntries = getDataEntryResourcesForTesting();
+        for (DataEntryResource dataResource : dataEntries) {
+          builder.addDataResources(dataResource);
+          String entryName = dataResource.getName();
+          try (InputStream dataStream = dataResource.getByteStream()) {
+            byte[] bytes = ByteStreams.toByteArray(dataStream);
+            writeToZipStream(out, entryName, bytes, ZipEntry.DEFLATED);
+          }
+        }
+        for (ProgramResourceProvider provider : programResourceProviders) {
+          for (ProgramResource programResource : provider.getProgramResources()) {
+            nextDexIndex =
+                dumpProgramResource(
+                    builder, seen, nextDexIndex, archiveOutputStream, programResource);
+          }
+        }
+      }
+      writeToZipStream(out, archiveName, archiveByteStream.toByteArray(), ZipEntry.DEFLATED);
+    }
+    return nextDexIndex;
+  }
+
+  private static int dumpClassFileResources(
+      String archiveName,
+      int nextDexIndex,
+      ZipOutputStream out,
+      Builder builder,
+      ImmutableList<ClassFileResourceProvider> classpathResourceProviders)
+      throws IOException, ResourceException {
+    try (ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream()) {
+      try (ZipOutputStream archiveOutputStream = new ZipOutputStream(archiveByteStream)) {
+        Set<String> seen = new HashSet<>();
+        for (ClassFileResourceProvider provider : classpathResourceProviders) {
+          for (String descriptor : provider.getClassDescriptors()) {
+            ProgramResource programResource = provider.getProgramResource(descriptor);
+            int oldDexIndex = nextDexIndex;
+            nextDexIndex =
+                dumpProgramResource(
+                    builder, seen, nextDexIndex, archiveOutputStream, programResource);
+            assert nextDexIndex == oldDexIndex;
+          }
+        }
+      }
+      writeToZipStream(out, archiveName, archiveByteStream.toByteArray(), ZipEntry.DEFLATED);
+    }
+    return nextDexIndex;
+  }
+
+  private static int dumpProgramResource(
+      Builder builder,
+      Set<String> seen,
+      int nextDexIndex,
+      ZipOutputStream archiveOutputStream,
+      ProgramResource programResource)
+      throws ResourceException, IOException {
+    byte[] bytes = ByteStreams.toByteArray(programResource.getByteStream());
+    Set<String> classDescriptors = programResource.getClassDescriptors();
+    if (classDescriptors.size() != 1 && programResource.getKind() == Kind.CF) {
+      classDescriptors = Collections.singleton(extractClassDescriptor(bytes));
+    }
+    if (programResource instanceof OneShotByteResource) {
+      builder.addProgramResources(
+          OneShotByteResource.create(
+              programResource.getKind(), programResource.getOrigin(), bytes, classDescriptors));
+
+    } else {
+      builder.addProgramResources(programResource);
+    }
+    String entryName;
+    if (programResource.getKind() == Kind.CF) {
+      String classDescriptor =
+          classDescriptors.size() == 1
+              ? classDescriptors.iterator().next()
+              : extractClassDescriptor(bytes);
+      String classFileName = DescriptorUtils.getClassFileName(classDescriptor);
+      entryName = seen.add(classDescriptor) ? classFileName : (classFileName + ".dup");
+    } else {
+      assert programResource.getKind() == Kind.DEX;
+      entryName = "classes" + nextDexIndex++ + ".dex";
+    }
+    writeToZipStream(archiveOutputStream, entryName, bytes, ZipEntry.DEFLATED);
+    return nextDexIndex;
+  }
+
+  private static String extractClassDescriptor(byte[] bytes) {
+    class ClassNameExtractor extends ClassVisitor {
+      private String className;
+
+      private ClassNameExtractor() {
+        super(ASM_VERSION);
+      }
+
+      @Override
+      public void visit(
+          int version,
+          int access,
+          String name,
+          String signature,
+          String superName,
+          String[] interfaces) {
+        className = name;
+      }
+
+      String getDescriptor() {
+        return "L" + className + ";";
+      }
+    }
+
+    org.objectweb.asm.ClassReader reader = new org.objectweb.asm.ClassReader(bytes);
+    ClassNameExtractor extractor = new ClassNameExtractor();
+    reader.accept(
+        extractor,
+        org.objectweb.asm.ClassReader.SKIP_CODE
+            | org.objectweb.asm.ClassReader.SKIP_DEBUG
+            | org.objectweb.asm.ClassReader.SKIP_FRAMES);
+    return extractor.getDescriptor();
+  }
+
   /**
    * Builder interface for constructing an AndroidApp.
    */
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 12c444f..828e8d2 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -68,6 +68,7 @@
   // Set to true to run compilation in a single thread and without randomly shuffling the input.
   // This makes life easier when running R8 in a debugger.
   public static final boolean DETERMINISTIC_DEBUGGING = false;
+
   public enum LineNumberOptimization {
     OFF,
     ON
@@ -287,7 +288,8 @@
     Marker marker =
         new Marker(tool)
             .setVersion(Version.LABEL)
-            .setCompilationMode(debug ? CompilationMode.DEBUG : CompilationMode.RELEASE);
+            .setCompilationMode(debug ? CompilationMode.DEBUG : CompilationMode.RELEASE)
+            .setHasChecksums(encodeChecksums);
     if (!isGeneratingClassFiles()) {
       marker.setMinApi(minApiLevel);
     }
diff --git a/src/main/java/com/android/tools/r8/utils/OneShotByteResource.java b/src/main/java/com/android/tools/r8/utils/OneShotByteResource.java
index da958c3..1dfbc64 100644
--- a/src/main/java/com/android/tools/r8/utils/OneShotByteResource.java
+++ b/src/main/java/com/android/tools/r8/utils/OneShotByteResource.java
@@ -17,7 +17,7 @@
   private byte[] bytes;
   private final Set<String> classDescriptors;
 
-  static ProgramResource create(
+  public static OneShotByteResource create(
       Kind kind, Origin origin, byte[] bytes, Set<String> classDescriptors) {
     return new OneShotByteResource(origin, kind, bytes, classDescriptors);
   }
diff --git a/src/test/java/com/android/tools/r8/D8TestCompileResult.java b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
index e4ea07c..117ca7b 100644
--- a/src/test/java/com/android/tools/r8/D8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
@@ -24,7 +24,7 @@
   }
 
   @Override
-  public D8TestRunResult createRunResult(ProcessResult result) {
-    return new D8TestRunResult(app, result);
+  public D8TestRunResult createRunResult(TestRuntime runtime, ProcessResult result) {
+    return new D8TestRunResult(app, runtime, result);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/D8TestRunResult.java b/src/test/java/com/android/tools/r8/D8TestRunResult.java
index 1aa095a..cd4b663 100644
--- a/src/test/java/com/android/tools/r8/D8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/D8TestRunResult.java
@@ -9,8 +9,8 @@
 
 public class D8TestRunResult extends TestRunResult<D8TestRunResult> {
 
-  public D8TestRunResult(AndroidApp app, ProcessResult result) {
-    super(app, result);
+  public D8TestRunResult(AndroidApp app, TestRuntime runtime, ProcessResult result) {
+    super(app, runtime, result);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/DXTestCompileResult.java b/src/test/java/com/android/tools/r8/DXTestCompileResult.java
index 4313211..3cf9b73 100644
--- a/src/test/java/com/android/tools/r8/DXTestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/DXTestCompileResult.java
@@ -23,7 +23,7 @@
   }
 
   @Override
-  public DXTestRunResult createRunResult(ProcessResult result) {
-    return new DXTestRunResult(app, result);
+  public DXTestRunResult createRunResult(TestRuntime runtime, ProcessResult result) {
+    return new DXTestRunResult(app, runtime, result);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/DXTestRunResult.java b/src/test/java/com/android/tools/r8/DXTestRunResult.java
index 830cbd8..bcec2f3 100644
--- a/src/test/java/com/android/tools/r8/DXTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/DXTestRunResult.java
@@ -9,8 +9,8 @@
 
 public class DXTestRunResult extends TestRunResult<DXTestRunResult> {
 
-  public DXTestRunResult(AndroidApp app, ProcessResult result) {
-    super(app, result);
+  public DXTestRunResult(AndroidApp app, TestRuntime runtime, ProcessResult result) {
+    super(app, runtime, result);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java b/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
index 1a4e0ec..f364232 100644
--- a/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
@@ -13,8 +13,8 @@
 
 public class Dex2OatTestRunResult extends TestRunResult<Dex2OatTestRunResult> {
 
-  public Dex2OatTestRunResult(AndroidApp app, ProcessResult result) {
-    super(app, result);
+  public Dex2OatTestRunResult(AndroidApp app, TestRuntime runtime, ProcessResult result) {
+    super(app, runtime, result);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/DumpInputsTest.java b/src/test/java/com/android/tools/r8/DumpInputsTest.java
new file mode 100644
index 0000000..3c2d9b3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/DumpInputsTest.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DumpInputsTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello, world");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntime(CfVm.JDK9).build();
+  }
+
+  public DumpInputsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    Path dump = temp.newFolder().toPath().resolve("dump.zip");
+    try {
+      testForExternalR8(parameters.getBackend())
+          .useExternalJDK(parameters.getRuntime().asCf().getVm())
+          .addJvmFlag("-Dcom.android.tools.r8.dumpinputtofile=" + dump)
+          .addProgramClasses(TestClass.class)
+          .compile();
+    } catch (AssertionError e) {
+      assertTrue(Files.exists(dump));
+      Path unzipped = temp.newFolder().toPath();
+      ZipUtils.unzip(dump.toString(), unzipped.toFile());
+      assertTrue(Files.exists(unzipped.resolve("program.jar")));
+      assertTrue(Files.exists(unzipped.resolve("classpath.jar")));
+      assertTrue(Files.exists(unzipped.resolve("proguard.config")));
+      assertTrue(Files.exists(unzipped.resolve("r8-version")));
+      Set<String> entries = new HashSet<>();
+      ZipUtils.iter(
+          unzipped.resolve("program.jar").toString(),
+          (entry, input) -> entries.add(entry.getName()));
+      assertTrue(
+          entries.contains(
+              DescriptorUtils.getClassFileName(
+                  DescriptorUtils.javaTypeToDescriptor(TestClass.class.getTypeName()))));
+      return;
+    }
+    fail("Expected external compilation to exit");
+  }
+
+  static class TestClass {
+    public static void main(String[] args) {
+      System.out.println("Hello, world");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
index 0328184..ae4a36c 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
@@ -57,6 +57,8 @@
 
   private boolean addR8ExternalDeps = false;
 
+  private List<String> jvmFlags = new ArrayList<>();
+
   private ExternalR8TestBuilder(TestState state, Builder builder, Backend backend) {
     super(state, builder, backend);
   }
@@ -72,7 +74,7 @@
 
   public ExternalR8TestBuilder useExternalJDK(CfVm externalJDK) {
     this.externalJDK = externalJDK;
-    return this;
+    return self();
   }
 
   private String getJDKToRun() {
@@ -82,6 +84,11 @@
     return getJavaExecutable(externalJDK);
   }
 
+  public ExternalR8TestBuilder addJvmFlag(String flag) {
+    jvmFlags.add(flag);
+    return self();
+  }
+
   @Override
   ExternalR8TestCompileResult internalCompile(
       Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
@@ -99,9 +106,12 @@
               : r8jar.toAbsolutePath().toString();
 
       List<String> command = new ArrayList<>();
+      Collections.addAll(command, getJDKToRun());
+
+      command.addAll(jvmFlags);
+
       Collections.addAll(
           command,
-          getJDKToRun(),
           "-ea",
           "-cp",
           classPath,
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java b/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java
index 290f6ac..04a37fa 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java
@@ -63,7 +63,7 @@
   }
 
   @Override
-  protected ExternalR8TestRunResult createRunResult(ProcessResult result) {
-    return new ExternalR8TestRunResult(app, outputJar, proguardMap, result);
+  protected ExternalR8TestRunResult createRunResult(TestRuntime runtime, ProcessResult result) {
+    return new ExternalR8TestRunResult(app, outputJar, proguardMap, runtime, result);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestRunResult.java b/src/test/java/com/android/tools/r8/ExternalR8TestRunResult.java
index f301b78..28e2835 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestRunResult.java
@@ -19,8 +19,12 @@
   private final String proguardMap;
 
   public ExternalR8TestRunResult(
-      AndroidApp app, Path outputJar, String proguardMap, ProcessResult result) {
-    super(app, result);
+      AndroidApp app,
+      Path outputJar,
+      String proguardMap,
+      TestRuntime runtime,
+      ProcessResult result) {
+    super(app, runtime, result);
     this.outputJar = outputJar;
     this.proguardMap = proguardMap;
   }
diff --git a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
index 948ab96..008ff1f 100644
--- a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
@@ -4,32 +4,57 @@
 package com.android.tools.r8;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.utils.BooleanUtils;
 import java.nio.file.Paths;
 import java.util.Collection;
 import java.util.Set;
+import org.junit.Assume;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
-public class ExtractMarkerTest {
+@RunWith(Parameterized.class)
+public class ExtractMarkerTest extends TestBase {
   private static final String CLASS_FILE =
       ToolHelper.EXAMPLES_BUILD_DIR + "classes/trivial/Trivial.class";
 
-  private static void verifyMarker(Marker marker, Tool tool) {
+  private final TestParameters parameters;
+  private boolean includeClassesChecksum;
+
+  @Parameterized.Parameters(name = "{0}, includeClassesChecksum: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+  }
+
+  public ExtractMarkerTest(TestParameters parameters, boolean includeClassesChecksum) {
+    this.parameters = parameters;
+    this.includeClassesChecksum = includeClassesChecksum;
+  }
+
+  private void verifyMarkerDex(Marker marker, Tool tool) {
     assertEquals(tool, marker.getTool());
     assertEquals(Version.LABEL, marker.getVersion());
     assertEquals(CompilationMode.DEBUG.toString().toLowerCase(), marker.getCompilationMode());
+    assertEquals(parameters.getApiLevel().getLevel(), marker.getMinApi().intValue());
+    assertEquals(includeClassesChecksum, marker.getHasChecksums());
   }
 
   @Test
   public void extractMarkerTestDex() throws CompilationFailedException {
-    boolean[] testExecuted = {false};
+    Assume.assumeTrue(parameters.getRuntime().isDex());
 
+    boolean[] testExecuted = {false};
     D8.run(
         D8Command.builder()
             .addProgramFiles(Paths.get(CLASS_FILE))
+            .setMinApiLevel(parameters.getApiLevel().getLevel())
+            .setIncludeClassesChecksum(includeClassesChecksum)
             .setProgramConsumer(
                 new DexIndexedConsumer.ForwardingConsumer(null) {
                   @Override
@@ -47,7 +72,7 @@
                     } catch (Exception e) {
                       throw new RuntimeException(e);
                     }
-                    verifyMarker(marker, Tool.D8);
+                    verifyMarkerDex(marker, Tool.D8);
                     testExecuted[0] = true;
                   }
                 })
@@ -55,8 +80,18 @@
     assertTrue(testExecuted[0]);
   }
 
+  private static void verifyMarkerCf(Marker marker, Tool tool) {
+    assertEquals(tool, marker.getTool());
+    assertEquals(Version.LABEL, marker.getVersion());
+    assertEquals(CompilationMode.DEBUG.toString().toLowerCase(), marker.getCompilationMode());
+    assertFalse(marker.getHasChecksums());
+  }
+
   @Test
   public void extractMarkerTestCf() throws CompilationFailedException {
+    Assume.assumeTrue(parameters.getRuntime().isCf());
+    Assume.assumeFalse(includeClassesChecksum);
+
     boolean[] testExecuted = {false};
     R8.run(
         R8Command.builder()
@@ -78,7 +113,7 @@
                     } catch (Exception e) {
                       throw new RuntimeException(e);
                     }
-                    verifyMarker(marker, Tool.R8);
+                    verifyMarkerCf(marker, Tool.R8);
                     testExecuted[0] = true;
                   }
                 })
diff --git a/src/test/java/com/android/tools/r8/GenerateMainDexListRunResult.java b/src/test/java/com/android/tools/r8/GenerateMainDexListRunResult.java
index b9842f2..166feeb 100644
--- a/src/test/java/com/android/tools/r8/GenerateMainDexListRunResult.java
+++ b/src/test/java/com/android/tools/r8/GenerateMainDexListRunResult.java
@@ -11,7 +11,7 @@
   List<String> mainDexList;
 
   public GenerateMainDexListRunResult(List<String> mainDexList) {
-    super(null, null);
+    super(null, null, null);
     this.mainDexList = mainDexList;
   }
 
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index c6a5026..06f97ce 100644
--- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -39,9 +39,9 @@
   }
 
   @Override
+  @Deprecated
   public JvmTestRunResult run(String mainClass) throws IOException {
-    ProcessResult result = ToolHelper.runJava(classpath, mainClass);
-    return new JvmTestRunResult(builder.build(), result);
+    return run(TestRuntime.getDefaultJavaRuntime(), mainClass);
   }
 
   public JvmTestRunResult run(TestRuntime runtime, String mainClass, String... args)
@@ -49,7 +49,7 @@
     assert runtime.isCf();
     ProcessResult result =
         ToolHelper.runJava(runtime.asCf().getVm(), classpath, ObjectArrays.concat(mainClass, args));
-    return new JvmTestRunResult(builder.build(), result);
+    return new JvmTestRunResult(builder.build(), runtime, result);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/JvmTestRunResult.java b/src/test/java/com/android/tools/r8/JvmTestRunResult.java
index b7a71fd..a94fd38 100644
--- a/src/test/java/com/android/tools/r8/JvmTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/JvmTestRunResult.java
@@ -9,8 +9,8 @@
 
 public class JvmTestRunResult extends TestRunResult<JvmTestRunResult> {
 
-  public JvmTestRunResult(AndroidApp app, ProcessResult result) {
-    super(app, result);
+  public JvmTestRunResult(AndroidApp app, TestRuntime runtime, ProcessResult result) {
+    super(app, runtime, result);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java b/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
index 0ed5425..fbb8282 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
@@ -42,7 +42,7 @@
   }
 
   @Override
-  public ProguardTestRunResult createRunResult(ProcessResult result) {
-    return new ProguardTestRunResult(app, result, proguardMap);
+  public ProguardTestRunResult createRunResult(TestRuntime runtime, ProcessResult result) {
+    return new ProguardTestRunResult(app, runtime, result, proguardMap);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ProguardTestRunResult.java b/src/test/java/com/android/tools/r8/ProguardTestRunResult.java
index e242a38..5cfce33 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestRunResult.java
@@ -7,7 +7,9 @@
 import static org.junit.Assert.assertNotNull;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
@@ -16,8 +18,9 @@
 
   private final String proguardMap;
 
-  public ProguardTestRunResult(AndroidApp app, ProcessResult result, String proguardMap) {
-    super(app, result);
+  public ProguardTestRunResult(
+      AndroidApp app, TestRuntime runtime, ProcessResult result, String proguardMap) {
+    super(app, runtime, result);
     this.proguardMap = proguardMap;
   }
 
@@ -27,10 +30,25 @@
   }
 
   @Override
+  public StackTrace getStackTrace() {
+    return super.getStackTrace().retrace(proguardMap);
+  }
+
+  public StackTrace getOriginalStackTrace() {
+    return super.getStackTrace();
+  }
+
+  @Override
   public CodeInspector inspector() throws IOException, ExecutionException {
     // See comment in base class.
     assertSuccess();
     assertNotNull(app);
     return new CodeInspector(app, proguardMap);
   }
+
+  public <E extends Throwable> ProguardTestRunResult inspectOriginalStackTrace(
+      ThrowingConsumer<StackTrace, E> consumer) throws E {
+    consumer.accept(getOriginalStackTrace());
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8EntryPointTests.java b/src/test/java/com/android/tools/r8/R8EntryPointTests.java
index 553ff0e..0bbc7bc 100644
--- a/src/test/java/com/android/tools/r8/R8EntryPointTests.java
+++ b/src/test/java/com/android/tools/r8/R8EntryPointTests.java
@@ -9,20 +9,13 @@
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.ZipUtils;
-import com.android.tools.r8.utils.ZipUtils.OnEntryHandler;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
-import java.io.InputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
-import java.util.zip.ZipEntry;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -180,53 +173,6 @@
   }
 
   @Test
-  public void testDumpInputs() throws IOException {
-    Path out = temp.newFile("dex.zip").toPath();
-    Path dump = temp.newFile("dump.zip").toPath();
-    ProcessResult r8 =
-        ToolHelper.forkR8WithJavaOptions(
-            Paths.get("."),
-            ImmutableList.of("-Dcom.android.tools.r8.dumpinputtofile=" + dump.toString()),
-            "--lib",
-            ToolHelper.getDefaultAndroidJar().toString(),
-            "--dex",
-            "--output",
-            out.toString(),
-            "--pg-conf",
-            PROGUARD_FLAGS.toString(),
-            "--pg-conf",
-            testFlags.toString(),
-            INPUT_JAR.toString());
-
-    List<ZipEntry> entries = new ArrayList<>();
-    ZipUtils.iter(
-        dump.toString(),
-        new OnEntryHandler() {
-          @Override
-          public void onEntry(ZipEntry entry, InputStream input) throws IOException {
-            entries.add(entry);
-          }
-        });
-    Assert.assertTrue(hasEntry(entries, "program.jar"));
-    Assert.assertTrue(hasEntry(entries, "library.jar"));
-    Assert.assertTrue(hasEntry(entries, "classpath.jar"));
-    Assert.assertTrue(hasEntry(entries, "proguard.config"));
-    Assert.assertTrue(hasEntry(entries, "r8-version"));
-    // When dumping the inputs we throw an error in the program to exit early.
-    Assert.assertNotEquals(0, r8.exitCode);
-  }
-
-  private boolean hasEntry(Collection<ZipEntry> entries, String name) {
-    for (ZipEntry entry : entries) {
-      if (entry.getName().equals(name)) {
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  @Test
   public void testSpecifyDexAndClassfileNotAllowed() throws IOException, InterruptedException {
     Path out = temp.newFile("dex.zip").toPath();
     ProcessResult r8 =
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index 8e63f34..c4ec2aa 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -83,8 +83,8 @@
   }
 
   @Override
-  public R8TestRunResult createRunResult(ProcessResult result) {
-    return new R8TestRunResult(app, result, proguardMap, this::graphInspector);
+  public R8TestRunResult createRunResult(TestRuntime runtime, ProcessResult result) {
+    return new R8TestRunResult(app, runtime, result, proguardMap, this::graphInspector);
   }
 
   public String getProguardMap() {
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 835c959..415abb1 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -7,10 +7,12 @@
 import static org.junit.Assert.assertNotNull;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.retrace.Retrace;
 import com.android.tools.r8.retrace.RetraceCommand;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.graphinspector.GraphInspector;
 import java.io.IOException;
@@ -29,10 +31,11 @@
 
   public R8TestRunResult(
       AndroidApp app,
+      TestRuntime runtime,
       ProcessResult result,
       String proguardMap,
       GraphInspectorSupplier graphInspector) {
-    super(app, result);
+    super(app, runtime, result);
     this.proguardMap = proguardMap;
     this.graphInspector = graphInspector;
   }
@@ -43,6 +46,15 @@
   }
 
   @Override
+  public StackTrace getStackTrace() {
+    return super.getStackTrace().retrace(proguardMap);
+  }
+
+  public StackTrace getOriginalStackTrace() {
+    return super.getStackTrace();
+  }
+
+  @Override
   public CodeInspector inspector() throws IOException, ExecutionException {
     // See comment in base class.
     assertSuccess();
@@ -50,6 +62,12 @@
     return new CodeInspector(app, proguardMap);
   }
 
+  public <E extends Throwable> R8TestRunResult inspectOriginalStackTrace(
+      ThrowingConsumer<StackTrace, E> consumer) throws E {
+    consumer.accept(getOriginalStackTrace());
+    return self();
+  }
+
   public GraphInspector graphInspector() throws IOException, ExecutionException {
     assertSuccess();
     return graphInspector.get();
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 4c6f535..8c643ff 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.TestRuntime.DexRuntime;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -81,7 +82,7 @@
     return outputMode;
   }
 
-  protected abstract RR createRunResult(ProcessResult result);
+  protected abstract RR createRunResult(TestRuntime runtime, ProcessResult result);
 
   @Deprecated
   public RR run(Class<?> mainClass) throws ExecutionException, IOException {
@@ -94,7 +95,10 @@
     assertThat(mainClassSubject, isPresent());
     switch (getBackend()) {
       case DEX:
-        return runArt(null, additionalRunClassPath, mainClassSubject.getFinalName());
+        return runArt(
+            new DexRuntime(ToolHelper.getDexVm()),
+            additionalRunClassPath,
+            mainClassSubject.getFinalName());
       case CF:
         return runJava(
             TestRuntime.getDefaultJavaRuntime(),
@@ -124,8 +128,7 @@
     ClassSubject mainClassSubject = inspector().clazz(mainClass);
     assertThat("Did you forget a keep rule for the main method?", mainClassSubject, isPresent());
     if (runtime.isDex()) {
-      return runArt(
-          runtime.asDex().getVm(), additionalRunClassPath, mainClassSubject.getFinalName(), args);
+      return runArt(runtime, additionalRunClassPath, mainClassSubject.getFinalName(), args);
     }
     assert runtime.isCf();
     return runJava(
@@ -293,11 +296,13 @@
         .build();
     ProcessResult result =
         ToolHelper.runJava(runtime.asCf().getVm(), vmArguments, classPath, arguments);
-    return createRunResult(result);
+    return createRunResult(runtime, result);
   }
 
-  private RR runArt(DexVm vm, List<Path> additionalClassPath, String mainClass, String... arguments)
+  private RR runArt(
+      TestRuntime runtime, List<Path> additionalClassPath, String mainClass, String... arguments)
       throws IOException {
+    DexVm vm = runtime.asDex().getVm();
     // TODO(b/127785410): Always assume a non-null runtime.
     Path out = state.getNewTempFolder().resolve("out.zip");
     app.writeToZip(out, OutputMode.DexIndexed);
@@ -312,24 +317,16 @@
     ProcessResult result =
         ToolHelper.runArtRaw(
             classPath, mainClass, commandConsumer, vm, withArtFrameworks, arguments);
-    return createRunResult(result);
-  }
-
-  @Deprecated
-  public Dex2OatTestRunResult runDex2Oat() throws IOException {
-    return runDex2Oat(ToolHelper.getDexVm());
+    return createRunResult(runtime, result);
   }
 
   public Dex2OatTestRunResult runDex2Oat(TestRuntime runtime) throws IOException {
-    return runDex2Oat(runtime.asDex().getVm());
-  }
-
-  public Dex2OatTestRunResult runDex2Oat(DexVm vm) throws IOException {
     assert getBackend() == DEX;
+    DexVm vm = runtime.asDex().getVm();
     Path tmp = state.getNewTempFolder();
     Path jarFile = tmp.resolve("out.jar");
     Path oatFile = tmp.resolve("out.oat");
     app.writeToZip(jarFile, OutputMode.DexIndexed);
-    return new Dex2OatTestRunResult(app, ToolHelper.runDex2OatRaw(jarFile, oatFile, vm));
+    return new Dex2OatTestRunResult(app, runtime, ToolHelper.runDex2OatRaw(jarFile, oatFile, vm));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index 4dd10a1..8e47b69 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -9,8 +9,10 @@
 import static org.junit.Assert.assertNotNull;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
 import java.io.PrintStream;
@@ -21,10 +23,12 @@
 
 public abstract class TestRunResult<RR extends TestRunResult<?>> {
   protected final AndroidApp app;
+  private final TestRuntime runtime;
   private final ProcessResult result;
 
-  public TestRunResult(AndroidApp app, ProcessResult result) {
+  public TestRunResult(AndroidApp app, TestRuntime runtime, ProcessResult result) {
     this.app = app;
+    this.runtime = runtime;
     this.result = result;
   }
 
@@ -51,6 +55,14 @@
     return result.stderr;
   }
 
+  public StackTrace getStackTrace() {
+    if (runtime.isDex()) {
+      return StackTrace.extractFromArt(getStdErr(), runtime.asDex().getVm());
+    } else {
+      return StackTrace.extractFromJvm(getStdErr());
+    }
+  }
+
   public int getExitCode() {
     return result.exitCode;
   }
@@ -113,6 +125,12 @@
     return self();
   }
 
+  public <E extends Throwable> RR inspectStackTrace(ThrowingConsumer<StackTrace, E> consumer)
+      throws E {
+    consumer.accept(getStackTrace());
+    return self();
+  }
+
   public RR disassemble(PrintStream ps) throws IOException, ExecutionException {
     ToolHelper.disassemble(app, ps);
     return self();
diff --git a/src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java b/src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java
index 93c0323..866dcf5 100644
--- a/src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java
@@ -71,7 +71,7 @@
         new ApplicationReader(
                 app, options, new Timing("D8FrameworkDexPassthroughMarkerTest"))
             .read();
-    Collection<Marker> markers = dexApp.dexItemFactory.extractMarker();
+    Collection<Marker> markers = dexApp.dexItemFactory.extractMarkers();
     assertEquals(1, markers.size());
     Marker readMarker = markers.iterator().next();
     assertEquals(marker, readMarker);
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTest.java
index 6bbf8a1..8fadf97 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTest.java
@@ -5,14 +5,19 @@
 
 import static org.hamcrest.CoreMatchers.endsWith;
 import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assert.assertFalse;
 
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.Diagnostic;
 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.desugar.corelib.CoreLibDesugarTestBase;
+import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Random;
 import java.util.function.IntUnaryOperator;
 import java.util.stream.IntStream;
@@ -60,6 +65,7 @@
         .setMinApi(parameters.getApiLevel())
         .enableCoreLibraryDesugaring(parameters.getApiLevel())
         .compile()
+        .assertOnlyInfos() // No warnings.
         .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
         .run(parameters.getRuntime(), Executor.class)
         .assertSuccessWithOutput(
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/EnumSetTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/shrinkingtests/EnumSetTest.java
similarity index 94%
rename from src/test/java/com/android/tools/r8/desugar/corelib/EnumSetTest.java
rename to src/test/java/com/android/tools/r8/desugar/corelib/shrinkingtests/EnumSetTest.java
index d38ac77..db9206f 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/EnumSetTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/shrinkingtests/EnumSetTest.java
@@ -2,9 +2,10 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.desugar.corelib;
+package com.android.tools.r8.desugar.corelib.shrinkingtests;
 
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.corelib.CoreLibDesugarTestBase;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.time.DayOfWeek;
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/EnumSetTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/shrinkingtests/FieldAccessTest.java
similarity index 62%
copy from src/test/java/com/android/tools/r8/desugar/corelib/EnumSetTest.java
copy to src/test/java/com/android/tools/r8/desugar/corelib/shrinkingtests/FieldAccessTest.java
index d38ac77..29d1554 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/EnumSetTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/shrinkingtests/FieldAccessTest.java
@@ -2,15 +2,13 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.desugar.corelib;
+package com.android.tools.r8.desugar.corelib.shrinkingtests;
 
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.corelib.CoreLibDesugarTestBase;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
-import java.time.DayOfWeek;
-import java.time.chrono.IsoEra;
-import java.time.chrono.MinguoEra;
-import java.util.EnumSet;
+import java.time.ZoneId;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -18,7 +16,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class EnumSetTest extends CoreLibDesugarTestBase {
+public class FieldAccessTest extends CoreLibDesugarTestBase {
 
   private final TestParameters parameters;
   private final boolean shrinkDesugaredLibrary;
@@ -29,16 +27,16 @@
         BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
   }
 
-  public EnumSetTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+  public FieldAccessTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
     this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
     this.parameters = parameters;
   }
 
   @Test
-  public void testEnum() throws Exception {
+  public void testField() throws Exception {
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
-        .addProgramClasses(EnumSetUser.class)
+        .addProgramClasses(Executor.class)
         .setMinApi(parameters.getApiLevel())
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
@@ -47,19 +45,14 @@
             parameters.getApiLevel(),
             keepRuleConsumer.get(),
             shrinkDesugaredLibrary)
-        .run(parameters.getRuntime(), EnumSetUser.class)
-        .assertSuccessWithOutput(StringUtils.lines("2", "0", "1"));
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(StringUtils.lines("Australia/Darwin"));
   }
 
-  static class EnumSetUser {
+  static class Executor {
 
     public static void main(String[] args) {
-      EnumSet<IsoEra> isoEras = EnumSet.allOf(IsoEra.class);
-      System.out.println(isoEras.size());
-      EnumSet<DayOfWeek> dayOfWeeks = EnumSet.noneOf(DayOfWeek.class);
-      System.out.println(dayOfWeeks.size());
-      EnumSet<MinguoEra> minguoEras = EnumSet.of(MinguoEra.BEFORE_ROC);
-      System.out.println(minguoEras.size());
+      System.out.println(ZoneId.SHORT_IDS.get("ACT"));
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/EnumSetTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/shrinkingtests/InheritanceTest.java
similarity index 61%
copy from src/test/java/com/android/tools/r8/desugar/corelib/EnumSetTest.java
copy to src/test/java/com/android/tools/r8/desugar/corelib/shrinkingtests/InheritanceTest.java
index d38ac77..24b6b6e 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/EnumSetTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/shrinkingtests/InheritanceTest.java
@@ -2,15 +2,15 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.desugar.corelib;
+package com.android.tools.r8.desugar.corelib.shrinkingtests;
 
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.corelib.CoreLibDesugarTestBase;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
-import java.time.DayOfWeek;
-import java.time.chrono.IsoEra;
-import java.time.chrono.MinguoEra;
-import java.util.EnumSet;
+import java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneId;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -18,7 +18,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class EnumSetTest extends CoreLibDesugarTestBase {
+public class InheritanceTest extends CoreLibDesugarTestBase {
 
   private final TestParameters parameters;
   private final boolean shrinkDesugaredLibrary;
@@ -29,16 +29,16 @@
         BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
   }
 
-  public EnumSetTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+  public InheritanceTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
     this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
     this.parameters = parameters;
   }
 
   @Test
-  public void testEnum() throws Exception {
+  public void testInheritance() throws Exception {
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
-        .addProgramClasses(EnumSetUser.class)
+        .addProgramClasses(Impl.class)
         .setMinApi(parameters.getApiLevel())
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
@@ -47,19 +47,30 @@
             parameters.getApiLevel(),
             keepRuleConsumer.get(),
             shrinkDesugaredLibrary)
-        .run(parameters.getRuntime(), EnumSetUser.class)
-        .assertSuccessWithOutput(StringUtils.lines("2", "0", "1"));
+        .run(parameters.getRuntime(), Impl.class)
+        .assertSuccessWithOutput(StringUtils.lines("123456"));
   }
 
-  static class EnumSetUser {
+  static class Impl extends Clock {
 
     public static void main(String[] args) {
-      EnumSet<IsoEra> isoEras = EnumSet.allOf(IsoEra.class);
-      System.out.println(isoEras.size());
-      EnumSet<DayOfWeek> dayOfWeeks = EnumSet.noneOf(DayOfWeek.class);
-      System.out.println(dayOfWeeks.size());
-      EnumSet<MinguoEra> minguoEras = EnumSet.of(MinguoEra.BEFORE_ROC);
-      System.out.println(minguoEras.size());
+      Impl impl = new Impl();
+      System.out.println(impl.millis());
+    }
+
+    @Override
+    public ZoneId getZone() {
+      return null;
+    }
+
+    @Override
+    public Clock withZone(ZoneId zone) {
+      return null;
+    }
+
+    @Override
+    public Instant instant() {
+      return Instant.ofEpochMilli(123456);
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/KeepRuleShrinkTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/shrinkingtests/KeepRuleShrinkTest.java
similarity index 94%
rename from src/test/java/com/android/tools/r8/desugar/corelib/KeepRuleShrinkTest.java
rename to src/test/java/com/android/tools/r8/desugar/corelib/shrinkingtests/KeepRuleShrinkTest.java
index 843e154..b20abee 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/KeepRuleShrinkTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/shrinkingtests/KeepRuleShrinkTest.java
@@ -2,17 +2,17 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.desugar.corelib;
+package com.android.tools.r8.desugar.corelib.shrinkingtests;
 
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.corelib.CoreLibDesugarTestBase;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ConcurrentSkipListMap;
-import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -37,7 +37,6 @@
 
   @Test
   public void testMapProblem() throws Exception {
-    Assume.assumeFalse("TODO(b/134732760): Fix shrinking of core library", shrinkDesugaredLibrary);
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     D8TestRunResult d8TestRunResult =
         testForD8()
diff --git a/src/test/java/com/android/tools/r8/dexfilemerger/DexMergeChecksumsHighSortingStrings.java b/src/test/java/com/android/tools/r8/dexfilemerger/DexMergeChecksumsHighSortingStrings.java
new file mode 100644
index 0000000..e6a90ff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dexfilemerger/DexMergeChecksumsHighSortingStrings.java
@@ -0,0 +1,127 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.dexfilemerger;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.ClassesChecksum;
+import com.android.tools.r8.graph.DexItemFactory;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DexMergeChecksumsHighSortingStrings extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public DexMergeChecksumsHighSortingStrings(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testChecksumWhitHighSortingStrings() throws Exception {
+    Path dexArchive1 =
+        testForD8()
+            .addProgramClasses(TestClass1.class)
+            .setMinApi(parameters.getApiLevel())
+            .setMode(CompilationMode.DEBUG)
+            .setIncludeClassesChecksum(true)
+            .compile()
+            .writeToZip();
+
+    Path dexArchive2 =
+        testForD8()
+            .addProgramClasses(TestClass2.class)
+            .setMinApi(parameters.getApiLevel())
+            .setMode(CompilationMode.DEBUG)
+            .setIncludeClassesChecksum(true)
+            .compile()
+            .writeToZip();
+
+    testForD8()
+        .setMinApi(parameters.getApiLevel())
+        .addProgramFiles(dexArchive1)
+        .setIncludeClassesChecksum(true)
+        .run(parameters.getRuntime(), TestClass1.class)
+        .assertSuccessWithOutputLines("Hello, \uDB3F\uDFFD");
+
+    testForD8()
+        .setMinApi(parameters.getApiLevel())
+        .addProgramFiles(dexArchive2)
+        .setIncludeClassesChecksum(true)
+        .run(parameters.getRuntime(), TestClass2.class)
+        .assertSuccessWithOutputLines("Hello, ~~~\u007f");
+
+    testForD8()
+        .setMinApi(parameters.getApiLevel())
+        .addProgramFiles(dexArchive1, dexArchive2)
+        .setIncludeClassesChecksum(true)
+        .run(parameters.getRuntime(), TestClass2.class)
+        .assertSuccessWithOutputLines("Hello, ~~~\u007f");
+  }
+
+  static class TestClass1 {
+    // U+DFFFD which is very end of unassigned plane.
+    private static final String STRING_SORTING_ABOVE_CHECKSUM_STRING = "\uDB3F\uDFFD";
+
+    public static void main(String[] args) {
+      System.out.println("Hello, " + STRING_SORTING_ABOVE_CHECKSUM_STRING);
+    }
+  }
+
+  static class TestClass2 {
+    private static final String STRING_SORTING_ABOVE_CHECKSUM_STRING = "~~~\u007f";
+
+    public static void main(String[] args) {
+      System.out.println("Hello, " + STRING_SORTING_ABOVE_CHECKSUM_STRING);
+    }
+  }
+
+  @Test
+  public void testChecksumMarkerComparison() {
+    // z     {     |     }     ~     <DEL>
+    // 0x7a  0x7b  0x7c  0x7d  0x7e  0x7f
+    assertTrue('{' - 1 == 'z');
+    assertTrue('{' + 1 == '|');
+    assertTrue('~' - 1 == '}');
+
+    DexItemFactory factory = new DexItemFactory();
+    assertFalse(
+        ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("\uDB3F\uDFFD")));
+    assertFalse(
+        ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~\uDB3F\uDFFD\"")));
+    assertFalse(
+        ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~~\uDB3F\uDFFD")));
+    assertFalse(
+        ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~~~\uDB3F\uDFFD")));
+    assertFalse(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("\u007f")));
+    assertFalse(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~\u007f")));
+    assertFalse(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~~\u007f")));
+    assertFalse(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~~~\u007f")));
+    assertFalse(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~~~|")));
+
+    assertTrue(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~~}")));
+    assertTrue(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~~")));
+    assertTrue(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~}")));
+    assertTrue(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~")));
+    assertTrue(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("}")));
+
+    // False negatives.
+    assertFalse(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~~~")));
+    assertFalse(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~~~z")));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/ArrayOfObjectsCreationCanBePostponedTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/ArrayOfObjectsCreationCanBePostponedTest.java
new file mode 100644
index 0000000..065e069
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/ArrayOfObjectsCreationCanBePostponedTest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.sideeffect;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.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;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ArrayOfObjectsCreationCanBePostponedTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ArrayOfObjectsCreationCanBePostponedTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ArrayOfObjectsCreationCanBePostponedTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(A.class);
+    assertThat(classSubject, isPresent());
+
+    // We should have been able to inline A.inlineable() since A.<clinit>() can safely be postponed.
+    assertThat(classSubject.uniqueMethodWithName("inlineable"), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      A.inlineable();
+      System.out.println(A.values[0].getMessage());
+    }
+  }
+
+  static class A {
+
+    static A[] values = new A[] {new A(" world!")};
+
+    String message;
+
+    A(String message) {
+      this.message = message;
+    }
+
+    static void inlineable() {
+      System.out.print("Hello");
+    }
+
+    String getMessage() {
+      return message;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java
index fd6ada0..dd0b5e2 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java
@@ -29,7 +29,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
   }
 
   public ConstrainedPrimitiveTypeTest(TestParameters parameters) throws Exception {
@@ -47,6 +47,7 @@
         .addInnerClasses(ConstrainedPrimitiveTypeTest.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(expectedOutput);
   }
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java b/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
index 2cde812..b35254e 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
@@ -51,20 +51,20 @@
 
     Set<Node> wave = Sets.newIdentityHashSet();
 
-    MethodProcessor.extractLeaves(nodes, wave::add);
+    PrimaryMethodProcessor.extractLeaves(nodes, wave::add);
     assertEquals(3, wave.size());
     assertThat(wave, hasItem(n3));
     assertThat(wave, hasItem(n4));
     assertThat(wave, hasItem(n6));
     wave.clear();
 
-    MethodProcessor.extractLeaves(nodes, wave::add);
+    PrimaryMethodProcessor.extractLeaves(nodes, wave::add);
     assertEquals(2, wave.size());
     assertThat(wave, hasItem(n2));
     assertThat(wave, hasItem(n5));
     wave.clear();
 
-    MethodProcessor.extractLeaves(nodes, wave::add);
+    PrimaryMethodProcessor.extractLeaves(nodes, wave::add);
     assertEquals(1, wave.size());
     assertThat(wave, hasItem(n1));
     assertTrue(nodes.isEmpty());
@@ -103,20 +103,20 @@
 
     Set<Node> wave = Sets.newIdentityHashSet();
 
-    MethodProcessor.extractLeaves(nodes, wave::add);
+    PrimaryMethodProcessor.extractLeaves(nodes, wave::add);
     assertEquals(3, wave.size());
     assertThat(wave, hasItem(n3));
     assertThat(wave, hasItem(n4));
     assertThat(wave, hasItem(n6));
     wave.clear();
 
-    MethodProcessor.extractLeaves(nodes, wave::add);
+    PrimaryMethodProcessor.extractLeaves(nodes, wave::add);
     assertEquals(2, wave.size());
     assertThat(wave, hasItem(n2));
     assertThat(wave, hasItem(n5));
     wave.clear();
 
-    MethodProcessor.extractLeaves(nodes, wave::add);
+    PrimaryMethodProcessor.extractLeaves(nodes, wave::add);
     assertEquals(1, wave.size());
     assertThat(wave, hasItem(n1));
     assertTrue(nodes.isEmpty());
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
index 769515c..880139f 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
@@ -83,20 +83,20 @@
 
     Set<Node> wave = Sets.newIdentityHashSet();
 
-    MethodProcessor.extractLeaves(cg.nodes, wave::add);
+    PrimaryMethodProcessor.extractLeaves(cg.nodes, wave::add);
     assertEquals(4, wave.size()); // including <init>
     assertThat(wave, hasItem(m3));
     assertThat(wave, hasItem(m4));
     assertThat(wave, hasItem(m6));
     wave.clear();
 
-    MethodProcessor.extractLeaves(cg.nodes, wave::add);
+    PrimaryMethodProcessor.extractLeaves(cg.nodes, wave::add);
     assertEquals(2, wave.size());
     assertThat(wave, hasItem(m2));
     assertThat(wave, hasItem(m5));
     wave.clear();
 
-    MethodProcessor.extractLeaves(cg.nodes, wave::add);
+    PrimaryMethodProcessor.extractLeaves(cg.nodes, wave::add);
     assertEquals(1, wave.size());
     assertThat(wave, hasItem(m1));
     assertTrue(cg.nodes.isEmpty());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/DefaultInterfaceIssue143628636Test.java b/src/test/java/com/android/tools/r8/ir/optimize/DefaultInterfaceIssue143628636Test.java
new file mode 100644
index 0000000..945e1af
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/DefaultInterfaceIssue143628636Test.java
@@ -0,0 +1,133 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize;
+
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * Reproduction for issue b/143628636.
+ *
+ * <p>There are a lot of conditions that need to be in place to trigger this issue. The root issue
+ * is resolution incorrectly pruning out a valid candidate. However, mostly code will not even get
+ * to that place as a single target is typically found prior to it, and then, if the incorrect
+ * pruning of the lookup targets takes place, that needs to further cause invalid call site
+ * information to be propagated, which in turn will cause inlining to inline a method where it
+ * should not. It is exceedingly unlikely that this test will continue to be a regression test as
+ * any one of the above aspects could and likely will be changed in the code base.
+ */
+@RunWith(Parameterized.class)
+public class DefaultInterfaceIssue143628636Test extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public DefaultInterfaceIssue143628636Test(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .enableInliningAnnotations()
+        .enableClassInliningAnnotations()
+        .enableMergeAnnotations()
+        .addInnerClasses(DefaultInterfaceIssue143628636Test.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassRules(I.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("2", "5");
+  }
+
+  @NeverMerge
+  @NeverClassInline
+  public interface A {
+
+    @NeverInline
+    default void f(
+        // This parameter is needed otherwise the info recording bails out. (See b/143684659).
+        int z) {
+      // In the end the issue will manifest as a class-cast exception because B.g() is inlined here
+      // due to the incorrect conclusion that the only possible receiver type is B.
+      System.out.println(g() + z);
+    }
+
+    int g();
+  }
+
+  // This intermediate interface is needed to cause lookupInterfaceTargets to return null as I
+  // is pinned (why does it return null for a pinned holder is questionable, filed b/143686005).
+  // The return of null, will cause the outer lookup to hit a different lookup case where the
+  // refined receiver (here I) will cause the non-subtype method A.f to be pruned.
+  public interface I extends A {}
+
+  // Make sure this class and the call to h() are never eliminated. It is the *partial* info
+  // propagated from h() to f() that results in incorrect call-site optimization info.
+  @NeverMerge
+  @NeverClassInline
+  public static class B implements A {
+    public final int x;
+
+    public B(int x) {
+      this.x = x;
+    }
+
+    @Override
+    public int g() {
+      return x;
+    }
+
+    @NeverInline
+    public void h() {
+      // This will lookup A.f and propagate that the receiver is known to be B.
+      f(x);
+    }
+  }
+
+  // Make sure this class and the call to h() are never eliminated. It is the *missing* info
+  // propagated from h() to f() that results in incorrect call-site optimization info.
+  @NeverMerge
+  @NeverClassInline
+  public static class C implements I {
+    public final I i;
+    public final int y;
+
+    public C(I i, int y) {
+      this.i = i;
+      this.y = y;
+    }
+
+    @Override
+    public int g() {
+      return y;
+    }
+
+    @NeverInline
+    public void h() {
+      // Due to the refined receiver type I used here, the lookup targets will omit A.f !
+      // Thus the info propagation does not propagate that I (and thus C) may be a receiver type.
+      i.f(y);
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new B(args.length + 1).h();
+      new C(args.length == 42 ? null : new C(null, args.length + 2), args.length + 3).h();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ifs/EnumAliasComparisonTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ifs/EnumAliasComparisonTest.java
new file mode 100644
index 0000000..5c0f75b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ifs/EnumAliasComparisonTest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.ifs;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EnumAliasComparisonTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public EnumAliasComparisonTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(EnumAliasComparisonTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("true");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(TestClass.class);
+    assertThat(classSubject, isPresent());
+
+    MethodSubject mainMethod = classSubject.mainMethod();
+    assertThat(mainMethod, isPresent());
+    assertTrue(
+        mainMethod
+            .streamInstructions()
+            .filter(InstructionSubject::isStaticGet)
+            .map(InstructionSubject::getField)
+            .map(field -> field.name.toSourceString())
+            .allMatch("out"::equals));
+
+    assertThat(inspector.clazz(MyEnum.class), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      // Should print "true" since MyEnum.B is an alias of MyEnum.A.
+      System.out.println(MyEnum.A == MyEnum.B);
+    }
+  }
+
+  enum MyEnum {
+    A;
+
+    // Introduce an alias of MyEnum.A.
+    static MyEnum B = A;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B143686595.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B143686595.java
new file mode 100644
index 0000000..036bde7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B143686595.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.membervaluepropagation;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class B143686595 extends TestBase {
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public B143686595(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    R8TestCompileResult libraryResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(I.class)
+            .addKeepClassAndMembersRules(I.class)
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addClasspathClasses(I.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addRunClasspathFiles(libraryResult.writeToZip())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("0.4");
+  }
+
+  interface I {
+    float foo(float v);
+  }
+
+  static class TestClass {
+    static final String UNNECESSARY_PUT;
+
+    static {
+      // Will trigger AppInfoWithLiveness#withoutStaticFieldsWrites that should have copied caches
+      // of relations between synthesized classes and their super types.
+      UNNECESSARY_PUT = "DEAD";
+    }
+
+    static final I DOUBLER = t -> t * 2.0f;
+
+    @NeverInline
+    static I wrap(I previous, float base) {
+      // Interface desugaring
+      return t -> previous.foo(t / base);
+    }
+
+    public static void main(String... args) {
+      I i = wrap(DOUBLER, 10f);
+      // Without such consistent cache, this will fail with an assertion error that lower/upper
+      // bound types of receiver are mismatching.
+      System.out.println(i.foo(2f));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/CompanionAsArgumentTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/CompanionAsArgumentTest.java
index cd52ef0..92cc700 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/CompanionAsArgumentTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/CompanionAsArgumentTest.java
@@ -27,7 +27,7 @@
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
     // TODO(b/112831361): support for class staticizer in CF backend.
-    return getTestParameters().withDexRuntimes().build();
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -43,7 +43,7 @@
         .addKeepMainRule(MAIN)
         .enableInliningAnnotations()
         .enableClassInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("Companion#foo(true)")
         .inspect(this::inspect);
@@ -55,9 +55,10 @@
     assertThat(companion, isPresent());
     MethodSubject foo = companion.uniqueMethodWithName("foo");
     assertThat(foo, isPresent());
-    assertTrue(foo.streamInstructions().anyMatch(
-        i -> i.isInvokeVirtual()
-            && i.getMethod().toSourceString().contains("PrintStream.println")));
+    assertTrue(
+        foo.streamInstructions().anyMatch(
+            i -> i.isInvokeVirtual()
+                && i.getMethod().toSourceString().contains("PrintStream.println")));
 
     // Nothing migrated from Companion to Host.
     ClassSubject host = inspector.clazz(Host.class);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InstanceInsideCompanionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InstanceInsideCompanionTest.java
new file mode 100644
index 0000000..fe55066
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InstanceInsideCompanionTest.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.staticizer;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InstanceInsideCompanionTest extends TestBase {
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    // TODO(b/112831361): support for class staticizer in CF backend.
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InstanceInsideCompanionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void b143684491() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InstanceInsideCompanionTest.class)
+        .addKeepMainRule(MAIN)
+        .enableInliningAnnotations()
+        .enableClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("Candidate#foo(false)")
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    // Check if the instance is gone.
+    ClassSubject host = inspector.clazz(Candidate.Host.class);
+    assertThat(host, isPresent());
+    FieldSubject instance = host.uniqueFieldWithName("INSTANCE");
+    assertThat(instance, not(isPresent()));
+
+    ClassSubject candidate = inspector.clazz(Candidate.class);
+    assertThat(candidate, not(isPresent()));
+
+    // Check if the candidate method is staticized and migrated.
+    MethodSubject foo = host.uniqueMethodWithName("foo");
+    assertThat(foo, isPresent());
+    assertTrue(foo.isStatic());
+    assertTrue(
+        foo.streamInstructions().anyMatch(
+            i -> i.isInvokeVirtual()
+                && i.getMethod().toSourceString().contains("PrintStream.println")));
+  }
+
+  @NeverClassInline
+  static class Candidate {
+    private static class Host {
+      static final Candidate INSTANCE = new Candidate();
+    }
+
+    public static Candidate getInstance() {
+      return Host.INSTANCE;
+    }
+
+    @NeverInline
+    public void foo(Object arg) {
+      System.out.println("Candidate#foo(" + (arg != null) + ")");
+    }
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      Candidate.getInstance().foo(null);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
index 94e2a9c..d102004 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
@@ -81,9 +81,7 @@
   private void checkCompilationResult(D8TestCompileResult compileResult) throws Exception {
     if (parameters.getRuntime().asDex().getMinApiLevel().getLevel()
         < nativeMultiDexLevel.getLevel()) {
-      compileResult
-          .runDex2Oat(parameters.getRuntime().asDex().getVm())
-          .assertNoVerificationErrors();
+      compileResult.runDex2Oat(parameters.getRuntime()).assertNoVerificationErrors();
     } else {
       compileResult.run(parameters.getRuntime(), TestClass.class).assertSuccessWithOutput(EXPECTED);
     }
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index ce4df9d..d4b758e 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.retrace.RetraceCommand;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.base.Equivalence;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Predicate;
@@ -23,12 +22,82 @@
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeMatcher;
 
-class StackTrace {
+public class StackTrace {
 
   public static String AT_PREFIX = "at ";
   public static String TAB_AT_PREFIX = "\t" + AT_PREFIX;
 
-  static class StackTraceLine {
+  public static class Builder {
+
+    private List<StackTraceLine> stackTraceLines = new ArrayList<>();
+
+    private Builder() {}
+
+    public Builder add(StackTraceLine line) {
+      stackTraceLines.add(line);
+      return this;
+    }
+
+    public Builder addWithoutFileNameAndLineNumber(Class<?> clazz, String methodName) {
+      return addWithoutFileNameAndLineNumber(clazz.getTypeName(), methodName);
+    }
+
+    public Builder addWithoutFileNameAndLineNumber(String className, String methodName) {
+      stackTraceLines.add(
+          StackTraceLine.builder().setClassName(className).setMethodName(methodName).build());
+      return this;
+    }
+
+    public StackTrace build() {
+      return new StackTrace(
+          stackTraceLines,
+          StringUtils.join(
+              stackTraceLines.stream().map(StackTraceLine::toString).collect(Collectors.toList()),
+              "\n"));
+    }
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class StackTraceLine {
+
+    public static class Builder {
+      private String className;
+      private String methodName;
+      private String fileName;
+      private int lineNumber = -1;
+
+      private Builder() {}
+
+      public Builder setClassName(String className) {
+        this.className = className;
+        return this;
+      }
+
+      public Builder setMethodName(String methodName) {
+        this.methodName = methodName;
+        return this;
+      }
+
+      public Builder setFileName(String fileName) {
+        this.fileName = fileName;
+        return this;
+      }
+
+      public Builder setLineNumber(int lineNumber) {
+        this.lineNumber = lineNumber;
+        return this;
+      }
+
+      public StackTraceLine build() {
+        String lineNumberPart = lineNumber >= 0 ? ":" + lineNumber : "";
+        String originalLine = className + '.' + methodName + '(' + fileName + lineNumberPart + ')';
+        return new StackTraceLine(originalLine, className, methodName, fileName, lineNumber);
+      }
+    }
+
     public final String originalLine;
     public final String className;
     public final String methodName;
@@ -44,6 +113,10 @@
       this.lineNumber = lineNumber;
     }
 
+    public static Builder builder() {
+      return new Builder();
+    }
+
     public boolean hasLineNumber() {
       return lineNumber >= 0;
     }
@@ -202,7 +275,7 @@
     return extractFromJvm(result.getStdErr());
   }
 
-  public StackTrace retrace(String map) throws IOException {
+  public StackTrace retrace(String map) {
     class Box {
       List<String> result;
     }
diff --git a/src/test/java/com/android/tools/r8/regress/b118075510/Regress118075510Runner.java b/src/test/java/com/android/tools/r8/regress/b118075510/Regress118075510Runner.java
index 12bd452..e562b4f 100644
--- a/src/test/java/com/android/tools/r8/regress/b118075510/Regress118075510Runner.java
+++ b/src/test/java/com/android/tools/r8/regress/b118075510/Regress118075510Runner.java
@@ -5,21 +5,39 @@
 
 import com.android.tools.r8.AsmTestBase;
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.io.IOException;
+import java.util.List;
 import java.util.concurrent.ExecutionException;
 import org.junit.Assert;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class Regress118075510Runner extends AsmTestBase {
 
   public static final Class<?> CLASS = Regress118075510Test.class;
   public static final String EXPECTED = StringUtils.lines("0", "0");
 
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}, {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(), CompilationMode.values());
+  }
+
+  public Regress118075510Runner(TestParameters parameters, CompilationMode mode) {
+    this.parameters = parameters;
+  }
+
   @Test
   public void test()
       throws CompilationFailedException, IOException, ExecutionException, NoSuchMethodException {
@@ -32,9 +50,9 @@
     checkMethodContainsLongSignum(inspector, "fooNoTryCatch");
     checkMethodContainsLongSignum(inspector, "fooWithTryCatch");
     // Check the program runs on ART/Dalvik
-    d8Result.run(CLASS).assertSuccessWithOutput(EXPECTED);
+    d8Result.run(parameters.getRuntime(), CLASS).assertSuccessWithOutput(EXPECTED);
     // Check the program can be dex2oat compiled to arm64. This will diverge without the fixup.
-    d8Result.runDex2Oat().assertSuccess();
+    d8Result.runDex2Oat(parameters.getRuntime()).assertSuccess();
   }
 
   private void checkMethodContainsLongSignum(CodeInspector inspector, String methodName)
diff --git a/src/test/java/com/android/tools/r8/regress/b77842465/Regress77842465.java b/src/test/java/com/android/tools/r8/regress/b77842465/Regress77842465.java
index 3ab3467..196ca52 100644
--- a/src/test/java/com/android/tools/r8/regress/b77842465/Regress77842465.java
+++ b/src/test/java/com/android/tools/r8/regress/b77842465/Regress77842465.java
@@ -5,6 +5,8 @@
 
 import com.android.tools.r8.AsmTestBase;
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.io.IOException;
 import org.junit.Test;
@@ -18,7 +20,7 @@
         .noDesugaring()
         .setMinApi(AndroidApiLevel.M)
         .compile()
-        .runDex2Oat()
+        .runDex2Oat(new DexRuntime(ToolHelper.getDexVm()))
         .assertSuccess();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
index e914a7b..ccdb49b 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
@@ -84,7 +84,7 @@
   public void lookupSingleTarget() {
     ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
     assertFalse(resolutionResult.isValidVirtualTarget(appInfo.app().options));
-    DexEncodedMethod resolved = resolutionResult.asSingleTarget();
+    DexEncodedMethod resolved = resolutionResult.getSingleTarget();
     assertEquals(methodOnA, resolved.method);
     DexEncodedMethod singleVirtualTarget =
         appInfo.lookupSingleVirtualTarget(methodOnB, methodOnB.holder);
@@ -94,7 +94,7 @@
   @Test
   public void lookupVirtualTargets() {
     ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
-    DexEncodedMethod resolved = resolutionResult.asSingleTarget();
+    DexEncodedMethod resolved = resolutionResult.getSingleTarget();
     assertEquals(methodOnA, resolved.method);
     assertFalse(resolutionResult.isValidVirtualTarget(appInfo.app().options));
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
index 8d867d8..6e87ef3 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
@@ -143,7 +143,7 @@
   @Test
   public void lookupVirtualTargets() {
     ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
-    DexEncodedMethod resolved = resolutionResult.asSingleTarget();
+    DexEncodedMethod resolved = resolutionResult.getSingleTarget();
     assertEquals(methodOnA, resolved.method);
     assertFalse(resolutionResult.isValidVirtualTarget(appInfo.app().options));
   }
@@ -165,7 +165,7 @@
               .setMinApi(parameters.getApiLevel())
               .run(parameters.getRuntime(), Main.class);
     }
-    checkResult(runResult);
+    checkResult(runResult, false);
   }
 
   @Test
@@ -177,11 +177,11 @@
             .addKeepMainRule(Main.class)
             .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), Main.class);
-    checkResult(runResult);
+    checkResult(runResult, true);
   }
 
-  private void checkResult(TestRunResult<?> runResult) {
-    if (expectedToIncorrectlyRun(parameters.getRuntime())) {
+  private void checkResult(TestRunResult<?> runResult, boolean isCorrectedByR8) {
+    if (expectedToIncorrectlyRun(parameters.getRuntime(), isCorrectedByR8)) {
       // Do to incorrect resolution, some Art VMs will resolve to Base.f (ignoring A.f) and thus
       // virtual dispatch to C.f. See b/140013075.
       runResult.assertSuccessWithOutputLines("Called C.f");
@@ -190,8 +190,9 @@
     }
   }
 
-  private boolean expectedToIncorrectlyRun(TestRuntime runtime) {
-    return runtime.isDex()
+  private boolean expectedToIncorrectlyRun(TestRuntime runtime, boolean isCorrectedByR8) {
+    return !isCorrectedByR8
+        && runtime.isDex()
         && runtime.asDex().getVm().isNewerThan(DexVm.ART_4_4_4_HOST)
         && runtime.asDex().getVm().isOlderThanOrEqual(DexVm.ART_7_0_0_HOST);
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
index f968c03..7c435d3 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
@@ -121,7 +121,7 @@
   public void lookupSingleTarget() {
     ResolutionResult resolutionResult =
         appInfo.resolveMethodOnInterface(methodOnB.holder, methodOnB);
-    DexEncodedMethod resolved = resolutionResult.asSingleTarget();
+    DexEncodedMethod resolved = resolutionResult.getSingleTarget();
     assertEquals(methodOnB, resolved.method);
     assertFalse(resolutionResult.isValidVirtualTarget(appInfo.app().options));
     DexEncodedMethod singleVirtualTarget =
@@ -132,7 +132,7 @@
   @Test
   public void lookupVirtualTargets() {
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(methodOnB.holder, methodOnB);
-    DexEncodedMethod resolved = resolutionResult.asSingleTarget();
+    DexEncodedMethod resolved = resolutionResult.getSingleTarget();
     assertEquals(methodOnB, resolved.method);
     assertFalse(resolutionResult.isValidVirtualTarget(appInfo.app().options));
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
index 20c3cab..968f21e 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
@@ -167,7 +167,7 @@
   @Test
   public void lookupSingleTarget() {
     ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
-    DexEncodedMethod resolved = resolutionResult.asSingleTarget();
+    DexEncodedMethod resolved = resolutionResult.getSingleTarget();
     assertEquals(methodOnA, resolved.method);
     assertFalse(resolutionResult.isValidVirtualTarget(appInfo.app().options));
     DexEncodedMethod singleVirtualTarget =
@@ -200,7 +200,7 @@
               .setMinApi(parameters.getApiLevel())
               .run(parameters.getRuntime(), Main.class);
     }
-    checkResult(runResult);
+    checkResult(runResult, false);
   }
 
   @Test
@@ -212,11 +212,11 @@
             .addKeepMainRule(Main.class)
             .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), Main.class);
-    checkResult(runResult);
+    checkResult(runResult, true);
   }
 
-  private void checkResult(TestRunResult<?> runResult) {
-    if (expectedToIncorrectlyRun(parameters.getRuntime())) {
+  private void checkResult(TestRunResult<?> runResult, boolean isCorrectedByR8) {
+    if (expectedToIncorrectlyRun(parameters.getRuntime(), isCorrectedByR8)) {
       // Do to incorrect resolution, some Art VMs will resolve to Base.f (ignoring A.f) and thus
       // virtual dispatch to C.f. See b/140013075.
       runResult.assertSuccessWithOutputLines("Called C.f");
@@ -225,8 +225,9 @@
     }
   }
 
-  private boolean expectedToIncorrectlyRun(TestRuntime runtime) {
-    return runtime.isDex()
+  private boolean expectedToIncorrectlyRun(TestRuntime runtime, boolean isCorrectedByR8) {
+    return !isCorrectedByR8
+        && runtime.isDex()
         && runtime.asDex().getVm().isNewerThan(DexVm.ART_4_4_4_HOST)
         && runtime.asDex().getVm().isOlderThanOrEqual(DexVm.ART_7_0_0_HOST);
   }
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineWithoutNullCheck.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineWithoutNullCheck.java
new file mode 100644
index 0000000..6126c4b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineWithoutNullCheck.java
@@ -0,0 +1,267 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileNameAndLineNumber;
+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.AlwaysInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InlineWithoutNullCheck extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public InlineWithoutNullCheck(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public StackTrace expectedStackTraceForInlineMethod;
+  public StackTrace expectedStackTraceForInlineField;
+  public StackTrace expectedStackTraceForInlineStaticField;
+
+  @Before
+  public void setup() throws Exception {
+    // Get the expected stack traces by running on the runtime to test.
+    expectedStackTraceForInlineMethod =
+        testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+            .addInnerClasses(InlineWithoutNullCheck.class)
+            .run(parameters.getRuntime(), TestClassForInlineMethod.class)
+            .writeProcessResult(System.out)
+            .assertFailure()
+            .getStackTrace();
+    expectedStackTraceForInlineField =
+        testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+            .addInnerClasses(InlineWithoutNullCheck.class)
+            .run(parameters.getRuntime(), TestClassForInlineField.class)
+            .writeProcessResult(System.out)
+            .assertFailure()
+            .getStackTrace();
+    expectedStackTraceForInlineStaticField =
+        testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+            .addInnerClasses(InlineWithoutNullCheck.class)
+            .run(parameters.getRuntime(), TestClassForInlineStaticField.class)
+            .writeProcessResult(System.out)
+            .assertFailure()
+            .getStackTrace();
+
+    // Check the expected stack traces from running on the runtime to test.
+    assertThat(
+        expectedStackTraceForInlineMethod,
+        isSameExceptForFileNameAndLineNumber(
+            StackTrace.builder()
+                .addWithoutFileNameAndLineNumber(A.class, "inlineMethodWhichAccessInstanceMethod")
+                .addWithoutFileNameAndLineNumber(TestClassForInlineMethod.class, "main")
+                .build()));
+    assertThat(
+        expectedStackTraceForInlineField,
+        isSameExceptForFileNameAndLineNumber(
+            StackTrace.builder()
+                .addWithoutFileNameAndLineNumber(A.class, "inlineMethodWhichAccessInstanceField")
+                .addWithoutFileNameAndLineNumber(TestClassForInlineField.class, "main")
+                .build()));
+    assertThat(
+        expectedStackTraceForInlineStaticField,
+        isSameExceptForFileNameAndLineNumber(
+            StackTrace.builder()
+                .addWithoutFileNameAndLineNumber(A.class, "inlineMethodWhichAccessStaticField")
+                .addWithoutFileNameAndLineNumber(TestClassForInlineStaticField.class, "main")
+                .build()));
+  }
+
+  private void checkSomething(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(Result.class);
+    assertThat(classSubject, isPresent());
+    assertThat(
+        classSubject.uniqueMethodWithName("methodWhichAccessInstanceMethod"), not(isPresent()));
+    assertThat(
+        classSubject.uniqueMethodWithName("methodWhichAccessInstanceField"), not(isPresent()));
+    assertThat(classSubject.uniqueMethodWithName("methodWhichAccessStaticField"), not(isPresent()));
+  }
+
+  @Test
+  public void testInlineMethodWhichChecksNullReceiverBeforeAnySideEffectMethod() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InlineWithoutNullCheck.class)
+        .addKeepMainRule(TestClassForInlineMethod.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::checkSomething)
+        .run(parameters.getRuntime(), TestClassForInlineMethod.class)
+        .assertFailure()
+        // TODO(b/143607166): The stack trace has one more frame on the top than expected.
+        .inspectStackTrace(
+            stackTrace -> assertThat(expectedStackTraceForInlineMethod, not(isSame(stackTrace))))
+        .inspectStackTrace(
+            stackTrace ->
+                assertThat(
+                    stackTrace,
+                    isSameExceptForFileNameAndLineNumber(
+                        StackTrace.builder()
+                            .addWithoutFileNameAndLineNumber(
+                                Result.class, "methodWhichAccessInstanceMethod")
+                            .addWithoutFileNameAndLineNumber(
+                                A.class, "inlineMethodWhichAccessInstanceMethod")
+                            .addWithoutFileNameAndLineNumber(TestClassForInlineMethod.class, "main")
+                            .build())));
+  }
+
+  @Test
+  public void testInlineMethodWhichChecksNullReceiverBeforeAnySideEffectField() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InlineWithoutNullCheck.class)
+        .addKeepMainRule(TestClassForInlineField.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::checkSomething)
+        .run(parameters.getRuntime(), TestClassForInlineField.class)
+        .assertFailure()
+        // TODO(b/143607166): The stack trace has one more frame on the top than expected.
+        .inspectStackTrace(
+            stackTrace -> assertThat(expectedStackTraceForInlineField, not(isSame(stackTrace))))
+        .inspectStackTrace(
+            stackTrace ->
+                assertThat(
+                    stackTrace,
+                    isSameExceptForFileNameAndLineNumber(
+                        StackTrace.builder()
+                            .addWithoutFileNameAndLineNumber(
+                                Result.class, "methodWhichAccessInstanceField")
+                            .addWithoutFileNameAndLineNumber(
+                                A.class, "inlineMethodWhichAccessInstanceField")
+                            .addWithoutFileNameAndLineNumber(TestClassForInlineField.class, "main")
+                            .build())));
+  }
+
+  @Test
+  public void testInlineMethodWhichChecksNullReceiverBeforeAnySideEffectStaticField()
+      throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InlineWithoutNullCheck.class)
+        .addKeepMainRule(TestClassForInlineStaticField.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::checkSomething)
+        .run(parameters.getRuntime(), TestClassForInlineStaticField.class)
+        .assertFailure()
+        // TODO(b/143607166): The stack trace has one more frame on the top than expected.
+        .inspectStackTrace(
+            stackTrace ->
+                assertThat(expectedStackTraceForInlineStaticField, not(isSame(stackTrace))))
+        .inspectStackTrace(
+            stackTrace ->
+                assertThat(
+                    stackTrace,
+                    isSameExceptForFileNameAndLineNumber(
+                        StackTrace.builder()
+                            .addWithoutFileNameAndLineNumber(
+                                Result.class, "methodWhichAccessStaticField")
+                            .addWithoutFileNameAndLineNumber(
+                                A.class, "inlineMethodWhichAccessStaticField")
+                            .addWithoutFileNameAndLineNumber(
+                                TestClassForInlineStaticField.class, "main")
+                            .build())));
+  }
+
+  static class TestClassForInlineMethod {
+
+    public static void main(String[] args) {
+      A a = new A(System.currentTimeMillis() > 0 ? null : new Result());
+      System.out.print(a.inlineMethodWhichAccessInstanceMethod());
+    }
+  }
+
+  static class TestClassForInlineField {
+
+    public static void main(String[] args) {
+      A a = new A(System.currentTimeMillis() > 0 ? null : new Result());
+      System.out.print(a.inlineMethodWhichAccessInstanceField());
+    }
+  }
+
+  static class TestClassForInlineStaticField {
+
+    public static void main(String[] args) {
+      A a = new A(System.currentTimeMillis() > 0 ? null : new Result());
+      System.out.print(a.inlineMethodWhichAccessStaticField());
+    }
+  }
+
+  static class A {
+    @NeverPropagateValue Result result;
+
+    A(Result result) {
+      this.result = result;
+    }
+
+    @NeverInline
+    Result get() {
+      return result;
+    }
+
+    @NeverInline
+    int inlineMethodWhichAccessInstanceMethod() {
+      return get().methodWhichAccessInstanceMethod();
+    }
+
+    @NeverInline
+    int inlineMethodWhichAccessInstanceField() {
+      return get().methodWhichAccessInstanceField();
+    }
+
+    @NeverInline
+    int inlineMethodWhichAccessStaticField() {
+      return get().methodWhichAccessStaticField();
+    }
+  }
+
+  static class Result {
+    @NeverPropagateValue int x = 1;
+    @NeverPropagateValue static int y = 1;
+
+    @AlwaysInline
+    int methodWhichAccessInstanceMethod() {
+      return privateMethod();
+    }
+
+    @AlwaysInline
+    int methodWhichAccessInstanceField() {
+      return x;
+    }
+
+    @AlwaysInline
+    int methodWhichAccessStaticField() {
+      return Result.y;
+    }
+
+    @NeverInline
+    private int privateMethod() {
+      return 0;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
index 73c42d0..3369fb4 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.rewrite.enums;
 
-import static com.android.tools.r8.ToolHelper.getDefaultAndroidJar;
 import static java.util.Collections.emptyList;
 import static java.util.stream.Collectors.toList;
 import static org.junit.Assert.assertEquals;
@@ -30,12 +29,14 @@
  */
 @RunWith(Parameterized.class)
 public class EnumOptimizationTest extends TestBase {
+
   private final boolean enableOptimization;
   private final TestParameters parameters;
 
   @Parameters(name = "{1}, enable enum optimization: {0}")
   public static List<Object[]> data() {
-    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
   public EnumOptimizationTest(boolean enableOptimization, TestParameters parameters) {
@@ -50,12 +51,11 @@
   @Test
   public void ordinals() throws Exception {
     testForR8(parameters.getBackend())
-        .addLibraryFiles(getDefaultAndroidJar())
         .addProgramClassesAndInnerClasses(Ordinals.class)
         .addKeepMainRule(Ordinals.class)
         .enableInliningAnnotations()
         .addOptionsModification(this::configure)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspectOrdinals)
         .run(parameters.getRuntime(), Ordinals.class)
@@ -96,12 +96,11 @@
   @Test
   public void names() throws Exception {
     testForR8(parameters.getBackend())
-        .addLibraryFiles(getDefaultAndroidJar())
         .addProgramClassesAndInnerClasses(Names.class)
         .addKeepMainRule(Names.class)
         .enableInliningAnnotations()
         .addOptionsModification(this::configure)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspectNames)
         .run(parameters.getRuntime(), Names.class)
@@ -139,17 +138,17 @@
   @Test
   public void toStrings() throws Exception {
     testForR8(parameters.getBackend())
-        .addLibraryFiles(getDefaultAndroidJar())
         .addProgramClassesAndInnerClasses(ToStrings.class)
         .addKeepMainRule(ToStrings.class)
         .enableInliningAnnotations()
         .addOptionsModification(this::configure)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspectToStrings)
         .run(parameters.getRuntime(), ToStrings.class)
-        .assertSuccessWithOutputLines("one", "one", "TWO", "TWO", "TWO", "1TWO", "TWO", "SECONDS",
-            "DOWN", "TWO", "TWO", "TWO");
+        .assertSuccessWithOutputLines(
+            "one", "one", "TWO", "TWO", "TWO", "1TWO", "TWO", "SECONDS", "DOWN", "TWO", "TWO",
+            "TWO");
   }
 
   private void inspectToStrings(CodeInspector inspector) {
@@ -157,7 +156,11 @@
     assertTrue(clazz.isPresent());
 
     assertToStringWasNotReplaced(clazz.uniqueMethodWithName("typeToString"));
-    assertToStringWasNotReplaced(clazz.uniqueMethodWithName("valueWithToString"));
+    if (parameters.isCfRuntime()) {
+      assertToStringWasNotReplaced(clazz.uniqueMethodWithName("valueWithToString"));
+    } else {
+      assertToStringReplacedWithConst(clazz.uniqueMethodWithName("valueWithToString"), "one");
+    }
     assertToStringWasNotReplaced(clazz.uniqueMethodWithName("valueWithoutToString"));
 
     if (enableOptimization) {
diff --git a/src/test/java/com/android/tools/r8/shaking/b136195382/AbstractBridgeInheritTest.java b/src/test/java/com/android/tools/r8/shaking/b136195382/AbstractBridgeInheritTest.java
index 2514381..f1fc012 100644
--- a/src/test/java/com/android/tools/r8/shaking/b136195382/AbstractBridgeInheritTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/b136195382/AbstractBridgeInheritTest.java
@@ -27,7 +27,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public AbstractBridgeInheritTest(TestParameters parameters) {
@@ -42,6 +42,7 @@
             Service.class, Factory.class, SubService.class, SubFactory.class, Main.class)
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("Hello World!")
         .inspector();
diff --git a/src/test/java/com/android/tools/r8/shaking/reflection/GetDeclaredFieldShouldMarkFieldAsWrittenTest.java b/src/test/java/com/android/tools/r8/shaking/reflection/GetDeclaredFieldShouldMarkFieldAsWrittenTest.java
new file mode 100644
index 0000000..0efa7fa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/reflection/GetDeclaredFieldShouldMarkFieldAsWrittenTest.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.reflection;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class GetDeclaredFieldShouldMarkFieldAsWrittenTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public GetDeclaredFieldShouldMarkFieldAsWrittenTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("42");
+  }
+
+  static class TestClass {
+
+    static int f = -1;
+
+    public static void main(String[] args) throws Exception {
+      TestClass.class.getDeclaredField("f").set(null, 42);
+      System.out.println(f);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/testing/StackTraceTest.java b/src/test/java/com/android/tools/r8/testing/StackTraceTest.java
new file mode 100644
index 0000000..33915ef
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/testing/StackTraceTest.java
@@ -0,0 +1,224 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.testing;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileName;
+import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileNameAndLineNumber;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class StackTraceTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public StackTraceTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJvmStackTrace() throws Exception {
+    String stderr =
+        StringUtils.join(
+            ImmutableList.of(
+                "Exception in thread \"main\" java.lang.RuntimeException",
+                "\tat com.example.A.method2(Test.java:30)",
+                "\tat com.example.A.method1(Test.java:20)",
+                "\tat com.example.Main.main(Test.java:10)"),
+            "\n");
+    checkStackTrace(StackTrace.extractFromJvm(stderr));
+  }
+
+  @Test
+  public void testArtStackTrace1() {
+    String stderr =
+        StringUtils.join(
+            "\n",
+            "dex2oat I 10-30 11:41:40 232588 232588 dex2oat.cc:3108]"
+                + " /usr/local/prj/r8-public/ws1/tools/linux/art/bin/../bin/dex2oat"
+                + " --instruction-set=x86_64"
+                + " --instruction-set-features=ssse3,sse4.1,sse4.2,-avx,-avx2,popcnt --runtime-arg"
+                + " -Xnorelocate --host"
+                + " --boot-image=/usr/local/prj/r8-public/ws1/tools/linux/art/bin/../framework/core.art"
+                + " --dex-file=/tmp/junit5857866495600860150/junit14865793933637294589/out.zip"
+                + " --output-vdex-fd=7 --oat-fd=8"
+                + " --oat-location=/tmp/junit5857866495600860150/junit14865793933637294589/oat/x86_64/out.odex"
+                + " --compiler-filter=quicken --class-loader-context=PCL[]\n",
+            "dex2oat I 10-30 11:41:40 232588 232588 dex2oat.cc:2808] dex2oat took 94.860ms"
+                + " (71.941ms cpu) (threads: 72) arena alloc=3KB (3312B) java alloc=32KB (32800B)"
+                + " native alloc=440KB (450720B) free=9MB (9539424B)",
+            "Exception in thread \"main\" java.lang.RuntimeException",
+            "\tat com.example.A.method2(Test.java:30)",
+            "\tat com.example.A.method1(Test.java:20)",
+            "\tat com.example.Main.main(Test.java:10)");
+    checkStackTrace(StackTrace.extractFromArt(stderr, DexVm.ART_5_1_1_HOST));
+  }
+
+  @Test
+  public void testArtStackTrace2() {
+    String stderr =
+        StringUtils.join(
+            "\n",
+            "dex2oat I 10-30 11:41:40 232588 232588 dex2oat.cc:3108]"
+                + " /usr/local/prj/r8-public/ws1/tools/linux/art/bin/../bin/dex2oat"
+                + " --instruction-set=x86_64"
+                + " --instruction-set-features=ssse3,sse4.1,sse4.2,-avx,-avx2,popcnt --runtime-arg"
+                + " -Xnorelocate --host"
+                + " --boot-image=/usr/local/prj/r8-public/ws1/tools/linux/art/bin/../framework/core.art"
+                + " --dex-file=/tmp/junit5857866495600860150/junit14865793933637294589/out.zip"
+                + " --output-vdex-fd=7 --oat-fd=8"
+                + " --oat-location=/tmp/junit5857866495600860150/junit14865793933637294589/oat/x86_64/out.odex"
+                + " --compiler-filter=quicken --class-loader-context=PCL[]\n",
+            "Exception in thread \"main\" java.lang.RuntimeException",
+            "\tat com.example.A.method2(Test.java:30)",
+            "\tat com.example.A.method1(Test.java:20)",
+            "\tat com.example.Main.main(Test.java:10)",
+            "dex2oat I 10-30 11:41:40 232588 232588 dex2oat.cc:2808] dex2oat took 94.860ms"
+                + " (71.941ms cpu) (threads: 72) arena alloc=3KB (3312B) java alloc=32KB (32800B)"
+                + " native alloc=440KB (450720B) free=9MB (9539424B)"
+            );
+    checkStackTrace(StackTrace.extractFromArt(stderr, DexVm.ART_5_1_1_HOST));
+  }
+
+  private void checkStackTrace(StackTrace stackTrace) {
+    assertThat(
+        stackTrace,
+        isSame(
+            StackTrace.builder()
+                .add(
+                    StackTraceLine.builder()
+                        .setClassName("com.example.A")
+                        .setMethodName("method2")
+                        .setFileName("Test.java")
+                        .setLineNumber(30)
+                        .build())
+                .add(
+                    StackTraceLine.builder()
+                        .setClassName("com.example.A")
+                        .setMethodName("method1")
+                        .setFileName("Test.java")
+                        .setLineNumber(20)
+                        .build())
+                .add(
+                    StackTraceLine.builder()
+                        .setClassName("com.example.Main")
+                        .setMethodName("main")
+                        .setFileName("Test.java")
+                        .setLineNumber(10)
+                        .build())
+                .build()));
+
+    assertThat(
+        stackTrace,
+        isSameExceptForFileName(
+            StackTrace.builder()
+                .add(
+                    StackTraceLine.builder()
+                        .setClassName("com.example.A")
+                        .setMethodName("method2")
+                        .setLineNumber(30)
+                        .build())
+                .add(
+                    StackTraceLine.builder()
+                        .setClassName("com.example.A")
+                        .setMethodName("method1")
+                        .setLineNumber(20)
+                        .build())
+                .add(
+                    StackTraceLine.builder()
+                        .setClassName("com.example.Main")
+                        .setMethodName("main")
+                        .setLineNumber(10)
+                        .build())
+                .build()));
+
+    assertThat(
+        stackTrace,
+        isSameExceptForFileNameAndLineNumber(
+            StackTrace.builder()
+                .add(
+                    StackTraceLine.builder()
+                        .setClassName("com.example.A")
+                        .setMethodName("method2")
+                        .build())
+                .add(
+                    StackTraceLine.builder()
+                        .setClassName("com.example.A")
+                        .setMethodName("method1")
+                        .build())
+                .add(
+                    StackTraceLine.builder()
+                        .setClassName("com.example.Main")
+                        .setMethodName("main")
+                        .build())
+                .build()));
+  }
+
+  @Test
+  public void testJvmStackTraceFromRunning() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addInnerClasses(StackTraceTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailure()
+        .inspectStackTrace(this::checkStackTraceFromRunning);
+  }
+
+  @Test
+  public void testArtStackTraceFromRunning() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addInnerClasses(StackTraceTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailure()
+        .inspectStackTrace(this::checkStackTraceFromRunning);
+  }
+
+  private void checkStackTraceFromRunning(StackTrace stackTrace) {
+    assertThat(
+        stackTrace,
+        isSameExceptForFileNameAndLineNumber(
+            StackTrace.builder()
+                .addWithoutFileNameAndLineNumber(A.class, "method2")
+                .addWithoutFileNameAndLineNumber(A.class, "method1")
+                .addWithoutFileNameAndLineNumber(Main.class, "main")
+                .build()));
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      new A().method1();
+    }
+  }
+
+  static class A {
+    public void method1() {
+      method2();
+    }
+
+    public void method2() {
+      throw new RuntimeException();
+    }
+  }
+}
diff --git a/tools/internal_test.py b/tools/internal_test.py
index 9d4bc2b..182ccf8 100755
--- a/tools/internal_test.py
+++ b/tools/internal_test.py
@@ -79,6 +79,9 @@
         'find-xmx-max': 1200,
         'find-xmx-range': 32,
         'oom-threshold': 1037,
+        # TODO(b/143431825): Youtube can OOM randomly in memory configurations
+        #  that should work.
+        'skip-find-xmx-max': True,
     },
     {
         'app': 'iosched',
@@ -108,7 +111,7 @@
       '--find-min-xmx-archive']
 
 def compile_with_memory_max_command(record):
-  return [
+  return [] if 'skip-find-xmx-max' in record else [
       'tools/run_on_app.py',
       '--compiler=r8',
       '--compiler-build=lib',
@@ -144,7 +147,6 @@
      + map(compile_with_memory_max_command, BENCHMARK_APPS)
      + map(compile_with_memory_min_command, BENCHMARK_APPS))
 
-
 # Command timeout, in seconds.
 RUN_TIMEOUT = 3600 * 6
 BOT_RUN_TIMEOUT = RUN_TIMEOUT * len(TEST_COMMANDS)
@@ -349,6 +351,9 @@
       print 'stdout: %s' % f.read()
 
 def execute(cmd, archive, env=None):
+  if cmd == []:
+    return
+
   utils.PrintCmd(cmd)
   with utils.TempDir() as temp:
     try:
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 3575edf..0048d52 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -75,16 +75,25 @@
         sed(old_version, version, R8_VERSION_FILE)
 
         subprocess.check_call([
-          'git', 'commit', '-a', '-m', '"Version %s"' % version])
+          'git', 'commit', '-a', '-m', 'Version %s' % version])
 
         version_diff_output = subprocess.check_output([
           'git', 'diff', '%s..HEAD' % commithash])
 
         invalid = version_change_diff(version_diff_output, "master", version)
         if invalid:
-          print "Unexpected diff content for line:"
-          print invalid
-          sys.exit(1)
+          print "Unexpected diff:"
+          print "=" * 80
+          print version_diff_output
+          print "=" * 80
+          accept_string = 'THE DIFF IS OK!'
+          input = raw_input(
+            "Accept the additonal diff as part of the release? "
+            "Type '%s' to accept: " % accept_string)
+          if input != accept_string:
+            print "You did not type '%s'" % accept_string
+            print 'Aborting dev release for %s' % version
+            sys.exit(1)
 
         # Double check that we want to push the release.
         if not args.dry_run:
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index e4ab909..8d7264b 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -402,6 +402,18 @@
   })
 ]
 
+
+class EnsureNoGradleAlive(object):
+ def __init__(self, active):
+   self.active = active
+
+ def __enter__(self):
+   if self.active:
+     print 'Running with wrapper that will kill java after'
+
+ def __exit__(self, *_):
+   subprocess.call(['pkill', 'java'])
+
 def GetAllApps():
   apps = []
   for repo in APP_REPOSITORIES:
@@ -783,6 +795,9 @@
 
   profile_dest_dir = os.path.join(out_dir, 'profile')
   as_utils.MoveProfileReportTo(profile_dest_dir, stdout, quiet=options.quiet)
+  # Ensure that the gradle daemon is stopped if we are running with it.
+  if options.use_daemon:
+    utils.RunGradlew(['--stop', '-g=' + os.path.join(checkout_dir, GRADLE_USER_HOME)])
 
   return (apk_dest, profile_dest_dir, proguard_config_dest)
 
@@ -1289,13 +1304,14 @@
         shutil.copyfile(utils.R8LIB_JAR, os.path.join(temp_dir, 'r8lib.jar'))
 
     result_per_shrinker_per_app = []
-
-    for (app, repo) in options.apps:
-      if app.skip:
-        continue
-      result_per_shrinker_per_app.append(
-          (app, GetResultsForApp(app, repo, options, temp_dir)))
-
+    # If we are running on golem we kill all java processes after the run
+    # to ensure no hanging gradle daemons.
+    with EnsureNoGradleAlive(options.golem):
+      for (app, repo) in options.apps:
+        if app.skip:
+          continue
+        result_per_shrinker_per_app.append(
+            (app, GetResultsForApp(app, repo, options, temp_dir)))
     return LogResultsForApps(result_per_shrinker_per_app, options)
 
 def success(message):